diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000000..5e4edee99e1 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +.env +icon_cache +logs +dist +node_modules +coverage +.yarn +.tests \ No newline at end of file diff --git a/.env.example b/.env.example index 5f0850d3dda..be4dcf13509 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,14 @@ +# You need to change these: +CLIENT_ID=PUT_YOUR_CLIENT_ID_HERE +BOT_TOKEN=PUT_YOUR_TOKEN_HERE + +# You may need to change these: +ROBOCHIMP_DATABASE_URL=postgresql://postgres:postgres@localhost:5436/robochimp_test +DATABASE_URL=postgresql://postgres:postgres@localhost:5435/osb_test + +# Optional +#REDIS_PORT=6379 +#TESTING_SERVER_ID=123456789012345678 + +# Dont change these: TZ="UTC" -ROBOCHIMP_DATABASE_URL=postgresql://postgres:postgres@localhost:5436/robochimp_integration_test -DATABASE_URL=postgresql://postgres:postgres@localhost:5435/osb_integration_test?connection_limit=20&pool_timeout=120 -PATREON_CAMPAIGN_ID=1234 -PATREON_TOKEN=asdfasdfasdf -PATREON_WEBHOOK_SECRET=asdfasdfasdf -HTTP_PORT=7373 -CLIENT_ID=111398433321891634 diff --git a/.env.test b/.env.test index 0ca10b7ce86..63f5c51628b 100644 --- a/.env.test +++ b/.env.test @@ -1,9 +1,6 @@ TZ="UTC" -ROBOCHIMP_DATABASE_URL=postgresql://postgres:postgres@localhost:5436/robochimp_integration_test?connection_limit=1000 -DATABASE_URL=postgresql://postgres:postgres@localhost:5435/osb_integration_test?pool_timeout=120&connection_limit=1000 -PATREON_CAMPAIGN_ID=1234 -PATREON_TOKEN=asdfasdfasdf -PATREON_WEBHOOK_SECRET=asdfasdfasdf -HTTP_PORT=7373 CLIENT_ID=111398433321891634 -# PRISMA_CLIENT_ENGINE_TYPE=binary +BOT_TOKEN=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +TEST=true +CI=true +YARN_ENABLE_HARDENED_MODE=0 \ No newline at end of file diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 443036b3cfa..00000000000 --- a/.eslintignore +++ /dev/null @@ -1,7 +0,0 @@ -node_modules/ -dist/ -*.js -icon_cache/ -licenses/ -coverage/ -.github/ \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index ae0cfc51597..00000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "extends": ["@oldschoolgg"], - "rules": { - "@typescript-eslint/naming-convention": 0, - "@typescript-eslint/no-throw-literal": 0, - "@typescript-eslint/default-param-last": 0, - "no-return-await": "error", - "import/no-cycle": "error", - "@typescript-eslint/strict-boolean-expressions": "warn", - "@typescript-eslint/no-misused-promises": [ - "error", - { - "checksVoidReturn": false - } - ], - "@typescript-eslint/no-base-to-string": "error", - "eqeqeq": "error", - "no-shadow": "warn", - "@typescript-eslint/no-floating-promises": "warn", - "@typescript-eslint/ban-ts-comment": "off" - } -} diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 00000000000..52f6fcf8cb7 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,2 @@ +BSO: + - base-branch: "bso" diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index 5671a71eacb..5442864076e 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -9,38 +9,15 @@ on: jobs: test: - name: Node v${{ matrix.node_version }} - ${{ matrix.os }} - runs-on: ${{ matrix.os }} + runs-on: ubuntu-latest timeout-minutes: 10 - strategy: - matrix: - node_version: [18.12.0, 20] - os: [ubuntu-latest] steps: - - name: Install System Packages - run: sudo apt-get install -y build-essential libpq-dev - - name: Checkout Project - uses: actions/checkout@v3 - - name: Use Node.js ${{ matrix.node_version }} - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node_version }} - cache: yarn - - name: Restore CI Cache - uses: actions/cache@v3 - with: - path: node_modules - key: ${{ runner.os }}-${{ matrix.node_version }}-${{ hashFiles(matrix.os == 'windows-latest' && '**\yarn.lock' || '**/yarn.lock') }} - - name: Install Dependencies - run: yarn --frozen-lockfile - - name: Copy Configuration - run: | - pushd src && - cp config.example.ts config.ts && - popd - - name: Copy env - run: cp .env.test .env - - name: Test - run: yarn test:integration + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Run Integration Tests + run: docker compose up --build --abort-on-container-exit --remove-orphans && docker compose down --volumes --remove-orphans diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 00000000000..da09e597730 --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,15 @@ +name: "Pull Request Labeler" +on: + pull_request_target: + types: [opened, synchronize, reopened] + +jobs: + labeler: + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/labeler@v5 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 168e1a2245f..118047b3da8 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -11,46 +11,36 @@ jobs: test: name: Node v${{ matrix.node_version }} - ${{ matrix.os }} runs-on: ${{ matrix.os }} - timeout-minutes: 10 + timeout-minutes: 5 strategy: matrix: - node_version: [18.12.0, 20] + node_version: [20.15.0] os: [ubuntu-latest] steps: - name: Checkout Project - uses: actions/checkout@v3 + uses: actions/checkout@v4 + - run: corepack enable && corepack install - name: Use Node.js ${{ matrix.node_version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node_version }} cache: yarn - name: Restore CI Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: node_modules - key: ${{ runner.os }}-${{ matrix.node_version }}-${{ hashFiles(matrix.os == 'windows-latest' && '**\yarn.lock' || '**/yarn.lock') }} + key: ${{ runner.os }}-${{ matrix.node_version }}-${{ hashFiles('**/yarn.lock') }} - name: Install Dependencies - run: yarn --frozen-lockfile + run: yarn --immutable - name: Copy Configuration run: | pushd src && cp config.example.ts config.ts && - popd - - name: Copy env - run: cp .env.test .env - - name: Generate Prisma Client + popd && cp .env.test .env + - name: Generate Prisma Clients run: yarn gen - - name: Run ESLint on changed files - uses: tj-actions/eslint-changed-files@v21 - with: - skip_annotations: true - config_path: ".eslintrc.json" - ignore_path: ".eslintignore" - file_extensions: | - **/*.ts - **/*.tsx - name: Build - run: yarn build + run: yarn build:tsc - name: Test - run: yarn test:unit + run: yarn test:ci:unit diff --git a/.gitignore b/.gitignore index 297cc60a2f8..96b1b2307aa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,5 @@ # Klasa *.log -bot/bwd/provider/json/ -src/bwd/provider/json/ /config/private.js /config/private.json private.json @@ -32,3 +30,18 @@ node_modules/ .eslintcache /coverage +*.cpuprofile + +cache.json + +tests/**/*.tsbuildinfo + +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions +overallItemsNotCheckedFor.txt +*.diff \ No newline at end of file diff --git a/.prettierignore b/.prettierignore index 53c37a16608..3d2d6656057 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,5 @@ -dist \ No newline at end of file +dist +coverage +node_modules +logs +licenses \ No newline at end of file diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 00000000000..9e339706948 --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1,5 @@ +nodeLinker: node-modules +telemetryInterval: 999999999999 +enableTelemetry: false +checksumBehavior: "update" +enableHardenedMode: false diff --git a/README.md b/README.md index 5ab549e99a3..90547eda100 100644 --- a/README.md +++ b/README.md @@ -22,49 +22,9 @@ To suggest a new feature, [click here](https://github.com/oldschoolgg/oldschoolb ## Contributing -Anyone is free to create PR's with improvements and additions to Old School Bot. - -Please lint your code with the projects' [ESLint](https://eslint.org/) config. - -Contributors are listed in this file, and given a Contributor role in the support server. If you have more questions, there are lots of helpful Contributors in the `#developers` channel on the Discord server. - -### Setting up the bot to run locally for contributing - -To run the bot, you need the following things first: Git, [NodeJS v18+](https://nodejs.org/en/), [Postgres](https://www.postgresql.org/download/), [Python](https://www.python.org/) and a discord bot account. - -#### **Setting up a Discord Bot** - -1. Head to [Discord Developers](https://discord.com/developers) and create an application. -2. Once created, click into your Application. -3. Copy and store the Application ID, you'll need this later on. -4. Create a Bot on the Bot tab. Copy and store the token for your bot, you'll need this later on. -5. Ensure your bot has `Privileged Gateway Intents > Server Members Intent` enabled. -6. Invite your bot to your server via this URL. Be sure to input your `Application ID` into the URL. `https://discord.com/api/oauth2/authorize?client_id=&permissions=2198754295617&scope=applications.commands%20bot` - -#### **Setting up your environment** - -1. Clone the repository: `git clone https://github.com/oldschoolgg/oldschoolbot.git` -2. Change into the new directory: `cd oldschoolbot` -3. Install the yarn dependency: `npm install --global yarn` -4. Make a config file from the example: `cp src/config.example.ts src/config.ts` -5. Edit this new `config.ts` file: - 1. Input your bot token you retrieved earlier into `botToken` - 2. Input your Application ID you retrieved earlier into `BotID` - 3. Copy your Discord ID into both `OWNER_IDS` and `ADMIN_IDS`. You can get your Discord ID by opening Settings, selecting My Account, selecting the three dots next to your user name and selecting Copy ID. You may need to enable Developer Mode in Advanced Settings to be given this option. - 4. Enter the Server ID where you want to Administer your bot from in `SupportServer`. You can get this by right clicking the logo of the server and selecting Copy ID. - 5. Enter the Server ID into `DEV_SERVER_ID` -6. Make a .env file copy from the example `cp .env.example .env` -7. Update this new `.env` file: - 1. Input your username, password, and schema names into `DATABASE_URL` and `ROBOCHIMP_DATABASE_URL` using the format `postgresql://USER:PASSWORD@HOST:PORT/DATABASE` - 2. Input your Application ID you retrieved earlier into `CLIENT_ID` -8. Run `yarn` then `yarn install` -9. Run `npx prisma generate` to generate the Prisma client files and load the DSN from #6. -10. Run `npx prisma db push` to create the tables on the database referenced in .env -11. Run `npx prisma generate --schema=./prisma/robochimp.prisma` to generate the Prisma client files and load the DSN from #6 for the `robochimp` database. -12. Run `npx prisma db push --schema=./prisma/robochimp.prisma` to create the tables on the database referenced in .env for the `robochimp` database. -13. Run `yarn build` - then run `yarn start`. In the future, you can type only `yarn start` to start the bot. - -If you have errors or issues, you can ask us for help in the #developer channel in the [discord server](https://discord.gg/ob). +Anyone is free to create PR's with improvements and additions to Old School Bot. If you have questions, ask in the `#developers` channel on our Discord server. + +[Read this file for help setting up a bot](SETUP.md) #### **Shared Testing Server** @@ -90,6 +50,16 @@ You can also ask Magna to invite your Bot with your invite link above if you so - [[Andre](https://github.com/ard35)] - [[TastyPumPum](https://github.com/TastyPumPum)] -## Self Hosting +### Self Hosting Self hosting is not supported. + +## Notes + +### Profiling tests + +- node --cpu-prof --cpu-prof-dir=./profiling ./node_modules/vitest/vitest.mjs run --coverage --config vitest.unit.config.mts parseStringBank + +## Module graph + +- yarn build && npx madge --image graph2.svg ./dist/index.js diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 00000000000..ee08f7bdf07 --- /dev/null +++ b/SETUP.md @@ -0,0 +1,57 @@ +# Setting up for contributing/running the bot + +This assumes you are using VSCode as your IDE. If you have errors or issues, you can ask us for help in the #developer channel in the [discord server](https://discord.gg/ob). + +## **Setup** + +### Discord Bot Account + +1. Create a discord bot account, and have the application ID and bot token saved. +2. Ensure your bot has `Privileged Gateway Intents > Server Members Intent` enabled. + +### Environment + +1. Install [NvM](https://github.com/coreybutler/nvm-windows/), then use it to install NodeJS v20.15.0 OR install the nodejs version directly. +2. Install [Postgres 16](https://www.postgresql.org/download/) and PGAdmin4 for interacting with postgres (optional, but helpful) +3. Install Yarn using: `npm i -g yarn` +4. Clone the repo: `git clone https://github.com/oldschoolgg/oldschoolbot.git` +5. Run the following commands in the root of the repo: `corepack enable`, `yarn`, `npx prisma db push` and `npx prisma db push --schema ./prisma/robochimp.prisma` + +### Configuration + +1. Copy the ".env.example" file and rename the copy to ".env", put your bot token and bot id (client id) in it. +2. Create 2 databases called "osb_test" and "robochimp_test" +3. Change `DATABASE_URL` and `ROBOCHIMP_DATABASE_URL` in your .env with the format `postgresql://USER:PASSWORD@HOST:PORT/DATABASE_NAME`. +4. Make a config file from the example: `cp src/config.example.ts src/config.ts` +5. Edit this new `config.ts` file: + - Copy your Discord ID into both `OWNER_IDS` and `ADMIN_IDS`. + - Enter the Server ID where you want to Administer your bot from in `SupportServer` + - Enter the Server ID into `DEV_SERVER_ID` + +### Running the bot + +1. Run `yarn start` + +#### VSCode settings (Optional) + +1. In VSCode, press CTRL+SHIFT+P, search "Open User Settings JSON" +2. Add this to the file: + +```json + // Format/fix code automatically + "editor.formatOnSave": true, + "editor.formatOnPaste": true, + "editor.codeActionsOnSave": { + "quickfix.biome": "explicit", + }, + // Disable telemetry + "telemetry.telemetryLevel": "off", + // Always use \n for EOF + "files.eol": "\n", +``` + +# Troubleshooting + +- Check your NodeJS/NPM/Yarn/Postgres versions. +- Uninstall prettier/eslint vscode plugins. +- Delete these folders: node_modules, dist diff --git a/biome.json b/biome.json new file mode 100644 index 00000000000..5a5a13acdf2 --- /dev/null +++ b/biome.json @@ -0,0 +1,58 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json", + "organizeImports": { + "enabled": true + }, + "files": { + "maxSize": 10000000, + "ignore": ["node_modules", "dist", "coverage", "profiling", "logs", "icon_cache"], + "include": ["**/*.ts", "**/*.mts", "**/*.json", "**/*.test.ts"] + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "a11y": { + "useAltText": "off", + "useKeyWithClickEvents": "off" + }, + "suspicious": { + "noExplicitAny": "warn", + "noAssignInExpressions": "warn", + "noAsyncPromiseExecutor": "off" + }, + "style": { + "noNonNullAssertion": "off", + "noParameterAssign": "off", + "useExponentiationOperator": "off", + "noUselessElse": "off", + "useLiteralEnumMembers": "warn" + }, + "correctness": { + "useExhaustiveDependencies": "warn", + "noUnnecessaryContinue": "warn", + "noConstructorReturn": "off" + }, + "complexity": { + "noExtraBooleanCast": "warn", + "noBannedTypes": "warn", + "noForEach": "warn" + } + } + }, + "formatter": { + "enabled": true, + "formatWithErrors": true, + "indentStyle": "tab", + "indentWidth": 4, + "lineWidth": 120, + "lineEnding": "lf" + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "trailingCommas": "none", + "arrowParentheses": "asNeeded" + } + } +} diff --git a/docker-compose.yml b/docker-compose.yml index 925d2b42db7..730e09776c2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,30 +1,27 @@ -# Set the version of docker compose to use -version: "3.9" - -# The containers that compose the project services: - osb: - image: postgres:13-alpine - command: -c 'max_connections=1000' + db: + image: postgres:16-alpine + command: -c 'max_connections=5000' restart: always container_name: osb_database ports: - - "5435:1234" + - "5435:5435" environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres - POSTGRES_DB: osb_integration_test - PGPORT: 1234 + PGPORT: 5435 + volumes: + - postgres_data:/var/lib/postgresql/data - robochimp: - image: postgres:13-alpine - command: -c 'max_connections=1000' - restart: always - container_name: robochimp_database - ports: - - "5436:1235" + app: + build: + context: . + depends_on: + - db environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: robochimp_integration_test - PGPORT: 1235 + ROBOCHIMP_DATABASE_URL: postgresql://postgres:postgres@db:5435/robochimp_integration_test?connection_limit=10&pool_timeout=0&schema=public + DATABASE_URL: postgresql://postgres:postgres@db:5435/osb_integration_test?connection_limit=10&pool_timeout=0&schema=public + WAIT_HOSTS: db:5435 + +volumes: + postgres_data: diff --git a/dockerfile b/dockerfile new file mode 100644 index 00000000000..be430354e0f --- /dev/null +++ b/dockerfile @@ -0,0 +1,35 @@ +FROM node:20.15.0-alpine AS base +WORKDIR /usr/src/app +ENV CI=true +RUN apk add --no-cache dumb-init python3 g++ make git +RUN corepack enable + +COPY yarn.lock package.json .yarnrc.yml ./ + +ENTRYPOINT ["dumb-init", "--"] + +FROM base AS dependencies +WORKDIR /usr/src/app +RUN yarn remove zlib-sync && yarn install + +FROM base AS build-run +WORKDIR /usr/src/app +ENV NODE_ENV="development" +ENV NODE_OPTIONS="--enable-source-maps --max_old_space_size=4096" + +COPY --from=dependencies /usr/src/app/node_modules /usr/src/app/node_modules +COPY . . + +ENV CI=true +RUN cp .env.test .env +RUN cp src/config.example.ts src/config.ts + +ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.9.0/wait /wait +RUN chmod +x /wait + +CMD /wait && \ + yarn prisma db push --schema='./prisma/robochimp.prisma' && \ + yarn prisma db push --schema='./prisma/schema.prisma' && \ + yarn run build:esbuild && \ + yarn vitest run --config vitest.integration.config.mts && \ + exit 0 \ No newline at end of file diff --git a/package.json b/package.json index 6100e908957..a11ef3d90ef 100644 --- a/package.json +++ b/package.json @@ -1,98 +1,82 @@ { "scripts": { - "gen": "concurrently \"prisma generate\" \"prisma generate --schema prisma/robochimp.prisma\" && echo \"Generated Prisma Client\"", - "lint": "concurrently \"prettier --use-tabs ./**/*.{js,md,json,yml} --write\" \"eslint *.{ts,mts} \"{src,tests}/**/*.ts\" --fix\"", + "watch": "nodemon --delay 1ms -e ts -w src --exec 'yarn buildandrun'", + "build": "tsx ./src/scripts/build.ts", + "fix": "tsx ./src/scripts/troubleshooter.ts", + "start": "yarn build && node --enable-source-maps dist/", + "gen": "concurrently --raw \"prisma generate --no-hints\" \"prisma generate --no-hints --schema prisma/robochimp.prisma\" && echo \"Generated Prisma Client\"", + "prettify": "prettier --use-tabs \"./**/*.{md,yml}\" --write", + "lint": "concurrently --raw --kill-others-on-fail \"biome check --write --unsafe --diagnostic-level=error\" \"yarn prettify\" \"prisma format --schema ./prisma/robochimp.prisma\" \"prisma format --schema ./prisma/schema.prisma\"", "build:tsc": "tsc -p src", - "build": "concurrently \"yarn wipedist\" \"yarn gen\" && concurrently \"yarn prebuild:scripts\" && yarn build:tsc && echo \"Finished Building\"", - "wipedist": "rimraf \"dist/\"", - "start": "yarn build && concurrently \"tsc -w -p src\" \"node --enable-source-maps dist/\"", - "test": "concurrently \"tsc -p src\" \"yarn test:lint\" \"yarn test:unit\"", - "test:lint": "eslint --quiet *.{mts,ts} \"{src,tests}/**/*.ts\"", + "watch:tsc": "tsc -w -p src", + "wipedist": "node -e \"try { require('fs').rmSync('dist', { recursive: true }) } catch(_){}\"", + "dev": "concurrently --raw --kill-others-on-fail \"yarn\" \"yarn wipedist\" \"yarn lint\" && yarn build && yarn test", + "test": "concurrently --raw --kill-others-on-fail \"tsc -p src && yarn test:circular\" \"yarn test:lint\" \"yarn test:unit\" \"tsc -p tests/integration --noEmit\" \"tsc -p tests/unit --noEmit\"", + "test:lint": "biome check --diagnostic-level=error", "test:unit": "vitest run --coverage --config vitest.unit.config.mts", - "dev": "yarn wipedist && tsc -w -p src", + "test:docker": "docker compose up --build --abort-on-container-exit --remove-orphans && docker compose down --volumes --remove-orphans", "test:watch": "vitest --config vitest.unit.config.mts --coverage", - "test:integration": "tsx ./src/scripts/integration-tests.ts", - "prebuild:scripts": "tsx ./src/scripts/render-creatables-file.ts", - "build:esbuild": "esbuild --bundle src/index.ts --outdir=dist --platform=node --loader:.node=file --external:canvas", - "run:node": "node --enable-source-maps dist/index.js" + "buildandrun": "yarn build:esbuild && node --enable-source-maps dist", + "build:esbuild": "concurrently --raw \"yarn build:main\" \"yarn build:workers\"", + "build:main": "esbuild src/index.ts src/lib/workers/index.ts --sourcemap=inline --minify --legal-comments=none --outdir=./dist --log-level=error --bundle --platform=node --loader:.node=file --external:@napi-rs/canvas --external:@prisma/robochimp --external:@prisma/client --external:zlib-sync --external:bufferutil --external:oldschooljs --external:discord.js --external:node-fetch --external:piscina", + "build:workers": "esbuild src/lib/workers/kill.worker.ts src/lib/workers/finish.worker.ts src/lib/workers/casket.worker.ts --sourcemap=inline --log-level=error --bundle --minify --legal-comments=none --outdir=./dist/lib/workers --platform=node --loader:.node=file --external:@napi-rs/canvas --external:@prisma/robochimp --external:@prisma/client --external:zlib-sync --external:bufferutil --external:oldschooljs --external:discord.js --external:node-fetch --external:piscina", + "test:circular": "dpdm --exit-code circular:1 --progress=false --warning=false --tree=false ./dist/index.js", + "test:ci:unit": "concurrently --raw --kill-others-on-fail \"yarn test:unit\" \"yarn test:lint\" \"tsc -p tests/integration\" \"tsc -p tests/unit\" \"yarn test:circular\"" }, "dependencies": { - "@fastify/cors": "^8.2.0", - "@fastify/helmet": "^10.1.0", - "@fastify/rate-limit": "^8.0.0", - "@fastify/sensible": "^5.2.0", - "@napi-rs/canvas": "0.1.38", - "@octokit/graphql": "^4.8.0", - "@oldschoolgg/toolkit": "^0.0.24", - "@prisma/client": "^5.13.0", + "@napi-rs/canvas": "^0.1.53", + "@oldschoolgg/toolkit": "git+https://github.com/oldschoolgg/toolkit.git#cd7c6865229ca7dc4a66b3816586f2d3f4a4fbed", + "@prisma/client": "^5.17.0", + "@sapphire/ratelimits": "^2.4.9", "@sapphire/snowflake": "^3.5.3", - "@sapphire/stopwatch": "^1.4.0", "@sapphire/time-utilities": "^1.6.0", - "@sapphire/utilities": "^1.4.7", - "@sentry/node": "^7.113.0", + "@sapphire/timer-manager": "^1.0.2", + "@sentry/node": "^8.15.0", "ascii-table3": "^0.9.0", "bufferutil": "^4.0.8", - "chart.js": "^3.7.0", - "chartjs-node-canvas": "github:gc/ChartjsNodeCanvas#a598b6dd27c44351f235bca07ca4ee660121f289", - "chartjs-plugin-datalabels": "^2.0.0", - "deep-equal": "^2.2.3", - "deepmerge": "^4.3.1", - "discord.js": "^14.14.1", - "dotenv": "^16.0.3", - "e": "^0.2.33", - "fastify": "^4.14.1", - "fastify-raw-body": "^4.2.0", - "he": "^1.2.0", + "discord.js": "^14.15.3", + "dotenv": "^16.4.5", + "e": "0.2.33", + "exit-hook": "^4.0.0", + "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21", - "lru-cache": "^8.0.0", - "mahoji": "^0.0.7", - "mathjs": "^12.4.2", + "lru-cache": "^10.3.0", "murmurhash": "^2.0.1", "node-cron": "^3.0.3", "node-fetch": "^2.6.7", - "oldschooljs": "^2.5.4", + "oldschooljs": "^2.5.10", "p-queue": "^6.6.2", - "piscina": "^4.4.0", + "piscina": "^4.6.1", "random-js": "^2.1.0", + "remeda": "^2.7.0", "simple-statistics": "^7.8.3", "sonic-boom": "^4.0.1", - "tsx": "^4.9.0", "zlib-sync": "^0.1.9", - "zod": "^3.23.6" + "zod": "^3.23.8" }, "devDependencies": { - "@oldschoolgg/eslint-config": "^2.0.13", - "@oldschoolgg/ts-config": "^0.0.1", - "@types/deep-equal": "^1.0.3", - "@types/he": "^1.1.2", - "@types/jest-image-snapshot": "^6.1.0", + "@biomejs/biome": "^1.8.3", "@types/lodash": "^4.14.195", - "@types/madge": "^5.0.0", - "@types/node": "^14.18.12", + "@types/node": "^20.14.9", "@types/node-cron": "^3.0.7", "@types/node-fetch": "^2.6.1", - "@typescript-eslint/eslint-plugin": "^5.41.0", - "@typescript-eslint/parser": "^5.41.0", - "@vitest/coverage-v8": "^1.3.1", - "concurrently": "^7.6.0", - "dotenv-cli": "^7.3.0", - "esbuild": "^0.20.2", - "eslint": "^8.36.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-simple-import-sort": "^8.0.0", - "eslint-plugin-unicorn": "^44.0.2", - "fast-check": "^3.18.0", - "jest-image-snapshot": "^6.2.0", - "madge": "^7.0.0", - "prettier": "^2.7.1", - "prisma": "^5.13.0", - "rimraf": "^5.0.5", - "typescript": "5.0.2", - "vitest": "^1.6.0" + "@vitest/coverage-v8": "^2.0.3", + "concurrently": "^8.2.2", + "dpdm": "^3.14.0", + "esbuild": "0.21.5", + "fast-glob": "^3.3.2", + "nodemon": "^3.1.4", + "prettier": "^3.3.2", + "prisma": "^5.17.0", + "tsx": "^4.16.2", + "typescript": "^5.5.3", + "vitest": "^2.0.3" }, "engines": { - "node": ">=18.12.0" + "node": "20.15.0" + }, + "packageManager": "yarn@4.3.1", + "resolutions": { + "esbuild": "0.21.5" } } diff --git a/prisma/robochimp.prisma b/prisma/robochimp.prisma index e69c51ed5ae..25d6275b83d 100644 --- a/prisma/robochimp.prisma +++ b/prisma/robochimp.prisma @@ -1,93 +1,109 @@ generator client { - provider = "prisma-client-js" - previewFeatures = ["fullTextSearch"] - output = "../node_modules/@prisma/robochimp" + provider = "prisma-client-js" + previewFeatures = ["fullTextSearch"] + output = "../node_modules/@prisma/robochimp" } datasource db { - provider = "postgresql" - url = env("ROBOCHIMP_DATABASE_URL") + provider = "postgresql" + url = env("ROBOCHIMP_DATABASE_URL") } model TriviaQuestion { - id Int @id @unique @default(autoincrement()) - question String @db.VarChar() - answers String[] @db.VarChar() + id Int @id @unique @default(autoincrement()) + question String @db.VarChar() + answers String[] @db.VarChar() - @@map("trivia_question") + @@map("trivia_question") } enum BlacklistedEntityType { - guild - user + guild + user } model BlacklistedEntity { - id BigInt @id @unique - type BlacklistedEntityType - reason String? - date DateTime @default(now()) @db.Timestamp(6) + id BigInt @id @unique + type BlacklistedEntityType + reason String? + date DateTime @default(now()) @db.Timestamp(6) - @@map("blacklisted_entity") + @@map("blacklisted_entity") } model User { - id BigInt @id @unique - bits Int[] - github_id Int? - patreon_id String? - migrated_user_id BigInt? + id BigInt @id @unique + bits Int[] + github_id Int? + patreon_id String? - leagues_completed_tasks_ids Int[] - leagues_points_balance_osb Int @default(0) - leagues_points_balance_bso Int @default(0) - leagues_points_total Int @default(0) + migrated_user_id BigInt? - react_emoji_id String? + leagues_completed_tasks_ids Int[] + leagues_points_balance_osb Int @default(0) + leagues_points_balance_bso Int @default(0) + leagues_points_total Int @default(0) - osb_total_level Int? - bso_total_level Int? - osb_total_xp BigInt? - bso_total_xp BigInt? - osb_cl_percent Float? - bso_cl_percent Float? - osb_mastery Float? - bso_mastery Float? + react_emoji_id String? - tag Tag[] + osb_total_level Int? + bso_total_level Int? + osb_total_xp BigInt? + bso_total_xp BigInt? + osb_cl_percent Float? + bso_cl_percent Float? + osb_mastery Float? + bso_mastery Float? - store_bitfield Int[] + store_bitfield Int[] - testing_points Float @default(0) - testing_points_balance Float @default(0) + testing_points Float @default(0) + testing_points_balance Float @default(0) - @@map("user") + perk_tier Int @default(0) + premium_balance_tier Int? + premium_balance_expiry_date BigInt? + + user_group_id String? @db.Uuid + userGroup UserGroup? @relation(fields: [user_group_id], references: [id]) + + tag Tag[] + + @@map("user") } model PingableRole { - role_id String @id - name String @unique @db.VarChar(32) + role_id String @id + name String @unique @db.VarChar(32) - @@map("pingable_role") + @@map("pingable_role") } model Tag { - id Int @id @unique @default(autoincrement()) - name String @unique @db.VarChar(32) - content String @db.VarChar(2000) + id Int @id @unique @default(autoincrement()) + name String @unique @db.VarChar(32) + content String @db.VarChar(2000) - user_id BigInt - creator User @relation(fields: [user_id], references: [id]) + user_id BigInt + creator User @relation(fields: [user_id], references: [id]) - @@map("tag") + @@map("tag") } model StoreCode { - product_id Int - code String @id @unique + product_id Int + code String @id @unique + + redeemed_at DateTime? + redeemed_by_user_id String? @db.VarChar(19) + + @@map("store_code") +} + +model UserGroup { + id String @id @default(uuid()) @db.Uuid - redeemed_at DateTime? - redeemed_by_user_id String? @db.VarChar(19) + users User[] - @@map("store_code") + @@map("user_group") } diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 6f946311f67..980daebb64f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -18,7 +18,7 @@ model Activity { group_activity Boolean type activity_type_enum channel_id BigInt - data Json @db.Json + data Json @db.JsonB pinnedTrip PinnedTrip[] @@index([user_id, finish_date]) @@ -41,6 +41,8 @@ model Analytic { ironMinionsCount Int? totalSacrificed BigInt? totalGP BigInt? + totalGeGp BigInt? + totalBigAlchGp BigInt? dicingBank BigInt? duelTaxBank BigInt? dailiesAmount BigInt? @@ -61,9 +63,6 @@ model Analytic { model ClientStorage { id String @id @db.VarChar(19) - userBlacklist String[] @db.VarChar(19) - guildBlacklist String[] @db.VarChar(19) - commandStats Json @default("{}") @db.Json totalCommandsUsed Int @default(0) prices Json @default("{}") @db.Json sold_items_bank Json @default("{}") @db.Json @@ -124,7 +123,7 @@ model ClientStorage { degraded_items_cost Json @default("{}") @db.Json tks_cost Json @default("{}") @db.Json tks_loot Json @default("{}") @db.Json - disabled_commands String[] @db.VarChar(32) + disabled_commands String[] @default([]) @db.VarChar(32) gp_tax_balance BigInt @default(0) gotr_cost Json @default("{}") @db.Json gotr_loot Json @default("{}") @db.Json @@ -133,6 +132,8 @@ model ClientStorage { nmz_cost Json @default("{}") @db.Json toa_cost Json @default("{}") @db.Json toa_loot Json @default("{}") @db.Json + colo_cost Json @default("{}") @db.Json + colo_loot Json @default("{}") @db.Json grand_exchange_is_locked Boolean @default(false) grand_exchange_total_tax BigInt @default(0) @@ -255,7 +256,7 @@ model Giveaway { message_id String @db.VarChar(19) reaction_id String? @db.VarChar(19) - users_entered String[] + users_entered String[] @default([]) @@index([completed, finish_date]) @@map("giveaway") @@ -263,11 +264,9 @@ model Giveaway { model Guild { id String @id @db.VarChar(19) - disabledCommands String[] - jmodComments String? @db.VarChar(19) + disabledCommands String[] @default([]) petchannel String? @db.VarChar(19) - tweetchannel String? @db.VarChar(19) - staffOnlyChannels String[] @db.VarChar(19) + staffOnlyChannels String[] @default([]) @db.VarChar(19) // BSO mega_duck_location Json @default("{\"x\":1356,\"y\":209,\"usersParticipated\":{}}") @db.Json @@ -361,19 +360,17 @@ model User { bank Json @default("{}") @db.Json collectionLogBank Json @default("{}") @db.JsonB blowpipe Json @default("{\"scales\":0,\"dartID\":null,\"dartQuantity\":0}") @db.Json - ironman_alts String[] - main_account String? - slayer_unlocks Int[] @map("slayer.unlocks") - slayer_blocked_ids Int[] @map("slayer.blocked_ids") + slayer_unlocks Int[] @default([]) @map("slayer.unlocks") + slayer_blocked_ids Int[] @default([]) @map("slayer.blocked_ids") slayer_last_task Int @default(0) @map("slayer.last_task") - badges Int[] - bitfield Int[] + badges Int[] @default([]) + bitfield Int[] @default([]) temp_cl Json @default("{}") @db.Json last_temp_cl_reset DateTime? @db.Timestamp(6) minion_equippedPet Int? @map("minion.equippedPet") minion_farmingContract Json? @map("minion.farmingContract") @db.Json minion_birdhouseTraps Json? @map("minion.birdhouseTraps") @db.Json - finished_quest_ids Int[] + finished_quest_ids Int[] @default([]) // Relations farmedCrops FarmedCrop[] @@ -384,19 +381,19 @@ model User { // Configs/Settings minion_defaultCompostToUse CropUpgradeType @default(compost) @map("minion.defaultCompostToUse") auto_farm_filter AutoFarmFilterEnum @default(AllFarm) - favoriteItems Int[] - favorite_alchables Int[] - favorite_food Int[] - favorite_bh_seeds Int[] + favoriteItems Int[] @default([]) + favorite_alchables Int[] @default([]) + favorite_food Int[] @default([]) + favorite_bh_seeds Int[] @default([]) minion_defaultPay Boolean @default(false) @map("minion.defaultPay") minion_icon String? @map("minion.icon") minion_name String? @map("minion.name") bank_bg_hex String? bankBackground Int @default(1) - attack_style String[] - combat_options Int[] + attack_style String[] @default([]) + combat_options Int[] @default([]) slayer_remember_master String? @map("slayer.remember_master") - slayer_autoslay_options Int[] @map("slayer.autoslay_options") + slayer_autoslay_options Int[] @default([]) @map("slayer.autoslay_options") bank_sort_method String? @db.VarChar(16) bank_sort_weightings Json @default("{}") @db.Json gambling_lockout_expiry DateTime? @@ -459,12 +456,12 @@ model User { zeal_tokens Int @default(0) slayer_points Int @default(0) @map("slayer.points") - completed_ca_task_ids Int[] + completed_ca_task_ids Int[] @default([]) // BSO nursery Json? @db.Json selected_tame Int? - monkeys_fought String[] + monkeys_fought String[] @default([]) emerged_inferno_attempts Int @default(0) skills_dungeoneering BigInt @default(0) @map("skills.dungeoneering") ourania_tokens Int @default(0) @@ -481,7 +478,7 @@ model User { last_patron_double_time_trigger DateTime? @db.Timestamp(6) lottery_input Json @default("{}") @db.Json gear_template Int @default(0) @db.SmallInt - unlocked_gear_templates Int[] + unlocked_gear_templates Int[] @default([]) last_bonanza_date DateTime? @db.Timestamp(6) painted_items_tuple Json? @db.Json @@ -490,14 +487,13 @@ model User { materials_owned Json @default("{}") @db.Json disassembled_items_bank Json @default("{}") @db.Json researched_materials_bank Json @default("{}") @db.Json - unlocked_blueprints Int[] - disabled_inventions Int[] + unlocked_blueprints Int[] @default([]) + disabled_inventions Int[] @default([]) bso_mystery_trail_current_step_id Int? store_bitfield Int[] - // Migrate farmingPatches_herb Json? @map("farmingPatches.herb") @db.Json farmingPatches_fruit_tree Json? @map("farmingPatches.fruit tree") @db.Json farmingPatches_tree Json? @map("farmingPatches.tree") @db.Json @@ -520,6 +516,8 @@ model User { cached_networth_value BigInt? + username String? @db.VarChar(32) + geListings GEListing[] bingo_participant BingoParticipant[] bingo Bingo[] @@ -536,6 +534,8 @@ model User { grinchions_caught Int @default(0) last_giveaway_ticket_given_date DateTime? @db.Timestamp(6) + cl_array Int[] @default([]) + @@index([id, last_command_date]) @@map("users") } @@ -686,6 +686,7 @@ model Minigame { nmz Int @default(0) shades_of_morton Int @default(0) tombs_of_amascut Int @default(0) + colosseum Int @default(0) // BSO ourania_delivery_service Int @default(0) @@ -779,14 +780,12 @@ model Tame { } model CommandUsage { - id Int @id @default(autoincrement()) - date DateTime @default(now()) @db.Timestamp(6) + id Int @id @default(autoincrement()) + date DateTime @default(now()) @db.Timestamp(6) user_id BigInt - command_name String @db.VarChar(32) - status command_usage_status @default(value: Unknown) - is_continue Boolean @default(false) - flags Json? - inhibited Boolean? @default(false) + command_name command_name_enum + is_continue Boolean @default(false) + inhibited Boolean? @default(false) is_mention_command Boolean @default(false) @@ -876,7 +875,7 @@ model EconomyTransaction { model StashUnit { stash_id Int user_id BigInt - items_contained Int[] + items_contained Int[] @default([]) has_built Boolean @@unique([stash_id, user_id]) @@ -904,7 +903,7 @@ model UserStats { farming_plant_cost_bank Json @default("{}") @db.Json farming_harvest_loot_bank Json @default("{}") @db.Json - cl_array Int[] + cl_array Int[] @default([]) cl_array_length Int @default(0) // BSO @@ -984,17 +983,18 @@ model UserStats { high_gambles Int @default(0) honour_points Int @default(0) - slayer_task_streak Int @default(0) - slayer_superior_count Int @default(0) - slayer_unsired_offered Int @default(0) - slayer_chewed_offered Int @default(0) + slayer_task_streak Int @default(0) + slayer_wildy_task_streak Int @default(0) + slayer_superior_count Int @default(0) + slayer_unsired_offered Int @default(0) + slayer_chewed_offered Int @default(0) tob_cost Json @default("{}") tob_loot Json @default("{}") creature_scores Json @default("{}") monster_scores Json @default("{}") laps_scores Json @default("{}") - sacrificed_bank Json @default("{}") + sacrificed_bank Json @default("{}") @db.JsonB openable_scores Json @default("{}") gp_luckypick BigInt @default(0) @@ -1043,6 +1043,13 @@ model UserStats { turaels_trials_cost_bank Json @default("{}") @db.Json turaels_trials_loot_bank Json @default("{}") @db.Json + colo_cost Json @default("{}") @db.Json + colo_loot Json @default("{}") @db.Json + colo_kc_bank Json @default("{}") @db.Json + colo_max_glory Int? + + quivers_sacrificed Int @default(0) + @@map("user_stats") } @@ -1126,13 +1133,6 @@ model HistoricalData { @@map("historical_data") } -enum command_usage_status { - Unknown - Success - Error - Inhibited -} - enum activity_type_enum { Agility Cooking @@ -1245,6 +1245,7 @@ enum activity_type_enum { DepthsOfAtlantis StrongholdOfSecurity BirthdayCollectIngredients + CombatRing SpecificQuest Mortimer CamdozaalFishing @@ -1253,6 +1254,7 @@ enum activity_type_enum { MemoryHarvest GuthixianCache TuraelsTrials + Colosseum } enum xp_gains_skill_enum { @@ -1362,14 +1364,14 @@ model Bingo { is_global Boolean @default(false) - organizers String[] + organizers String[] @default([]) start_date DateTime @default(now()) @db.Timestamp(6) duration_days Int team_size Int title String notifications_channel_id String @db.VarChar(19) ticket_price BigInt - bingo_tiles Json[] + bingo_tiles Json[] @default([]) was_finalized Boolean @default(false) guild_id String @db.VarChar(19) @@ -1479,3 +1481,310 @@ model UserEvent { @@map("user_event") } + +enum command_name_enum { + testpotato + achievementdiary + activities + admin + aerialfish + agilityarena + alch + amrod + ash + ask + autoequip + autoslay + bal + bank + bankbg + barbassault + bgcolor + bingo + birdhouse + blastfurnace + blowpipe + bossrecords + botleagues + botstats + bs + bso + build + bury + buy + ca + cancel + capegamble + cash + casket + cast + castlewars + cd + championchallenge + channel + chargeglories + chargewealth + checkmasses + checkpatch + chompyhunt + choose + chop + christmas + cl + claim + clbank + clue + clues + cmd + collect + collectionlog + combat + combatoptions + compostbin + config + cook + cox + cracker + craft + create + daily + darkaltar + data + decant + defaultfarming + defender + diary + dice + dicebank + disable + dmm + driftnet + drop + drycalc + drystreak + duel + easter + economybank + emotes + enable + enchant + equip + eval + fake + fakearma + fakebandos + fakeely + fakepm + fakesara + fakescythe + fakezammy + faq + farm + farming + farmingcontract + favalch + favfood + favorite + favour + fightcaves + finish + fish + fishingtrawler + fletch + gamble + gauntlet + ge + gear + gearpresets + gearstats + gift + github + giveaway + gnomerestaurant + gp + groupkill + halloween + hans + harvest + hcim + hcimdeaths + help + hiscores + hunt + inbank + inferno + info + invite + ironman + is + itemtrivia + jmodcomments + jmodtweets + k + kc + kcgains + kill + lamp + lapcount + laps + lastmanstanding + lb + leaderboard + leagues + light + lms + loot + love + luckyimp + luckypick + lvl + m + magearena + magearena2 + mahoganyhomes + mass + mclue + mine + minigames + minion + minionstats + mix + monster + mostdrops + mta + mygiveaways + mypets + news + nightmare + offer + open + osrskc + patreon + pay + pestcontrol + pet + petmessages + petrate + petroll + pickpocket + ping + players + plunder + poh + poll + polls + prefix + price + pvp + quest + raid + randomevents + randquote + ranks + rc + redeem + reload + resetrng + revs + roguesden + roles + roll + rp + runecraft + runelite + s + sacrifice + sacrificedbank + sacrificegp + sacrificelog + sawmill + seedpack + sell + sellto + sendtoabutton + sepulchre + server + setrsn + shutdownlock + simulate + skillcape + slayer + slayershop + slayertask + smelt + smith + soulwars + stats + steal + streamertweets + support + tag + tearsofguthix + tempoross + tithefarm + tithefarmshop + tob + tokkulshop + tools + trade + train + trek + trekshop + trickortreat + trivia + tweets + uim + unequip + unequipall + use + user + virtualstats + volcanicmine + warriorsguild + wiki + wintertodt + world + wt + wyson + xp + xpgains + xpto99 + zalcano + completion + tames + birthday + bsominigames + dg + divination + droprate + dung + fishingcontest + givebox + giverandomitem + hammy + ic + igne + invention + itemcontract + kibble + kinggoldemar + kk + lottery + lotterybank + megaduck + mmmr + nex + nursery + ods + paint + pingmass + rates + slots + smokeylottery + sotw + spawn + spawnbox + spawnlamp + testershop + vasa +} diff --git a/src/config.example.ts b/src/config.example.ts index e8a3d15bce6..7c3d2c498fc 100644 --- a/src/config.example.ts +++ b/src/config.example.ts @@ -1,19 +1,9 @@ -import { IDiscordSettings } from './lib/types'; +import type { IDiscordSettings } from './lib/types'; -export const botToken = ''; export const production = false; - export const SENTRY_DSN: string | null = null; -export const CLIENT_SECRET = ''; -export const DEV_SERVER_ID = ''; -export const GITHUB_TOKEN = ''; -export const DISCORD_SETTINGS: Partial = { - // Your bot unique ID goes here - BotID: '729244028989603850' -}; -// Add or replace these with your Discord ID: +export const DISCORD_SETTINGS: Partial = {}; export const OWNER_IDS = ['157797566833098752']; export const ADMIN_IDS = ['425134194436341760']; export const MAXING_MESSAGE = 'Congratulations on maxing!'; -// Discord server where admin commands will be allowed: export const SupportServer = '940758552425955348'; diff --git a/src/index.ts b/src/index.ts index 9d010d255a0..176aee36680 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,53 +1,44 @@ -import './lib/customItems/customItems'; -import './lib/data/itemAliases'; -import './lib/crons'; +import './lib/safeglobals'; +import './lib/globals'; import './lib/MUser'; import './lib/util/transactItemsFromBank'; -import './lib/data/trophies'; -import './lib/itemMods'; import './lib/geImage'; -import * as Sentry from '@sentry/node'; -import { Chart } from 'chart.js'; -import ChartDataLabels from 'chartjs-plugin-datalabels'; -import { GatewayIntentBits, Options, Partials, TextChannel } from 'discord.js'; +import { MahojiClient } from '@oldschoolgg/toolkit'; +import { init } from '@sentry/node'; +import type { TextChannel } from 'discord.js'; +import { GatewayIntentBits, Options, Partials } from 'discord.js'; import { isObject } from 'e'; -import { MahojiClient } from 'mahoji'; -import { join } from 'path'; -import { botToken, DEV_SERVER_ID, SENTRY_DSN, SupportServer } from './config'; +import { SENTRY_DSN, SupportServer } from './config'; import { BLACKLISTED_GUILDS, BLACKLISTED_USERS } from './lib/blacklists'; -import { Channel, Events, globalConfig, META_CONSTANTS } from './lib/constants'; +import { Channel, Events, gitHash, globalConfig } from './lib/constants'; import { economyLog } from './lib/economyLogs'; import { onMessage } from './lib/events'; -import { makeServer } from './lib/http'; import { modalInteractionHook } from './lib/modals'; -import { runStartupScripts } from './lib/startupScripts'; +import { preStartup } from './lib/preStartup'; import { OldSchoolBotClient } from './lib/structures/OldSchoolBotClient'; -import { syncActivityCache } from './lib/Task'; -import { assert, runTimedLoggedFn } from './lib/util'; -import { CACHED_ACTIVE_USER_IDS, syncActiveUserIDs } from './lib/util/cachedUserIDs'; +import { CACHED_ACTIVE_USER_IDS } from './lib/util/cachedUserIDs'; import { interactionHook } from './lib/util/globalInteractions'; -import { handleInteractionError } from './lib/util/interactionReply'; +import { handleInteractionError, interactionReply } from './lib/util/interactionReply'; import { logError } from './lib/util/logError'; -import { sonicBoom } from './lib/util/logger'; +import { allCommands } from './mahoji/commands/allCommands'; import { onStartup } from './mahoji/lib/events'; +import { exitCleanup } from './mahoji/lib/exitHandler'; import { postCommand } from './mahoji/lib/postCommand'; import { preCommand } from './mahoji/lib/preCommand'; import { convertMahojiCommandToAbstractCommand } from './mahoji/lib/util'; -debugLog(`Starting... Git Hash ${META_CONSTANTS.GIT_HASH}`); - -Chart.register(ChartDataLabels); - if (SENTRY_DSN) { - Sentry.init({ - dsn: SENTRY_DSN + init({ + dsn: SENTRY_DSN, + enableTracing: false, + defaultIntegrations: false, + integrations: [], + release: gitHash }); } -assert(process.env.TZ === 'UTC'); - const client = new OldSchoolBotClient({ shards: 'auto', intents: [ @@ -74,7 +65,10 @@ const client = new OldSchoolBotClient({ maxSize: 200, keepOverLimit: member => CACHED_ACTIVE_USER_IDS.has(member.user.id) }, - GuildEmojiManager: { maxSize: 1, keepOverLimit: i => [DEV_SERVER_ID, SupportServer].includes(i.guild.id) }, + GuildEmojiManager: { + maxSize: 1, + keepOverLimit: i => [globalConfig.testingServerID, SupportServer].includes(i.guild.id) + }, GuildStickerManager: { maxSize: 0 }, PresenceManager: { maxSize: 0 }, VoiceStateManager: { maxSize: 0 }, @@ -95,9 +89,9 @@ const client = new OldSchoolBotClient({ }); export const mahojiClient = new MahojiClient({ - developmentServerID: DEV_SERVER_ID, + developmentServerID: globalConfig.testingServerID, applicationID: globalConfig.clientID, - storeDirs: [join('dist', 'mahoji')], + commands: allCommands, handlers: { preCommand: async ({ command, interaction, options }) => { const result = await preCommand({ @@ -128,14 +122,7 @@ export const mahojiClient = new MahojiClient({ }); declare global { - const globalClient: OldSchoolBotClient; -} -declare global { - namespace NodeJS { - interface Global { - globalClient: OldSchoolBotClient; - } - } + var globalClient: OldSchoolBotClient; } client.mahojiClient = mahojiClient; @@ -143,15 +130,26 @@ global.globalClient = client; client.on('messageCreate', msg => { onMessage(msg); }); +client.on('error', console.error); client.on('interactionCreate', async interaction => { - if (BLACKLISTED_USERS.has(interaction.user.id)) return; - if (interaction.guildId && BLACKLISTED_GUILDS.has(interaction.guildId)) return; - - if (!client.isReady()) { - if (interaction.isChatInputCommand()) { - await interaction.reply({ + if (globalClient.isShuttingDown) { + if (interaction.isRepliable()) { + await interactionReply(interaction, { content: - 'BSO is currently down for maintenance/updates, please try again in a couple minutes! Thank you <3', + 'BSO is currently shutting down for maintenance/updates, please try again in a couple minutes! Thank you <3', + ephemeral: true + }); + } + return; + } + + if ( + BLACKLISTED_USERS.has(interaction.user.id) || + (interaction.guildId && BLACKLISTED_GUILDS.has(interaction.guildId)) + ) { + if (interaction.isRepliable()) { + await interactionReply(interaction, { + content: 'You are blacklisted.', ephemeral: true }); } @@ -177,7 +175,9 @@ client.on('interactionCreate', async interaction => { client.on(Events.ServerNotification, (message: string) => { const channel = globalClient.channels.cache.get(Channel.Notifications); - if (channel) (channel as TextChannel).send(message); + if (channel) { + (channel as TextChannel).send({ content: message, allowedMentions: { parse: [], users: [], roles: [] } }); + } }); client.on(Events.EconomyLog, async (message: string) => { @@ -190,23 +190,21 @@ client.on('guildCreate', guild => { } }); -client.on('shardDisconnect', ({ wasClean, code, reason }) => debugLog('Shard Disconnect', { wasClean, code, reason })); client.on('shardError', err => debugLog('Shard Error', { error: err.message })); -client.once('ready', () => runTimedLoggedFn('OnStartup', async () => onStartup())); +client.once('ready', () => onStartup()); async function main() { - if (process.env.TEST) return; - client.fastifyServer = await makeServer(); - await Promise.all([ - runTimedLoggedFn('Sync Active User IDs', syncActiveUserIDs), - runTimedLoggedFn('Sync Activity Cache', syncActivityCache) - ]); await Promise.all([ - runTimedLoggedFn('Start Mahoji Client', async () => mahojiClient.start()), - runTimedLoggedFn('Startup Scripts', runStartupScripts) + preStartup(), + import('exit-hook').then(({ asyncExitHook }) => + asyncExitHook(exitCleanup, { + wait: 2000 + }) + ) ]); - - await runTimedLoggedFn('Log In', () => client.login(botToken)); + if (process.env.TEST) return; + await client.login(globalConfig.botToken); + console.log(`Logged in as ${globalClient.user.username}`); } process.on('uncaughtException', err => { @@ -219,9 +217,4 @@ process.on('unhandledRejection', err => { logError(err); }); -process.on('exit', exitCode => { - sonicBoom.flushSync(); - debugLog('Process Exit', { type: 'PROCESS_EXIT', exitCode }); -}); - main(); diff --git a/src/lib/DynamicButtons.ts b/src/lib/DynamicButtons.ts index ddf8d714e6c..5d892ba99ec 100644 --- a/src/lib/DynamicButtons.ts +++ b/src/lib/DynamicButtons.ts @@ -1,7 +1,6 @@ -import { +import type { BaseMessageOptions, - ButtonBuilder, - ButtonStyle, + ButtonInteraction, DMChannel, Message, MessageComponentInteraction, @@ -9,11 +8,13 @@ import { TextChannel, ThreadChannel } from 'discord.js'; -import { noOp, Time } from 'e'; +import { ButtonBuilder, ButtonStyle } from 'discord.js'; +import { Time, isFunction, noOp } from 'e'; import murmurhash from 'murmurhash'; import { BLACKLISTED_USERS } from './blacklists'; import { awaitMessageComponentInteraction, makeComponents } from './util'; +import { silentButtonAck } from './util/handleMahojiConfirmation'; import { minionIsBusy } from './util/minionIsBusy'; type DynamicButtonFn = (opts: { message: Message; interaction: MessageComponentInteraction }) => unknown; @@ -22,7 +23,7 @@ export class DynamicButtons { buttons: { name: string; id: string; - fn: DynamicButtonFn; + fn?: DynamicButtonFn; emoji: string | undefined; cantBeBusy: boolean; style?: ButtonStyle; @@ -84,9 +85,11 @@ export class DynamicButtons { ...messageOptions, components: makeComponents(buttons) }); - const collectedInteraction = await awaitMessageComponentInteraction({ + const collectedInteraction: ButtonInteraction = (await awaitMessageComponentInteraction({ message: this.message, - filter: i => { + filter: async i => { + if (!i.isButton()) return false; + await silentButtonAck(i); if (BLACKLISTED_USERS.has(i.user.id)) return false; if (this.usersWhoCanInteract.includes(i.user.id)) { return true; @@ -95,7 +98,7 @@ export class DynamicButtons { return false; }, time: this.timer ?? Time.Second * 20 - }).catch(noOp); + }).catch(noOp)) as ButtonInteraction; if (this.deleteAfterConfirm === true) { await this.message.delete().catch(noOp); } else { @@ -105,20 +108,22 @@ export class DynamicButtons { if (collectedInteraction) { for (const button of this.buttons) { if (collectedInteraction.customId === button.id) { - collectedInteraction.deferUpdate(); if (minionIsBusy(collectedInteraction.user.id) && button.cantBeBusy) { - return collectedInteraction.reply({ + await collectedInteraction.reply({ content: "Your action couldn't be performed, because your minion is busy.", ephemeral: true }); + return null; } - await button.fn({ message: this.message!, interaction: collectedInteraction }); - return collectedInteraction; + if ('fn' in button && isFunction(button.fn)) { + await button.fn({ message: this.message!, interaction: collectedInteraction }); + } + return button; } } } - return collectedInteraction; + return null; } add({ @@ -129,7 +134,7 @@ export class DynamicButtons { style }: { name: string; - fn: DynamicButtonFn; + fn?: DynamicButtonFn; emoji?: string; cantBeBusy?: boolean; style?: ButtonStyle; @@ -137,7 +142,7 @@ export class DynamicButtons { const id = murmurhash(name).toString(); this.buttons.push({ name, - id, + id: `DYN_${id}`, fn, emoji, cantBeBusy: cantBeBusy ?? false, diff --git a/src/lib/InteractionID.ts b/src/lib/InteractionID.ts new file mode 100644 index 00000000000..cff0e337dec --- /dev/null +++ b/src/lib/InteractionID.ts @@ -0,0 +1,17 @@ +export const InteractionID = { + PaginatedMessage: { + FirstPage: 'PM_FIRST_PAGE', + PreviousPage: 'PM_PREVIOUS_PAGE', + NextPage: 'PM_NEXT_PAGE', + LastPage: 'PM_LAST_PAGE' + }, + Slayer: { + AutoSlaySaved: 'SLAYER_AUTO_SLAY_SAVED', + AutoSlayDefault: 'SLAYER_AUTO_SLAY_DEFAULT', + AutoSlayEHP: 'SLAYER_AUTO_SLAY_EHP', + AutoSlayBoss: 'SLAYER_AUTO_SLAY_BOSS', + SkipTask: 'SLAYER_SKIP_TASK', + CancelTask: 'SLAYER_CANCEL_TASK', + BlockTask: 'SLAYER_BLOCK_TASK' + } +} as const; diff --git a/src/lib/MUser.ts b/src/lib/MUser.ts index dec37c2ed22..2c46ad8066d 100644 --- a/src/lib/MUser.ts +++ b/src/lib/MUser.ts @@ -1,65 +1,68 @@ -import { mentionCommand } from '@oldschoolgg/toolkit'; -import { UserError } from '@oldschoolgg/toolkit/dist/lib/UserError'; -import { Prisma, TameActivity, User, UserStats, xp_gains_skill_enum } from '@prisma/client'; +import { PerkTier, cleanUsername, mentionCommand, seedShuffle } from '@oldschoolgg/toolkit'; +import { UserError } from '@oldschoolgg/toolkit'; +import type { GearSetupType, Prisma, TameActivity, User, UserStats, xp_gains_skill_enum } from '@prisma/client'; import { userMention } from 'discord.js'; -import { calcWhatPercent, objectEntries, percentChance, randArrItem, sumArr, Time, uniqueArr } from 'e'; +import { Time, calcWhatPercent, objectEntries, percentChance, randArrItem, sumArr, uniqueArr } from 'e'; import { Bank } from 'oldschooljs'; -import { EquipmentSlot, Item } from 'oldschooljs/dist/meta/types'; +import type { Item, ItemBank } from 'oldschooljs/dist/meta/types'; +import { EquipmentSlot } from 'oldschooljs/dist/meta/types'; +import { resolveItems } from 'oldschooljs/dist/util/util'; import { timePerAlch } from '../mahoji/lib/abstracted_commands/alchCommand'; import { getParsedStashUnits } from '../mahoji/lib/abstracted_commands/stashUnitsCommand'; -import { userStatsUpdate } from '../mahoji/mahojiSettings'; +import { fetchUserStats, userStatsUpdate } from '../mahoji/mahojiSettings'; import { addXP } from './addXP'; -import { GodFavourBank, GodName } from './bso/divineDominion'; +import type { GodFavourBank, GodName } from './bso/divineDominion'; import { userIsBusy } from './busyCounterCache'; import { ClueTiers } from './clues/clueTiers'; -import { CATier, CombatAchievements } from './combat_achievements/combatAchievements'; -import { badges, BitField, Emoji, PerkTier, projectiles, usernameCache } from './constants'; +import { type CATier, CombatAchievements } from './combat_achievements/combatAchievements'; +import { BitField, projectiles } from './constants'; import { bossCLItems } from './data/Collections'; import { allPetIDs } from './data/CollectionsExport'; import { getSimilarItems } from './data/similarItems'; import { degradeableItems } from './degradeableItems'; +import { type GearSetup, type UserFullGearSetup, defaultGear } from './gear'; import { gearImages } from './gear/functions/generateGearImage'; -import { GearSetup, GearSetupType, UserFullGearSetup } from './gear/types'; import { handleNewCLItems } from './handleNewCLItems'; -import { IMaterialBank } from './invention'; +import type { IMaterialBank } from './invention'; import { MaterialBank } from './invention/MaterialBank'; import { marketPriceOfBank } from './marketPrices'; import backgroundImages from './minions/data/bankBackgrounds'; -import { CombatOptionsEnum } from './minions/data/combatConstants'; +import type { CombatOptionsEnum } from './minions/data/combatConstants'; import { defaultFarmingContract } from './minions/farming'; -import { FarmingContract } from './minions/farming/types'; -import { AttackStyles } from './minions/functions'; +import type { FarmingContract } from './minions/farming/types'; +import type { AttackStyles } from './minions/functions'; import { blowpipeDarts, validateBlowpipeData } from './minions/functions/blowpipeCommand'; -import { AddXpParams, BlowpipeData, ClueBank } from './minions/types'; +import type { AddXpParams, BlowpipeData, ClueBank } from './minions/types'; import { mysteriousStepData, mysteriousTrailTracks } from './mysteryTrail'; -import { getUsersPerkTier, syncPerkTierOfUser } from './perkTiers'; +import { getUsersPerkTier } from './perkTiers'; import { roboChimpUserFetch } from './roboChimp'; -import { getMinigameEntity, Minigames, MinigameScore } from './settings/minigames'; -import { prisma } from './settings/prisma'; +import type { MinigameScore } from './settings/minigames'; +import { Minigames, getMinigameEntity } from './settings/minigames'; import { getFarmingInfoFromUser } from './skilling/functions/getFarmingInfo'; import Farming from './skilling/skills/farming'; import { SkillsEnum } from './skilling/types'; -import { BankSortMethod } from './sorts'; -import { ChargeBank, XPBank } from './structures/Banks'; -import { defaultGear, Gear } from './structures/Gear'; +import type { BankSortMethod } from './sorts'; +import type { ChargeBank, XPBank } from './structures/Banks'; +import { Gear } from './structures/Gear'; import { MTame } from './structures/MTame'; -import { ItemBank, Skills } from './types'; -import { addItemToBank, convertXPtoLVL, getAllIDsOfUser, itemNameFromID, murMurSort } from './util'; +import type { Skills } from './types'; +import { addItemToBank, convertXPtoLVL, itemNameFromID } from './util'; import { determineRunes } from './util/determineRunes'; +import { findGroupOfUser } from './util/findGroupOfUser'; import { getKCByName } from './util/getKCByName'; import getOSItem, { getItem } from './util/getOSItem'; import itemID from './util/itemID'; import { logError } from './util/logError'; +import { makeBadgeString } from './util/makeBadgeString'; import { minionIsBusy } from './util/minionIsBusy'; import { minionName } from './util/minionUtils'; import { repairBrokenItemsFromUser } from './util/repairBrokenItems'; -import resolveItems from './util/resolveItems'; -import { TransactItemsArgs } from './util/transactItemsFromBank'; +import type { TransactItemsArgs } from './util/transactItemsFromBank'; export async function mahojiUserSettingsUpdate(user: string | bigint, data: Prisma.UserUncheckedUpdateInput) { try { - const newUser = await prisma.user.update({ + const newUser = await global.prisma.user.update({ data, where: { id: user.toString() @@ -102,13 +105,13 @@ export class MUserClass { skillsAsXP!: Required; skillsAsLevels!: Required; paintedItems!: Map; + badgesString!: string; + bitfield!: readonly BitField[]; constructor(user: User) { this.user = user; this.id = user.id; this.updateProperties(); - - syncPerkTierOfUser(this); } private updateProperties() { @@ -140,13 +143,16 @@ export class MUserClass { this.skillsAsLevels = this.getSkills(true); this.paintedItems = this.buildPaintedItems(); + this.badgesString = makeBadgeString(this.user.badges, this.isIronman); + + this.bitfield = this.user.bitfield as readonly BitField[]; } get gearTemplate() { return gearImages.find(i => i.id === this.user.gear_template)!; } - countSkillsAtleast99() { + countSkillsAtLeast99() { return Object.values(this.skillsAsLevels).filter(lvl => lvl >= 99).length; } @@ -206,12 +212,8 @@ export class MUserClass { return Number(this.user.GP); } - get bitfield() { - return this.user.bitfield as readonly BitField[]; - } - - perkTier(noCheckOtherAccounts?: boolean | undefined) { - return getUsersPerkTier(this, noCheckOtherAccounts); + perkTier() { + return getUsersPerkTier(this); } skillLevel(skill: xp_gains_skill_enum) { @@ -227,23 +229,15 @@ export class MUserClass { } get rawUsername() { - return globalClient.users.cache.get(this.id)?.username ?? usernameCache.get(this.id) ?? 'Unknown'; + return cleanUsername(this.user.username ?? globalClient.users.cache.get(this.id)?.username ?? 'Unknown'); } get usernameOrMention() { - return usernameCache.get(this.id) ?? this.mention; - } - - get badgeString() { - const rawBadges = this.user.badges.map(num => badges[num]); - if (this.isIronman) { - rawBadges.push(Emoji.Ironman); - } - return rawBadges.join(' '); + return this.rawUsername; } get badgedUsername() { - return `${this.badgeString} ${this.usernameOrMention}`; + return `${this.badgesString} ${this.usernameOrMention}`.trim(); } toString() { @@ -302,14 +296,13 @@ export class MUserClass { } async calcActualClues() { - const result: { id: number; qty: number }[] = - await prisma.$queryRawUnsafe(`SELECT (data->>'clueID')::int AS id, SUM((data->>'quantity')::int)::int AS qty + const result: { id: number; qty: number }[] = await prisma.$queryRawUnsafe(`SELECT (data->>'ci')::int AS id, SUM((data->>'q')::int)::int AS qty FROM activity WHERE type = 'ClueCompletion' AND user_id = '${this.id}'::bigint -AND data->>'clueID' IS NOT NULL +AND data->>'ci' IS NOT NULL AND completed = true -GROUP BY data->>'clueID';`); +GROUP BY data->>'ci';`); const casketsCompleted = new Bank(); for (const res of result) { const item = getItem(res.id); @@ -460,7 +453,7 @@ GROUP BY data->>'clueID';`); return false; } } - return type === 'one' ? false : true; + return type !== 'one'; } getSkills(levels: boolean) { @@ -612,8 +605,8 @@ Charge your items using ${mentionCommand(globalClient, 'minion', 'charge')}.` const ammo = newRangeGear.ammo?.quantity; const projectileCategory = Object.values(projectiles).find(i => i.items.includes(equippedAmmo)); - if (hasAvas && projectileCategory!.savedByAvas) { - let ammoCopy = ammoRemove[1]; + if (hasAvas && projectileCategory?.savedByAvas) { + const ammoCopy = ammoRemove[1]; for (let i = 0; i < ammoCopy; i++) { if (percentChance(80)) { ammoRemove[1]--; @@ -624,10 +617,10 @@ Charge your items using ${mentionCommand(globalClient, 'minion', 'charge')}.` if (!ammo || ammo < ammoRemove[1]) throw new UserError( `Not enough ${ammoRemove[0].name} equipped in ${gearKey} gear, you need ${ - ammoRemove![1] + ammoRemove?.[1] } but you have only ${ammo}.` ); - newRangeGear.ammo!.quantity -= ammoRemove![1]; + newRangeGear.ammo!.quantity -= ammoRemove?.[1]; if (newRangeGear.ammo!.quantity <= 0) newRangeGear.ammo = null; const updateKey = options?.wildy ? 'gear_wildy' : 'gear_range'; updates[updateKey] = newRangeGear as any as Prisma.InputJsonObject; @@ -635,7 +628,7 @@ Charge your items using ${mentionCommand(globalClient, 'minion', 'charge')}.` if (dart) { if (hasAvas) { - let copyDarts = dart![1]; + const copyDarts = dart?.[1]; for (let i = 0; i < copyDarts; i++) { if (percentChance(80)) { realCost.remove(dart[0].id, 1); @@ -664,7 +657,7 @@ Charge your items using ${mentionCommand(globalClient, 'minion', 'charge')}.` ); } const bpData = { ...this.blowpipe }; - bpData.dartQuantity -= dart![1]; + bpData.dartQuantity -= dart?.[1]; bpData.scales -= scales; validateBlowpipeData(bpData); updates.blowpipe = bpData; @@ -751,19 +744,7 @@ Charge your items using ${mentionCommand(globalClient, 'minion', 'charge')}.` } async fetchStats(selectKeys: T): Promise> { - const keysToSelect = Object.keys(selectKeys).length === 0 ? { user_id: true } : selectKeys; - const result = await prisma.userStats.upsert({ - where: { - user_id: BigInt(this.id) - }, - create: { - user_id: BigInt(this.id) - }, - update: {}, - select: keysToSelect - }); - - return result as unknown as SelectedUserStats; + return fetchUserStats(this.id, selectKeys); } get logName() { @@ -803,13 +784,8 @@ Charge your items using ${mentionCommand(globalClient, 'minion', 'charge')}.` return new Map(rawPaintedItems as [number, number][]); } - async getTames() { - const tames = await prisma.tame.findMany({ where: { user_id: this.id } }); - return tames.map(t => new MTame(t)); - } - async getGodFavour(): Promise { - let { god_favour_bank: currentFavour } = await this.fetchStats({ god_favour_bank: true }); + const { god_favour_bank: currentFavour } = await this.fetchStats({ god_favour_bank: true }); if (!currentFavour) { return { Zamorak: 0, @@ -866,9 +842,9 @@ Charge your items using ${mentionCommand(globalClient, 'minion', 'charge')}.` if (currentStepID === null) { return { step: null, track: null }; } - const [trackID] = murMurSort( + const [trackID] = seedShuffle( mysteriousTrailTracks.map(i => i.id), - `${this.id}i222v1dv,2` + this.id ); const track = mysteriousTrailTracks.find(i => i.id === trackID)!; const step = track.steps[currentStepID - 1]; @@ -901,20 +877,28 @@ Charge your items using ${mentionCommand(globalClient, 'minion', 'charge')}.` return this.caPoints() >= CombatAchievements[tier].rewardThreshold; } - buildCATertiaryItemChanges() { + buildTertiaryItemChanges(hasRingOfWealthI = false, inWildy = false, onTask = false) { const changes = new Map(); - if (this.hasCompletedCATier('easy')) { - changes.set('Clue scroll (easy)', 5); - } - if (this.hasCompletedCATier('medium')) { - changes.set('Clue scroll (medium)', 5); - } - if (this.hasCompletedCATier('hard')) { - changes.set('Clue scroll (hard)', 5); + + const tiers = Object.keys(CombatAchievements) as Array; + for (const tier of tiers) { + let change = hasRingOfWealthI ? 50 : 0; + if (this.hasCompletedCATier(tier)) { + change += 5; + } + changes.set(`Clue scroll (${tier})`, change); } - if (this.hasCompletedCATier('elite')) { - changes.set('Clue scroll (elite)', 5); + + if (inWildy) changes.set('Giant key', 50); + + if (inWildy && !onTask) { + changes.set('Mossy key', 60); + } else if (!inWildy && onTask) { + changes.set('Mossy key', 66.67); + } else if (inWildy && onTask) { + changes.set('Mossy key', 77.6); } + return changes; } @@ -943,7 +927,7 @@ Charge your items using ${mentionCommand(globalClient, 'minion', 'charge')}.` return; } if (background.owners) { - const userIDs = getAllIDsOfUser(this); + const userIDs = await findGroupOfUser(this.id); if (background.owners.some(owner => userIDs.includes(owner))) { return; } @@ -992,7 +976,7 @@ Charge your items using ${mentionCommand(globalClient, 'minion', 'charge')}.` } async validateEquippedGear() { - let itemsUnequippedAndRefunded = new Bank(); + const itemsUnequippedAndRefunded = new Bank(); for (const [gearSetupName, gearSetup] of Object.entries(this.gear) as [GearSetupType, GearSetup][]) { if (gearSetup['2h'] !== null) { if (gearSetup.weapon?.item) { @@ -1037,12 +1021,32 @@ Charge your items using ${mentionCommand(globalClient, 'minion', 'charge')}.` } async fetchTames() { - const tames = await prisma.tame.findMany({ + const rawTames = await prisma.tame.findMany({ where: { user_id: this.id } }); - return tames.map(t => new MTame(t)); + + const tames = rawTames.map(t => new MTame(t)); + + const totalBank = new Bank(); + for (const tame of tames) { + totalBank.add(tame.totalLoot); + } + await prisma.userStats.upsert({ + where: { + user_id: BigInt(this.id) + }, + create: { + user_id: BigInt(this.id), + tame_cl_bank: totalBank.bank + }, + update: { + tame_cl_bank: totalBank.bank + } + }); + + return tames; } async fetchActiveTame(): Promise<{ tame: null; activity: null } | { activity: TameActivity | null; tame: MTame }> { @@ -1110,14 +1114,16 @@ Charge your items using ${mentionCommand(globalClient, 'minion', 'charge')}.` } declare global { export type MUser = MUserClass; + var mUserFetch: typeof srcMUserFetch; + var GlobalMUserClass: typeof MUserClass; } -export async function srcMUserFetch(userID: string) { +async function srcMUserFetch(userID: string, updates: Prisma.UserUpdateInput = {}) { const user = await prisma.user.upsert({ create: { id: userID }, - update: {}, + update: updates, where: { id: userID } @@ -1125,32 +1131,12 @@ export async function srcMUserFetch(userID: string) { return new MUserClass(user); } -declare global { - const mUserFetch: typeof srcMUserFetch; - const GlobalMUserClass: typeof MUserClass; -} -declare global { - namespace NodeJS { - interface Global { - mUserFetch: typeof srcMUserFetch; - GlobalMUserClass: typeof MUserClass; - } - } -} global.mUserFetch = srcMUserFetch; -/** - * Determines if the user is only a patron because they have shared perks from another account. - */ -export function isPrimaryPatron(user: MUser) { - const perkTier = getUsersPerkTier(user, true); - return perkTier > 0; -} - export const dailyResetTime = Time.Hour * 4; export const spawnLampResetTime = (user: MUser) => { const bf = user.bitfield; - const perkTier = getUsersPerkTier(user, true); + const perkTier = user.perkTier(); const hasPerm = bf.includes(BitField.HasPermanentSpawnLamp); const hasTier5 = perkTier >= PerkTier.Five; diff --git a/src/lib/PaginatedMessage.ts b/src/lib/PaginatedMessage.ts index 848859469d0..8f184a31fd7 100644 --- a/src/lib/PaginatedMessage.ts +++ b/src/lib/PaginatedMessage.ts @@ -1,17 +1,11 @@ -import { UserError } from '@oldschoolgg/toolkit/dist/lib/UserError'; -import { - ActionRowBuilder, - BaseMessageOptions, - ButtonBuilder, - ButtonStyle, - ComponentType, - MessageEditOptions, - TextChannel -} from 'discord.js'; -import { Time } from 'e'; +import { UserError } from '@oldschoolgg/toolkit'; +import type { BaseMessageOptions, ComponentType, MessageEditOptions, TextChannel } from 'discord.js'; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js'; +import { Time, isFunction } from 'e'; -import { PaginatedMessagePage } from './util'; -import { logError } from './util/logError'; +import { InteractionID } from './InteractionID'; +import type { PaginatedMessagePage } from './util'; +import { logError, logErrorForInteraction } from './util/logError'; const controlButtons: { customId: string; @@ -19,12 +13,12 @@ const controlButtons: { run: (opts: { paginatedMessage: PaginatedMessage }) => unknown; }[] = [ { - customId: 'pm-first-page', + customId: InteractionID.PaginatedMessage.FirstPage, emoji: '⏪', run: ({ paginatedMessage }) => (paginatedMessage.index = 0) }, { - customId: 'pm-previous-page', + customId: InteractionID.PaginatedMessage.PreviousPage, emoji: '◀️', run: ({ paginatedMessage }) => { if (paginatedMessage.index === 0) { @@ -35,7 +29,7 @@ const controlButtons: { } }, { - customId: 'pm-next-page', + customId: InteractionID.PaginatedMessage.NextPage, emoji: '▶️', run: ({ paginatedMessage }) => { if (paginatedMessage.index === paginatedMessage.totalPages - 1) { @@ -46,7 +40,7 @@ const controlButtons: { } }, { - customId: 'pm-last-page', + customId: InteractionID.PaginatedMessage.LastPage, emoji: '⏩', run: ({ paginatedMessage }) => (paginatedMessage.index = paginatedMessage.totalPages - 1) } @@ -83,8 +77,9 @@ export class PaginatedMessage { const rawPage = !Array.isArray(this.pages) ? await this.pages.generate({ currentPage: this.index }) : this.pages[this.index]; + return { - ...rawPage, + ...(isFunction(rawPage) ? await rawPage() : rawPage), components: numberOfPages === 1 ? [] @@ -97,7 +92,7 @@ export class PaginatedMessage { .setEmoji(i.emoji) ) ) - ] + ] }; } catch (err) { if (typeof err === 'string') return err; @@ -128,7 +123,11 @@ export class PaginatedMessage { }); if (previousIndex !== this.index) { - await interaction.update(await this.render()); + try { + await interaction.update(await this.render()); + } catch (err) { + logErrorForInteraction(err, interaction); + } return; } } diff --git a/src/lib/Task.ts b/src/lib/Task.ts index 655768fc96e..2cca2d1123d 100644 --- a/src/lib/Task.ts +++ b/src/lib/Task.ts @@ -1,7 +1,16 @@ -import { Activity, activity_type_enum } from '@prisma/client'; -import { z, ZodSchema } from 'zod'; +import type { Activity } from '@prisma/client'; +import { activity_type_enum } from '@prisma/client'; +import type { ZodSchema } from 'zod'; +import { z } from 'zod'; import { production } from '../config'; +import { aerialFishingTask } from '../tasks/minions/HunterActivity/aerialFishingActivity'; +import { birdHouseTask } from '../tasks/minions/HunterActivity/birdhouseActivity'; +import { driftNetTask } from '../tasks/minions/HunterActivity/driftNetActivity'; +import { hunterTask } from '../tasks/minions/HunterActivity/hunterActivity'; +import { buryingTask } from '../tasks/minions/PrayerActivity/buryingActivity'; +import { offeringTask } from '../tasks/minions/PrayerActivity/offeringActivity'; +import { scatteringTask } from '../tasks/minions/PrayerActivity/scatteringActivity'; import { agilityTask } from '../tasks/minions/agilityActivity'; import { alchingTask } from '../tasks/minions/alchingActivity'; import { bossEventTask } from '../tasks/minions/bossEventActivity'; @@ -35,6 +44,8 @@ import { camdozaalSmithingTask } from '../tasks/minions/camdozaalActivity/camdoz import { castingTask } from '../tasks/minions/castingActivity'; import { clueTask } from '../tasks/minions/clueActivity'; import { collectingTask } from '../tasks/minions/collectingActivity'; +import { colosseumTask } from '../tasks/minions/colosseumActivity'; +import { combatRingTask } from '../tasks/minions/combatRingActivity'; import { constructionTask } from '../tasks/minions/constructionActivity'; import { cookingTask } from '../tasks/minions/cookingActivity'; import { craftingTask } from '../tasks/minions/craftingActivity'; @@ -48,10 +59,6 @@ import { fletchingTask } from '../tasks/minions/fletchingActivity'; import { gloryChargingTask } from '../tasks/minions/gloryChargingActivity'; import { groupoMonsterTask } from '../tasks/minions/groupMonsterActivity'; import { herbloreTask } from '../tasks/minions/herbloreActivity'; -import { aerialFishingTask } from '../tasks/minions/HunterActivity/aerialFishingActivity'; -import { birdHouseTask } from '../tasks/minions/HunterActivity/birdhouseActivity'; -import { driftNetTask } from '../tasks/minions/HunterActivity/driftNetActivity'; -import { hunterTask } from '../tasks/minions/HunterActivity/hunterActivity'; import { mageArenaTwoTask } from '../tasks/minions/mageArena2Activity'; import { mageArenaTask } from '../tasks/minions/mageArenaActivity'; import { agilityArenaTask } from '../tasks/minions/minigames/agilityArenaActivity'; @@ -91,9 +98,6 @@ import { miningTask } from '../tasks/minions/miningActivity'; import { monsterTask } from '../tasks/minions/monsterActivity'; import { motherlodeMiningTask } from '../tasks/minions/motherlodeMineActivity'; import { pickpocketTask } from '../tasks/minions/pickpocketActivity'; -import { buryingTask } from '../tasks/minions/PrayerActivity/buryingActivity'; -import { offeringTask } from '../tasks/minions/PrayerActivity/offeringActivity'; -import { scatteringTask } from '../tasks/minions/PrayerActivity/scatteringActivity'; import { questingTask } from '../tasks/minions/questingActivity'; import { runecraftTask } from '../tasks/minions/runecraftActivity'; import { sawmillTask } from '../tasks/minions/sawmillActivity'; @@ -113,11 +117,11 @@ import { nightmareZoneTask } from './../tasks/minions/minigames/nightmareZoneAct import { underwaterAgilityThievingTask } from './../tasks/minions/underwaterActivity'; import { modifyBusyCounter } from './busyCounterCache'; import { minionActivityCache } from './constants'; -import { convertStoredActivityToFlatActivity, prisma } from './settings/prisma'; +import { convertStoredActivityToFlatActivity } from './settings/prisma'; import { activitySync, minionActivityCacheDelete } from './settings/settings'; import { logError } from './util/logError'; -export const tasks: MinionTask[] = [ +const tasks: MinionTask[] = [ aerialFishingTask, birdHouseTask, driftNetTask, @@ -221,13 +225,15 @@ export const tasks: MinionTask[] = [ underwaterAgilityThievingTask, doaTask, strongholdTask, + combatRingTask, specificQuestTask, camdozaalMiningTask, camdozaalSmithingTask, camdozaalFishingTask, memoryHarvestTask, guthixianCacheTask, - turaelsTrialsTask + turaelsTrialsTask, + colosseumTask ]; export async function processPendingActivities() { @@ -237,9 +243,10 @@ export async function processPendingActivities() { finish_date: production ? { lt: new Date() - } + } : undefined - } + }, + take: 5 }); if (activities.length > 0) { @@ -253,20 +260,17 @@ export async function processPendingActivities() { completed: true } }); + await Promise.all(activities.map(completeActivity)); } - - await Promise.all(activities.map(completeActivity)); - return activities; } -export async function syncActivityCache() { +export const syncActivityCache = async () => { const tasks = await prisma.activity.findMany({ where: { completed: false } }); - minionActivityCache.clear(); for (const task of tasks) { activitySync(task); } -} +}; const ActivityTaskOptionsSchema = z.object({ userID: z.string(), @@ -276,17 +280,18 @@ const ActivityTaskOptionsSchema = z.object({ channelID: z.string() }); -export async function completeActivity(_activity: Activity) { +async function completeActivity(_activity: Activity) { const activity = convertStoredActivityToFlatActivity(_activity); - debugLog(`Attemping to complete activity ID[${activity.id}] TYPE[${activity.type}] USER[${activity.userID}]`); if (_activity.completed) { - throw new Error('Tried to complete an already completed task.'); + logError(new Error('Tried to complete an already completed task.')); + return; } const task = tasks.find(i => i.type === activity.type)!; if (!task) { - throw new Error('Missing task'); + logError(new Error('Missing task')); + return; } modifyBusyCounter(activity.userID, 1); @@ -295,7 +300,7 @@ export async function completeActivity(_activity: Activity) { const schema = ActivityTaskOptionsSchema.and(task.dataSchema); const { success } = schema.safeParse(activity); if (!success) { - console.error(`Invalid activity data for ${activity.type} task: ${JSON.stringify(activity)}`); + logError(new Error(`Invalid activity data for ${activity.type} task: ${JSON.stringify(activity)}`)); } } await task.run(activity); @@ -304,7 +309,6 @@ export async function completeActivity(_activity: Activity) { } finally { modifyBusyCounter(activity.userID, -1); minionActivityCacheDelete(activity.userID); - debugLog(`Finished completing activity ID[${activity.id}] TYPE[${activity.type}] USER[${activity.userID}]`); } } diff --git a/src/lib/addXP.ts b/src/lib/addXP.ts index f7f7aa3878a..202d9cced3f 100644 --- a/src/lib/addXP.ts +++ b/src/lib/addXP.ts @@ -1,9 +1,8 @@ import { formatOrdinal, toTitleCase } from '@oldschoolgg/toolkit'; import { UserEventType } from '@prisma/client'; import { bold } from 'discord.js'; -import { increaseNumByPercent, noOp, notEmpty, objectValues, Time } from 'e'; -import { Item } from 'oldschooljs/dist/meta/types'; -import { convertLVLtoXP, convertXPtoLVL, toKMB } from 'oldschooljs/dist/util/util'; +import { Time, increaseNumByPercent, noOp, notEmpty, objectValues } from 'e'; +import type { Item } from 'oldschooljs/dist/meta/types'; import { MAXING_MESSAGE } from '../config'; import { Channel, Events, GLOBAL_BSO_XP_MULTIPLIER, LEVEL_120_XP, MAX_TOTAL_LEVEL, MAX_XP } from './constants'; @@ -16,12 +15,12 @@ import { } from './data/CollectionsExport'; import { skillEmoji } from './data/emojis'; import { getSimilarItems } from './data/similarItems'; -import { AddXpParams } from './minions/types'; -import { prisma } from './settings/prisma'; +import type { AddXpParams } from './minions/types'; + import Skillcapes from './skilling/skillcapes'; import Skills from './skilling/skills'; import { SkillsEnum } from './skilling/types'; -import { itemNameFromID } from './util'; +import { convertLVLtoXP, convertXPtoLVL, itemNameFromID, toKMB } from './util'; import getOSItem from './util/getOSItem'; import resolveItems from './util/resolveItems'; import { insertUserEvent } from './util/userEvents'; @@ -39,7 +38,7 @@ async function howManyMaxed() { (await Promise.all([prisma.$queryRawUnsafe(makeQuery(false)), prisma.$queryRawUnsafe(makeQuery(true))])) as any ) .map((i: any) => i[0].count) - .map((i: any) => parseInt(i)); + .map((i: any) => Number.parseInt(i)); return { normies, @@ -110,7 +109,7 @@ const skillingOutfitBoosts = [ // Build list of all Master capes including combined capes. const allMasterCapes = Skillcapes.map(i => i.masterCape) .map(msc => getSimilarItems(msc.id)) - .flat(Infinity) as number[]; + .flat(Number.POSITIVE_INFINITY) as number[]; function getEquippedCapes(user: MUser) { return objectValues(user.gear) @@ -361,26 +360,24 @@ export async function addXP(user: MUser, params: AddXpParams): Promise { if (currentXP >= MAX_XP) { let xpStr = ''; if (params.duration && !params.minimal) { - xpStr += `You received no XP because you have ${toKMB(MAX_XP)} ${name} XP already.`; - xpStr += ` Tracked ${params.amount.toLocaleString()} ${skill.emoji} XP.`; + xpStr += `You received no XP because you have ${toKMB(MAX_XP)} ${name}XP already`; + xpStr += ` Tracked ${params.amount.toLocaleString()}${skill.emoji}XP.`; let rawXPHr = (params.amount / (params.duration / Time.Minute)) * 60; rawXPHr = Math.floor(rawXPHr / 1000) * 1000; xpStr += ` (${toKMB(rawXPHr)}/Hr)`; } else { - xpStr += `:no_entry_sign: Tracked ${params.amount.toLocaleString()} ${skill.emoji} XP.`; + xpStr += `:no_entry_sign: Tracked ${params.amount.toLocaleString()}${skill.emoji}XP.`; } return xpStr; } - await user.update({ - [`skills_${params.skillName}`]: Math.floor(newXP) - }); + await user.update({ [`skills_${params.skillName}`]: Math.floor(newXP) }); if (currentXP < MAX_XP && newXP === MAX_XP && Object.values(user.skillsAsXP).every(xp => xp === MAX_XP)) { globalClient.emit( Events.ServerNotification, bold( - `🎉 ${skill.emoji} **${user.badgedUsername}'s** minion, ${user.minionName}, just achieved the maximum possible total XP!` + `🎉 ${skill.emoji}**${user.badgedUsername}'s** minion, ${user.minionName}, just achieved the maximum possible total XP!` ) ); await insertUserEvent({ userID: user.id, type: UserEventType.MaxTotalXP }); diff --git a/src/lib/analytics.ts b/src/lib/analytics.ts index be29bc33b82..5a09787ae7d 100644 --- a/src/lib/analytics.ts +++ b/src/lib/analytics.ts @@ -1,7 +1,8 @@ import { ActivityGroup, globalConfig } from '../lib/constants'; -import { prisma } from '../lib/settings/prisma'; -import { GroupMonsterActivityTaskOptions } from '../lib/types/minions'; + +import type { GroupMonsterActivityTaskOptions } from '../lib/types/minions'; import { taskGroupFromActivity } from '../lib/util/taskGroupFromActivity'; +import { getItem } from './util/getOSItem'; async function calculateMinionTaskCounts() { const minionTaskCounts: Record = { @@ -33,9 +34,6 @@ async function calculateMinionTaskCounts() { } export async function analyticsTick() { - debugLog('Analytics tick', { - type: 'ANALYTICS_TICK' - }); const [numberOfMinions, totalSacrificed, numberOfIronmen, totalGP] = ( await Promise.all( [ @@ -45,10 +43,23 @@ export async function analyticsTick() { 'SELECT SUM("GP") AS count FROM users;' ].map(query => prisma.$queryRawUnsafe(query)) ) - ).map((result: any) => parseInt(result[0].count)) as number[]; + ).map((result: any) => Number.parseInt(result[0].count)) as number[]; + + const artifact = getItem('Magical artifact')!; + const statuette = getItem('Demon statuette')!; + + const [totalGeGp, totalArtifactGp, totalDemonStatuetteGp] = ( + await Promise.all( + [ + 'SELECT quantity AS val FROM ge_bank WHERE item_id = 995', + `SELECT COALESCE(SUM((bank->>'${artifact.id}')::bigint) * ${artifact.highalch}, 0) as val FROM users WHERE bank->>'${artifact.id}' IS NOT NULL`, + `SELECT COALESCE(SUM((bank->>'${statuette.id}')::bigint) * ${statuette.highalch}, 0) as val FROM users WHERE bank->>'${artifact.id}' IS NOT NULL` + ].map(q => prisma.$queryRawUnsafe<{ val: bigint }[]>(q)) + ) + ).map((v: { val: bigint }[]) => BigInt(v[0].val)); const taskCounts = await calculateMinionTaskCounts(); - const currentClientSettings = await await prisma.clientStorage.findFirst({ + const currentClientSettings = await prisma.clientStorage.upsert({ where: { id: globalConfig.clientID }, @@ -68,9 +79,12 @@ export async function analyticsTick() { gp_tax_balance: true, economyStats_dailiesAmount: true, gp_ic: true - } + }, + create: { + id: globalConfig.clientID + }, + update: {} }); - if (!currentClientSettings) throw new Error('No client settings found'); await prisma.analytic.create({ data: { guildsCount: globalClient.guilds.cache.size, @@ -84,6 +98,8 @@ export async function analyticsTick() { minionsCount: numberOfMinions, totalSacrificed, totalGP, + totalGeGp, + totalBigAlchGp: totalDemonStatuetteGp + totalArtifactGp, dicingBank: currentClientSettings.economyStats_dicingBank, duelTaxBank: currentClientSettings.economyStats_duelTaxBank, dailiesAmount: currentClientSettings.economyStats_dailiesAmount, diff --git a/src/lib/badges.ts b/src/lib/badges.ts index c7fdc9cba61..bcf839069ab 100644 --- a/src/lib/badges.ts +++ b/src/lib/badges.ts @@ -1,5 +1,4 @@ -import { badges, BadgesEnum } from '../lib/constants'; -import { prisma } from '../lib/settings/prisma'; +import { BadgesEnum, badges } from '../lib/constants'; export async function cacheBadges() { const newCache = new Map(); @@ -25,5 +24,6 @@ export async function cacheBadges() { newCache.set(user.RSN.toLowerCase(), userBadges.join(' ')); } + globalClient._badgeCache?.clear(); globalClient._badgeCache = newCache; } diff --git a/src/lib/bankImage.ts b/src/lib/bankImage.ts index 20260b73bf4..f2609f6394d 100644 --- a/src/lib/bankImage.ts +++ b/src/lib/bankImage.ts @@ -1,35 +1,35 @@ -/* eslint-disable @typescript-eslint/no-invalid-this */ -import { Canvas, GlobalFonts, Image, loadImage, SKRSContext2D } from '@napi-rs/canvas'; -import { cleanString, formatItemStackQuantity, generateHexColorForCashStack } from '@oldschoolgg/toolkit'; -import { UserError } from '@oldschoolgg/toolkit/dist/lib/UserError'; +import { existsSync } from 'node:fs'; +import * as fs from 'node:fs/promises'; +import * as path from 'node:path'; +import type { SKRSContext2D } from '@napi-rs/canvas'; +import { Canvas, GlobalFonts, Image, loadImage } from '@napi-rs/canvas'; +import { PerkTier, cleanString, formatItemStackQuantity, generateHexColorForCashStack } from '@oldschoolgg/toolkit'; +import { UserError } from '@oldschoolgg/toolkit'; import { AttachmentBuilder } from 'discord.js'; import { chunk, randInt, sumArr } from 'e'; -import { existsSync } from 'fs'; -import * as fs from 'fs/promises'; import fetch from 'node-fetch'; import { Bank } from 'oldschooljs'; -import { Item } from 'oldschooljs/dist/meta/types'; -import { toKMB } from 'oldschooljs/dist/util/util'; -import * as path from 'path'; +import type { Item } from 'oldschooljs/dist/meta/types'; +import { resolveItems, toKMB } from 'oldschooljs/dist/util/util'; -import { BitField, BOT_TYPE, doaPurples, ItemIconPacks, PerkTier, toaPurpleItems } from '../lib/constants'; import { allCLItems } from '../lib/data/Collections'; import { filterableTypes } from '../lib/data/filterables'; import { calcWholeDisXP, findDisassemblyGroup } from '../lib/invention/disassemble'; import backgroundImages from '../lib/minions/data/bankBackgrounds'; -import { BankBackground, FlagMap, Flags } from '../lib/minions/types'; -import { BankSortMethod, BankSortMethods, sorts } from '../lib/sorts'; -import { ItemBank } from '../lib/types'; +import type { BankBackground, FlagMap, Flags } from '../lib/minions/types'; +import type { BankSortMethod } from '../lib/sorts'; +import { BankSortMethods, sorts } from '../lib/sorts'; +import type { ItemBank } from '../lib/types'; import { drawImageWithOutline, fillTextXTimesInCtx, getClippedRegionImage } from '../lib/util/canvasUtil'; import itemID from '../lib/util/itemID'; import { logError } from '../lib/util/logError'; import { XPLamps } from '../mahoji/lib/abstracted_commands/lampCommand'; import { divinationEnergies } from './bso/divination'; +import { BOT_TYPE, BitField, ItemIconPacks, doaPurples, toaPurpleItems } from './constants'; import { TOBUniques } from './data/tob'; import { marketPriceOfBank, marketPriceOrBotPrice } from './marketPrices'; import { SkillsEnum } from './skilling/types'; import { applyCustomItemEffects } from './util/customItemEffects'; -import resolveItems from './util/resolveItems'; import { allSlayerMaskHelmsAndMasks, slayerMaskLeaderboardCache } from './util/slayerMaskLeaderboard'; const fonts = { @@ -276,7 +276,7 @@ function drawTitle(ctx: SKRSContext2D, title: string, canvas: Canvas) { // Draw Bank Title ctx.font = '16px RuneScape Bold 12'; const titleWidthPx = ctx.measureText(title); - let titleX = Math.floor(floor(canvas.width / 2) - titleWidthPx.width / 2); + const titleX = Math.floor(floor(canvas.width / 2) - titleWidthPx.width / 2); ctx.fillStyle = '#000000'; fillTextXTimesInCtx(ctx, title, titleX + 1, 22); @@ -298,7 +298,7 @@ export const bankFlags = [ ] as const; export type BankFlag = (typeof bankFlags)[number]; -class BankImageTask { +export class BankImageTask { public itemIconsList: Set; public itemIconImagesCache: Map; public backgroundImages: BankBackground[] = []; @@ -311,6 +311,7 @@ class BankImageTask { public bananaGlow: Image | null = null; public glows: Map; public treeImage!: Image; + public ready!: Promise; public constructor() { // This tells us simply whether the file exists or not on disk. @@ -323,6 +324,8 @@ class BankImageTask { [itemID('Dragon egg'), this.redGlow!], [itemID('Monkey egg'), this.bananaGlow!] ]); + + this.ready = this.init(); } async init() { @@ -337,8 +340,8 @@ class BankImageTask { const basePath = './src/lib/resources/images/bank_backgrounds/spritesheet/'; const files = await fs.readdir(basePath); for (const file of files) { - const bgName: BGSpriteName = file.split('\\').pop()!.split('/').pop()!.split('.').shift()! as BGSpriteName; - let d = await loadImage(await fs.readFile(basePath + file)); + const bgName: BGSpriteName = file.split('\\').pop()?.split('/').pop()?.split('.').shift()! as BGSpriteName; + const d = await loadImage(await fs.readFile(basePath + file)); this._bgSpriteData = d; this.bgSpriteList[bgName] = { name: bgName, @@ -403,7 +406,7 @@ class BankImageTask { // For each one, set a cache value that it exists. for (const fileName of filesInDir) { - this.itemIconsList.add(parseInt(path.parse(fileName).name)); + this.itemIconsList.add(Number.parseInt(path.parse(fileName).name)); } for (const pack of ItemIconPacks) { @@ -412,7 +415,7 @@ class BankImageTask { for (const dir of directories) { const filesInThisDir = await fs.readdir(`./src/lib/resources/images/icon_packs/${pack.id}_${dir}`); for (const fileName of filesInThisDir) { - const themedItemID = parseInt(path.parse(fileName).name); + const themedItemID = Number.parseInt(path.parse(fileName).name); const image = await loadImage( `./src/lib/resources/images/icon_packs/${pack.id}_${dir}/${fileName}` ); @@ -436,12 +439,7 @@ class BankImageTask { const isOnDisk = this.itemIconsList.has(itemID); if (!isOnDisk) { - try { - await this.fetchAndCacheImage(itemID); - } catch (err) { - console.error(`Failed to load ${itemID} image`, err); - return this.getItemImage(1, user); - } + await this.fetchAndCacheImage(itemID); return this.getItemImage(itemID, user); } @@ -532,13 +530,12 @@ class BankImageTask { } } - getBgAndSprite(bankBgId: number = 1, user?: MUser) { - let background = this.backgroundImages.find(i => i.id === bankBgId)!; + getBgAndSprite(bankBgId = 1, user?: MUser) { + const background = this.backgroundImages.find(i => i.id === bankBgId)!; const currentContract = user?.farmingContract(); const isFarmingContractReadyToHarvest = Boolean( - currentContract && - currentContract.contract.hasContract && + currentContract?.contract.hasContract && currentContract.matchingPlantedCrop && currentContract.matchingPlantedCrop.ready ); @@ -590,7 +587,7 @@ class BankImageTask { // Adds distance from side // 36 + 21 is the itemLength + the space between each item xLoc = 2 + 6 + (compact ? 9 : 20) + (i % itemsPerRow) * itemWidthSize; - let [item, quantity] = items[i]; + const [item, quantity] = items[i]; const itemImage = await this.getItemImage(item.id, user); const itemHeight = compact ? itemImage.height / 1 : itemImage.height; const itemWidth = compact ? itemImage.width / 1 : itemImage.width; @@ -669,7 +666,7 @@ class BankImageTask { } if (bottomItemText) { - let text = + const text = typeof bottomItemText === 'number' ? toKMB(bottomItemText) : bottomItemText.toString().slice(0, 8); ctx.fillStyle = 'black'; fillTextXTimesInCtx(ctx, text, floor(xLoc), yLoc + distanceFromTop); @@ -720,8 +717,6 @@ class BankImageTask { let items = bank.items(); - debugLog(`Generating a bank image with ${items.length} items`, { title, userID: user?.id }); - // Sorting const favorites = user?.user.favoriteItems; const weightings = user?.user.bank_sort_weightings as ItemBank; @@ -772,7 +767,7 @@ class BankImageTask { // Paging if (typeof page === 'number' && !isShowingFullBankImage) { - let pageLoot = chunked[page]; + const pageLoot = chunked[page]; if (!pageLoot) throw new UserError('You have no items on this page.'); items = pageLoot; } @@ -790,7 +785,7 @@ class BankImageTask { itemSize * 1.5 ) - 2; - let { + const { sprite: bgSprite, uniqueSprite: hasBgSprite, background: bgImage, @@ -804,7 +799,7 @@ class BankImageTask { currentCL !== undefined && bank.items().some(([item]) => !currentCL.has(item.id) && allCLItems.includes(item.id)); - let actualBackground = isPurple && bgImage.hasPurple ? bgImage.purpleImage! : backgroundImage; + const actualBackground = isPurple && bgImage.hasPurple ? bgImage.purpleImage! : backgroundImage; const hexColor = user?.user.bank_bg_hex; @@ -855,6 +850,7 @@ class BankImageTask { if (!isTransparent && noBorder !== 1) { this.drawBorder(ctx, bgSprite, bgImage.name === 'Default'); } + await this.drawItems( ctx, compact, @@ -1020,7 +1016,7 @@ export async function drawChestLootImage(options: { name: fileName }); } - let spaceBetweenImages = 15; + const spaceBetweenImages = 15; const combinedCanvas = new Canvas( canvases[0].width * canvases.length + spaceBetweenImages * canvases.length, canvases[0].height @@ -1036,14 +1032,8 @@ export async function drawChestLootImage(options: { } declare global { - const bankImageGenerator: BankImageTask; + var bankImageGenerator: BankImageTask; } -declare global { - namespace NodeJS { - interface Global { - bankImageGenerator: BankImageTask; - } - } -} -global.bankImageGenerator = new BankImageTask(); -bankImageGenerator.init(); + +export const bankImageTask = new BankImageTask(); +global.bankImageGenerator = bankImageTask; diff --git a/src/lib/baxtorianBathhouses.ts b/src/lib/baxtorianBathhouses.ts index a7876c0576d..86b6f0d0b71 100644 --- a/src/lib/baxtorianBathhouses.ts +++ b/src/lib/baxtorianBathhouses.ts @@ -1,23 +1,24 @@ import { userMention } from '@discordjs/builders'; -import { User } from '@prisma/client'; -import { randArrItem, reduceNumByPercent, roll, Time, uniqueArr } from 'e'; +import type { User } from '@prisma/client'; +import { Time, randArrItem, reduceNumByPercent, roll, uniqueArr } from 'e'; import { Bank, LootTable } from 'oldschooljs'; -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; import { MysteryBoxes } from './bsoOpenables'; import { Emoji, GLOBAL_BSO_XP_MULTIPLIER } from './constants'; import { incrementMinigameScore } from './settings/minigames'; import Grimy from './skilling/skills/herblore/mixables/grimy'; import { SkillsEnum } from './skilling/types'; -import { getAllUserTames, TameSpeciesID } from './tames'; -import { Skills } from './types'; -import { BathhouseTaskOptions } from './types/minions'; +import { TameSpeciesID, getAllUserTames } from './tames'; +import type { Skills } from './types'; +import type { BathhouseTaskOptions } from './types/minions'; import { formatDuration, formatSkillRequirements, makeTable, skillsMeetRequirements, stringMatches } from './util'; import addSubTaskToActivityTask from './util/addSubTaskToActivityTask'; import { calcMaxTripLength } from './util/calcMaxTripLength'; import getOSItem from './util/getOSItem'; import { handleTripFinish } from './util/handleTripFinish'; -import resolveItems, { resolveOSItems } from './util/resolveItems'; +import { makeBankImage } from './util/makeBankImage'; +import resolveItems from './util/resolveItems'; import { updateBankSetting } from './util/updateBankSetting'; export const bathhouseTierNames = ['Warm', 'Hot', 'Fiery'] as const; @@ -123,13 +124,13 @@ export const bathHouseTiers: BathhouseTier[] = [ ]; export const BathwaterMixtures = [ - { items: resolveOSItems(['Guam leaf', 'Avantoe']), name: 'Vitalizing' }, - { items: resolveOSItems(['Marrentill', 'Kwuarm']), name: 'Soothing' }, - { items: resolveOSItems(['Tarromin', 'Snapdragon']), name: 'Caustic' }, - { items: resolveOSItems(['Harralander', 'Cadantine']), name: 'Magical' }, - { items: resolveOSItems(['Ranarr weed', 'Lantadyme']), name: 'Unholy' }, - { items: resolveOSItems(['Toadflax', 'Dwarf weed']), name: 'Invigorating' }, - { items: resolveOSItems(['Irit leaf', 'Torstol']), name: 'Healing' } + { items: resolveItems(['Guam leaf', 'Avantoe']), name: 'Vitalizing' }, + { items: resolveItems(['Marrentill', 'Kwuarm']), name: 'Soothing' }, + { items: resolveItems(['Tarromin', 'Snapdragon']), name: 'Caustic' }, + { items: resolveItems(['Harralander', 'Cadantine']), name: 'Magical' }, + { items: resolveItems(['Ranarr weed', 'Lantadyme']), name: 'Unholy' }, + { items: resolveItems(['Toadflax', 'Dwarf weed']), name: 'Invigorating' }, + { items: resolveItems(['Irit leaf', 'Torstol']), name: 'Healing' } ] as const; type BathwaterMixtureName = (typeof BathwaterMixtures)[number]['name']; @@ -306,7 +307,7 @@ export async function baxtorianBathhousesStartCommand({ mixtureToUse = BathwaterMixtures.find(o => { const checkBank = new Bank(); - for (const h of o.items) checkBank.add(h.id, herbsNeeded); + for (const id of o.items) checkBank.add(id, herbsNeeded); return userBank.has(checkBank); }) ?? BathwaterMixtures[0]; } @@ -351,14 +352,14 @@ export async function baxtorianBathhousesStartCommand({ .add(oreToUse.logs.id, logsNeeded); const herbCost = new Bank(); - for (const h of mixtureToUse.items) herbCost.add(h.id, herbsNeeded); + for (const id of mixtureToUse.items) herbCost.add(id, herbsNeeded); const cost = new Bank().add(heatingCost).add(herbCost); if (!user.owns(cost)) { return `You don't have enough supplies to do a trip, for ${quantity}x ${bathHouseTier.name} baths, you need: ${cost}.`; } - updateBankSetting('bb_cost', cost); + await updateBankSetting('bb_cost', cost); await user.removeItemsFromBank(cost); await addSubTaskToActivityTask({ @@ -384,7 +385,7 @@ export function calculateBathouseResult(data: BathhouseTaskOptions) { const ore = BathhouseOres.find(i => i.item.id === data.ore)!; const tier = bathHouseTiers.find(t => t.name === data.tier)!; const mixture = BathwaterMixtures.find(i => i.name === data.mixture)!; - const [firstHerb, secondHerb] = mixture.items.map(herb => Grimy.find(i => i.item.id === herb.id)!); + const [firstHerb, secondHerb] = mixture.items.map(herbID => Grimy.find(i => i.item.id === herbID)!); const speciesCanServe = species.filter(i => i.tier === tier.name); const herbXP = quantity * (firstHerb.xp + secondHerb.xp) * 220 * tier.xpMultiplier; @@ -419,7 +420,7 @@ export function calculateBathouseResult(data: BathhouseTaskOptions) { } export function baxBathSim() { - let results = []; + const results = []; for (const tier of bathHouseTiers) { for (const ore of BathhouseOres) { for (const mixture of BathwaterMixtures) { @@ -440,7 +441,7 @@ export function baxBathSim() { } results.sort((a, b) => b.firemakingXP * GLOBAL_BSO_XP_MULTIPLIER - a.firemakingXP * GLOBAL_BSO_XP_MULTIPLIER); - let tableArr = []; + const tableArr = []; for (const { tier, ore, mixture, firemakingXP, herbXP } of results) { tableArr.push([ `${tier.name} ${ore.item.name} ${mixture.name}`, @@ -473,7 +474,12 @@ export async function baxtorianBathhousesActivity(data: BathhouseTaskOptions) { } } - await user.addItemsToBank({ items: loot, collectionLog: true }); + const { previousCL, itemsAdded } = await transactItems({ + userID: user.id, + collectionLog: true, + itemsToAdd: loot + }); + let xpStr = await user.addXP({ skillName: SkillsEnum.Herblore, amount: herbXP, duration }); xpStr += '\n'; xpStr += await user.addXP({ @@ -482,16 +488,17 @@ export async function baxtorianBathhousesActivity(data: BathhouseTaskOptions) { duration }); - let uniqSpecies = uniqueArr(speciesServed); - updateBankSetting('bb_loot', loot); + const uniqSpecies = uniqueArr(speciesServed); + await updateBankSetting('bb_loot', loot); - const bankImage = await bankImageGenerator.generateBankImage({ - bank: loot, + const bankImage = await makeBankImage({ + bank: itemsAdded, + title: 'Baxtorian Bathhouses Loot', user, - title: 'Baxtorian Bathhouses Loot' + previousCL }); - handleTripFinish( + return handleTripFinish( user, channelID, `${userMention(userID)}, ${user.minionName} finished running ${quantity}x ${tier.name} baths for ${ @@ -502,7 +509,7 @@ export async function baxtorianBathhousesActivity(data: BathhouseTaskOptions) { : '' } ${xpStr}`, - bankImage.image, + bankImage.file.attachment, data, loot ); diff --git a/src/lib/blacklists.ts b/src/lib/blacklists.ts index a5ee8359429..278e74d6af5 100644 --- a/src/lib/blacklists.ts +++ b/src/lib/blacklists.ts @@ -1,12 +1,11 @@ import { Time } from 'e'; -import { production } from '../config'; +import { TimerManager } from '@sapphire/timer-manager'; export const BLACKLISTED_USERS = new Set(); export const BLACKLISTED_GUILDS = new Set(); export async function syncBlacklists() { - debugLog('Syncing blacklists'); const blacklistedEntities = await roboChimpClient.blacklistedEntity.findMany(); BLACKLISTED_USERS.clear(); BLACKLISTED_GUILDS.clear(); @@ -16,6 +15,4 @@ export async function syncBlacklists() { } } -if (production) { - setInterval(syncBlacklists, Time.Minute * 10); -} +TimerManager.setInterval(syncBlacklists, Time.Minute * 10); diff --git a/src/lib/bossEvents.ts b/src/lib/bossEvents.ts index bd2249642a7..ca290e16d4f 100644 --- a/src/lib/bossEvents.ts +++ b/src/lib/bossEvents.ts @@ -1,31 +1,31 @@ -import { EmbedBuilder, TextChannel } from 'discord.js'; +import { EmbedBuilder, type TextChannel } from 'discord.js'; import { + Time, calcPercentOfNum, calcWhatPercent, chunk, percentChance, randArrItem, reduceNumByPercent, - shuffleArr, - Time + shuffleArr } from 'e'; import { Bank, LootTable } from 'oldschooljs'; import { OWNER_IDS, production } from '../config'; -import { scaryEatables } from './constants'; -import { prisma } from './settings/prisma'; + import { - getPHeadDescriptor, - numberOfPHeadItemsInCL, PUMPKINHEAD_HEALING_NEEDED, PUMPKINHEAD_ID, + getPHeadDescriptor, + numberOfPHeadItemsInCL, pumpkinHeadNonUniqueTable, pumpkinHeadUniqueTable } from './simulation/pumpkinHead'; -import { BossInstance, BossOptions, BossUser } from './structures/Boss'; +import { BossInstance, type BossOptions, type BossUser } from './structures/Boss'; import { Gear } from './structures/Gear'; -import { NewBossOptions } from './types/minions'; +import type { NewBossOptions } from './types/minions'; import { formatDuration, roll } from './util'; +import getOSItem from './util/getOSItem'; import { logError } from './util/logError'; import { sendToChannelID } from './util/webhook'; import { LampTable } from './xpLamps'; @@ -39,10 +39,49 @@ interface BossEvent { export const bossEventChannelID = production ? '897170239333220432' : '1023760501957722163'; +export const scaryEatables = [ + { + item: getOSItem('Candy teeth'), + healAmount: 3 + }, + { + item: getOSItem('Toffeet'), + healAmount: 5 + }, + { + item: getOSItem('Chocolified skull'), + healAmount: 8 + }, + { + item: getOSItem('Rotten sweets'), + healAmount: 9 + }, + { + item: getOSItem('Hairyfloss'), + healAmount: 12 + }, + { + item: getOSItem('Eyescream'), + healAmount: 13 + }, + { + item: getOSItem('Goblinfinger soup'), + healAmount: 20 + }, + { + item: getOSItem("Benny's brain brew"), + healAmount: 50 + }, + { + item: getOSItem('Roasted newt'), + healAmount: 120 + } +]; + function getScaryFoodFromBank(user: MUser, totalHealingNeeded: number, _userBank?: Bank): false | Bank { if (OWNER_IDS.includes(user.id)) return new Bank(); let totalHealingCalc = totalHealingNeeded; - let foodToRemove = new Bank(); + const foodToRemove = new Bank(); const userBank = _userBank ?? user.bank; const sorted = [...scaryEatables] @@ -81,7 +120,7 @@ export const bossEvents: BossEvent[] = [ name: 'Pumpkinhead', handleFinish: async (data, bossUsers) => { const lootElligible = shuffleArr(bossUsers.filter(i => !percentChance(i.deathChance))); - let userLoot: Record = {}; + const userLoot: Record = {}; for (const i of lootElligible) { userLoot[i.user.id] = new Bank(); userLoot[i.user.id].add(pumpkinHeadNonUniqueTable.roll(5)); @@ -93,10 +132,10 @@ export const bossEvents: BossEvent[] = [ const lootGroups = chunk(lootElligible, 4).filter(i => i.length === 4); const uniqueItemRecipients = lootGroups.map(groupArr => randArrItem(groupArr)); - let uniqueLootStr = []; - let rerolledUsersStr = []; + const uniqueLootStr = []; + const rerolledUsersStr = []; - let secondChancePeople = []; + const secondChancePeople = []; for (const lootElliPerson of lootElligible) { if ( !uniqueItemRecipients.includes(lootElliPerson) && @@ -215,7 +254,7 @@ ${rerolledUsersStr.length > 0 ? rerolledUsersStr.join('\n') : 'Nobody was reroll itemCost: async data => { const foodRequired = getScaryFoodFromBank(data.user, PUMPKINHEAD_HEALING_NEEDED); if (!foodRequired) { - let fakeBank = new Bank(); + const fakeBank = new Bank(); for (const { item } of scaryEatables) fakeBank.add(item.id, 100); return getScaryFoodFromBank(data.user, PUMPKINHEAD_HEALING_NEEDED, fakeBank) as Bank; } @@ -261,7 +300,7 @@ export async function bossActiveIsActiveOrSoonActive(id?: BossEvent['id']) { ? undefined : { not: id - } + } } }); @@ -294,7 +333,7 @@ export async function startBossEvent({ boss, id }: { boss: BossEvent; id?: BossE return channel.send({ embeds: [embed], - content: instance.boosts.length > 0 ? `**Boosts:** ${instance.boosts.join(', ')}.` : undefined, + content: instance.boosts.length > 0 ? `**Boosts:** ${instance.boosts.join(', ')}.` : 'No boosts.', allowedMentions: { roles: ['896845245873025067'] } diff --git a/src/lib/boxFrenzy.ts b/src/lib/boxFrenzy.ts index c50bbf1f8a3..d2b9fbccca3 100644 --- a/src/lib/boxFrenzy.ts +++ b/src/lib/boxFrenzy.ts @@ -1,4 +1,4 @@ -import { Message, TextChannel } from 'discord.js'; +import { type Message, TextChannel } from 'discord.js'; import { noOp } from 'e'; import { Bank, Items } from 'oldschooljs'; @@ -12,7 +12,7 @@ export async function boxFrenzy(channelID: string, content: string, quantity: nu throw new Error(`Tried to start box frenzy in invalid channel ${channelID}`); } - let bank = new Bank(); + const bank = new Bank(); for (let i = 0; i < quantity; i++) { bank.add(Items.random().id, 1); } diff --git a/src/lib/boxSpawns.ts b/src/lib/boxSpawns.ts index e73c444cb4e..8822503d860 100644 --- a/src/lib/boxSpawns.ts +++ b/src/lib/boxSpawns.ts @@ -1,7 +1,6 @@ import { formatOrdinal } from '@oldschoolgg/toolkit'; -import { EmbedBuilder, Message, User } from 'discord.js'; -import { isFunction, randArrItem, shuffleArr, Time } from 'e'; -import he from 'he'; +import { EmbedBuilder, type Message, type User } from 'discord.js'; +import { Time, isFunction, randArrItem, shuffleArr } from 'e'; import fetch from 'node-fetch'; import { Bank, Items, LootTable, Monsters } from 'oldschooljs'; @@ -26,7 +25,7 @@ const triviaChallenge: Challenge = async (msg: Message): Promise => const embed = new EmbedBuilder() .setTitle('Reply with the answer for a reward!') - .setDescription(`${he.decode(question)}\n\nPossible answers: ${allAnswers.join(', ')}`) + .setDescription(`${question}\n\nPossible answers: ${allAnswers.join(', ')}`) .setThumbnail( 'https://cdn.discordapp.com/attachments/357422607982919680/1100378550189707314/534px-Mystery_box_detail.png' ); @@ -99,8 +98,8 @@ const createdChallenge: Challenge = async (msg: Message): Promise = isFunction(randomCreatable.inputItems) ? "This shouldn't be possible..." : randomCreatable.inputItems instanceof Bank - ? randomCreatable.inputItems - : new Bank(randomCreatable.inputItems) + ? randomCreatable.inputItems + : new Bank(randomCreatable.inputItems) }` ) .setThumbnail( @@ -141,7 +140,7 @@ const monsters = [...Object.values(BSOMonsters), ...killableMonsters] .filter(m => m.allItems.length >= 3); const monsterDropChallenge: Challenge = async (msg: Message): Promise => { - let monster = randArrItem(monsters); + const monster = randArrItem(monsters); const items = shuffleArr(monster.allItems).slice(0, 3); const validMonsters = monsters.filter(mon => items.every(t => mon.allItems.includes(t))); @@ -260,7 +259,7 @@ export async function boxSpawnHandler(msg: Message) { }, { main_server_challenges_won: true } ); - let wonStr = `This is your ${formatOrdinal(newStats.main_server_challenges_won)} challenge win!`; + const wonStr = `This is your ${formatOrdinal(newStats.main_server_challenges_won)} challenge win!`; const loot = roll(20) ? LampTable.roll() : MysteryBoxes.roll(); await winnerUser.addItemsToBank({ items: loot, collectionLog: true }); diff --git a/src/lib/bso/calcBossFood.ts b/src/lib/bso/calcBossFood.ts index 153e71f5f23..c6568b1e7ec 100644 --- a/src/lib/bso/calcBossFood.ts +++ b/src/lib/bso/calcBossFood.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import calculateMonsterFood from '../minions/functions/calculateMonsterFood'; -import { KillableMonster } from '../minions/types'; +import type { KillableMonster } from '../minions/types'; export async function calcBossFood(user: MUser, monster: KillableMonster, teamSize: number, quantity: number) { let [healAmountNeeded] = calculateMonsterFood(monster, user); diff --git a/src/lib/bso/calculateCompCapeProgress.ts b/src/lib/bso/calculateCompCapeProgress.ts index 687d2bd709c..71d5fd3569f 100644 --- a/src/lib/bso/calculateCompCapeProgress.ts +++ b/src/lib/bso/calculateCompCapeProgress.ts @@ -2,6 +2,7 @@ import { calcWhatPercent } from 'e'; import { userStatsUpdate } from '../../mahoji/mahojiSettings'; import { compCapeCategories, compCapeTrimmedRequirements } from '../compCape'; +import { Requirements } from '../structures/Requirements'; export async function calculateCompCapeProgress(user: MUser) { let finalStr = ''; @@ -10,8 +11,9 @@ export async function calculateCompCapeProgress(user: MUser) { let totalCompletedTrimmed = 0; let totalCompletedUntrimmed = 0; + const data = await Requirements.fetchRequiredData(user); for (const cat of compCapeCategories) { - const progress = await cat.requirements.check(user); + const progress = cat.requirements.check(data); let subStr = `${cat.name} (Finished ${progress.metRequirements}/${ progress.totalRequirements diff --git a/src/lib/bso/divination.ts b/src/lib/bso/divination.ts index 1842162807b..2535457efec 100644 --- a/src/lib/bso/divination.ts +++ b/src/lib/bso/divination.ts @@ -1,9 +1,8 @@ -import { Portent } from '@prisma/client'; +import type { Portent } from '@prisma/client'; import { Bank, LootTable } from 'oldschooljs'; -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; import { BitField } from '../constants'; -import { prisma } from '../settings/prisma'; import { hasUnlockedAtlantis } from '../util'; import getOSItem from '../util/getOSItem'; import itemID from '../util/itemID'; @@ -253,9 +252,9 @@ for (const energy of divinationEnergies) { export const allDivinationEnergyTypes = divinationEnergies.map(e => e.type); export enum MemoryHarvestType { - ConvertToXP, - ConvertToEnergy, - ConvertWithEnergyToXP + ConvertToXP = 0, + ConvertToEnergy = 1, + ConvertWithEnergyToXP = 2 } export const memoryHarvestTypes = [ { id: MemoryHarvestType.ConvertToXP, name: 'Convert to XP (Default)' }, @@ -352,9 +351,9 @@ export const portents: SourcePortent[] = [ description: 'Consumes stone spirits to grant extra mining XP, instead of extra ore.', divinationLevelToCreate: 90, cost: new Bank().add('Incandescent energy', 1200), - chargesPerPortent: 1000, + chargesPerPortent: 60 * 10, addChargeMessage: portent => - `You used a Spiritual mining portent, your next ${portent.charges_remaining}x stone spirits will grant XP instead of ore.` + `You used a Spiritual mining portent, it will turn stone spirits into extra mining XP, instead of ore, in your next ${portent.charges_remaining} minutes of mining.` }, { id: PortentID.PacifistPortent, @@ -374,7 +373,7 @@ export async function getAllPortentCharges(user: MUser) { user_id: user.id } }); - let result: Record = {}; + const result: Record = {}; for (const portent of portents) { result[portent.id] = 0; } diff --git a/src/lib/bso/divineDominion.ts b/src/lib/bso/divineDominion.ts index c573ad683ca..cd0330d1c14 100644 --- a/src/lib/bso/divineDominion.ts +++ b/src/lib/bso/divineDominion.ts @@ -1,4 +1,4 @@ -import { roll, Time } from 'e'; +import { Time, roll } from 'e'; import { Bank, Monsters } from 'oldschooljs'; import { userStatsBankUpdate } from '../../mahoji/mahojiSettings'; @@ -277,4 +277,4 @@ ${await divineDominionCheck(user)}`; return response; } -export const allGodlyItems = gods.map(g => g.godItems).flat(); +export const allGodlyItems = gods.flatMap(g => g.godItems); diff --git a/src/lib/bso/doa/doaLootTable.ts b/src/lib/bso/doa/doaLootTable.ts new file mode 100644 index 00000000000..ccb9d0a3f55 --- /dev/null +++ b/src/lib/bso/doa/doaLootTable.ts @@ -0,0 +1,42 @@ +import { LootTable } from 'oldschooljs'; + +import { DragonTable } from '../../simulation/grandmasterClue'; +import { StoneSpiritTable, runeAlchablesTable } from '../../simulation/sharedTables'; + +const DragonFletchingTable = new LootTable() + .add('Dragon arrowtips', [10, 20]) + .add('Dragon dart tip', [10, 20]) + .add('Dragon javelin heads', [10, 20]); + +const RareGemRockTable = new LootTable() + .add('Uncut sapphire', 1, 9) + .add('Uncut emerald', 1, 5) + .add('Uncut ruby', 1, 5) + .add('Uncut diamond', 1, 4); + +const RareOreTable = new LootTable() + .add('Mithril ore', [10, 70], 55) + .add('Adamantite ore', [15, 50], 55) + .add('Runite ore', [1, 20], 45) + .add('Amethyst', [1, 15], 45); + +const BaseNonUniqueTable = new LootTable() + .add(RareGemRockTable, [80, 120], undefined, { multiply: true }) + .add(DragonFletchingTable, [15, 20], undefined, { multiply: true }) + .add(runeAlchablesTable, [25, 30], undefined, { multiply: true }) + .add(RareOreTable, [11, 15], undefined, { multiply: true }) + .add(DragonTable, [11, 18], undefined, { multiply: true }) + .add(StoneSpiritTable, [30, 60], undefined, { multiply: true }); + +const DOAClueTable = new LootTable() + .add('Clue scroll (medium)', 1, 5) + .add('Clue scroll (hard)', 1, 4) + .add('Clue scroll (elite)', 1, 3) + .add('Clue scroll (master)', 1, 2) + .add('Clue scroll (grandmaster)', 1, 2); + +export const DOANonUniqueTable = new LootTable() + .tertiary(100, 'Oceanic dye') + .oneIn(40, 'Shark tooth') + .every(DOAClueTable, 2, { multiply: true }) + .every(BaseNonUniqueTable, 3, { multiply: true }); diff --git a/src/lib/bso/doa/doaStartCommand.ts b/src/lib/bso/doa/doaStartCommand.ts new file mode 100644 index 00000000000..fbc219aaebd --- /dev/null +++ b/src/lib/bso/doa/doaStartCommand.ts @@ -0,0 +1,200 @@ +import { type CommandResponse, channelIsSendable, formatDuration } from '@oldschoolgg/toolkit'; +import { Time, clamp } from 'e'; +import { Bank } from 'oldschooljs'; + +import { mahojiParseNumber, userStatsBankUpdate } from '../../../mahoji/mahojiSettings'; +import { Emoji } from '../../constants'; +import { degradeItem } from '../../degradeableItems'; +import { + calcDOAInput, + calculateUserGearPercents, + checkDOATeam, + checkDOAUser, + createDOATeam +} from '../../depthsOfAtlantis'; +import { trackLoot } from '../../lootTrack'; +import { setupParty } from '../../party'; +import type { MakePartyOptions } from '../../types'; +import type { DOAOptions } from '../../types/minions'; +import { bankToStrShortNames } from '../../util'; +import addSubTaskToActivityTask from '../../util/addSubTaskToActivityTask'; +import { calcMaxTripLength } from '../../util/calcMaxTripLength'; +import getOSItem from '../../util/getOSItem'; +import { updateBankSetting } from '../../util/updateBankSetting'; + +export async function doaStartCommand( + user: MUser, + challengeMode: boolean, + solo: boolean, + channelID: string, + teamSize: number | undefined, + quantityInput: number | undefined +): CommandResponse { + if (user.minionIsBusy) { + return `${user.usernameOrMention} minion is busy`; + } + + const initialCheck = await checkDOAUser({ + user, + challengeMode, + duration: Time.Hour, + quantity: 1 + }); + if (typeof initialCheck === 'string') { + return initialCheck; + } + + if (user.minionIsBusy) { + return "Your minion is busy, so you can't start a raid."; + } + + const maxSize = mahojiParseNumber({ input: teamSize, min: 2, max: 8 }) ?? 8; + + const partyOptions: MakePartyOptions = { + leader: user, + minSize: 1, + maxSize, + ironmanAllowed: true, + message: `${user.usernameOrMention} is hosting a Depths of Atlantis mass! Use the buttons below to join/leave.`, + customDenier: async checkUser => { + const checkResult = await checkDOAUser({ + user: checkUser, + challengeMode, + duration: Time.Hour, + quantity: 1 + }); + if (typeof checkResult === 'string') { + return [true, checkResult]; + } + + return [false]; + } + }; + + const channel = globalClient.channels.cache.get(channelID); + if (!channelIsSendable(channel)) return 'No channel found.'; + + let usersWhoConfirmed = []; + try { + usersWhoConfirmed = solo ? [user] : await setupParty(channel, user, partyOptions); + } catch (err: any) { + return { + content: typeof err === 'string' ? err : 'Your mass failed to start.', + ephemeral: true + }; + } + const users = usersWhoConfirmed.filter(u => !u.minionIsBusy).slice(0, maxSize); + + const teamCheck = await checkDOATeam(users, challengeMode, 1); + if (typeof teamCheck === 'string') { + return { + content: `Your mass failed to start because of this reason: ${teamCheck} ${users}`, + allowedMentions: { + users: users.map(i => i.id) + } + }; + } + + const baseDuration = createDOATeam({ + team: teamCheck, + quantity: 1, + challengeMode + }).fakeDuration; + const maxTripLength = Math.max(...users.map(i => calcMaxTripLength(i, 'DepthsOfAtlantis'))); + const maxQuantity = clamp(Math.floor(maxTripLength / baseDuration), 1, 5); + const quantity = clamp(quantityInput ?? maxQuantity, 1, maxQuantity); + + const createdDOATeam = createDOATeam({ + team: teamCheck, + quantity, + challengeMode + }); + + let debugStr = ''; + + const totalCost = new Bank(); + + const costResult = await Promise.all( + users.map(async u => { + const { cost, sangCharges, voidStaffCharges, tumShadowCharges } = await calcDOAInput({ + user: u, + duration: createdDOATeam.fakeDuration, + quantity, + challengeMode + }); + const { realCost } = await u.specialRemoveItems(cost.clone()); + + await degradeItem({ + item: getOSItem('Sanguinesti staff'), + chargesToDegrade: sangCharges, + user: u + }); + + if (voidStaffCharges) { + await degradeItem({ + item: getOSItem('Void staff'), + chargesToDegrade: voidStaffCharges, + user: u + }); + } else if (tumShadowCharges) { + await degradeItem({ + item: getOSItem("Tumeken's shadow"), + chargesToDegrade: tumShadowCharges, + user: u + }); + } else { + throw new Error('No staff equipped'); + } + await userStatsBankUpdate(u.id, 'doa_cost', realCost); + const effectiveCost = realCost.clone(); + totalCost.add(effectiveCost); + + const { total } = calculateUserGearPercents(u.gear); + + const gearMarker = users.length > 5 ? 'Gear: ' : Emoji.Gear; + const boostsMarker = users.length > 5 ? 'Boosts: ' : Emoji.CombatSword; + debugStr += `**- ${u.usernameOrMention}** ${gearMarker}${total.toFixed( + 1 + )}% ${boostsMarker} used ${bankToStrShortNames(realCost)}\n\n`; + return { + userID: u.id, + effectiveCost + }; + }) + ); + + await updateBankSetting('doa_cost', totalCost); + await trackLoot({ + totalCost, + id: 'depths_of_atlantis', + type: 'Minigame', + changeType: 'cost', + users: costResult.map(i => ({ + id: i.userID, + cost: i.effectiveCost, + duration: createdDOATeam.realDuration + })) + }); + await addSubTaskToActivityTask({ + userID: user.id, + channelID: channelID.toString(), + duration: createdDOATeam.realDuration, + type: 'DepthsOfAtlantis', + leader: user.id, + users: users.map(i => i.id), + fakeDuration: createdDOATeam.fakeDuration, + quantity, + cm: challengeMode, + raids: createdDOATeam.raids + }); + + let str = `${partyOptions.leader.usernameOrMention}'s party (${users + .map(u => u.usernameOrMention) + .join(', ')}) is now off to do ${quantity === 1 ? 'a' : `${quantity}x`} ${ + challengeMode ? 'Challenge Mode' : '' + } Depths of Atlantis raid - the total trip will take ${formatDuration(createdDOATeam.fakeDuration)}.`; + + str += ` \n\n${debugStr}`; + + return str; +} diff --git a/src/lib/bso/guthixianCache.ts b/src/lib/bso/guthixianCache.ts index 051b406a116..32b9dfc488c 100644 --- a/src/lib/bso/guthixianCache.ts +++ b/src/lib/bso/guthixianCache.ts @@ -1,8 +1,7 @@ +import { formatDuration, getInterval } from '@oldschoolgg/toolkit'; import { Time } from 'e'; -import { prisma } from '../settings/prisma'; -import { MinigameActivityTaskOptionsWithNoChanges } from '../types/minions'; -import { formatDuration, getInterval } from '../util'; +import type { MinigameActivityTaskOptionsWithNoChanges } from '../types/minions'; import addSubTaskToActivityTask from '../util/addSubTaskToActivityTask'; export const getGuthixianCacheInterval = () => getInterval(24); @@ -47,7 +46,7 @@ export async function joinGuthixianCache(user: MUser, channelID: string) { minigameID: 'guthixian_cache' }); - let response = `${ + const response = `${ user.minionName } is now participating in the current Guthixian Cache, it'll take around ${formatDuration( task.duration diff --git a/src/lib/bso/naxxus/rollNaxxusLoot.ts b/src/lib/bso/naxxus/rollNaxxusLoot.ts new file mode 100644 index 00000000000..10a97c382fd --- /dev/null +++ b/src/lib/bso/naxxus/rollNaxxusLoot.ts @@ -0,0 +1,34 @@ +import { roll } from 'e'; +import { Bank, LootTable } from 'oldschooljs'; + +import { NaxxusLootTable } from '../../minions/data/killableMonsters/custom/bosses/Naxxus'; + +export function rollNaxxusLoot(quantity = 1, cl?: Bank) { + const loot = new Bank(); + loot.add(NaxxusLootTable.roll(quantity)); + + // Handle uniques => Don't give duplicates until log full + const uniqueChance = 150; + // Add new uniques to a dummy CL to support multiple uniques per trip. + const tempClWithNewUniques = cl ? cl.clone() : new Bank(); + for (let i = 0; i < quantity; i++) { + if (roll(uniqueChance)) { + const uniques = [ + { name: 'Dark crystal', weight: 2 }, + { name: 'Abyssal gem', weight: 3 }, + { name: 'Tattered tome', weight: 2 }, + { name: 'Spellbound ring', weight: 3 } + ]; + + const filteredUniques = uniques.filter(u => !tempClWithNewUniques.has(u.name)); + const uniqueTable = filteredUniques.length === 0 ? uniques : filteredUniques; + const lootTable = new LootTable(); + uniqueTable.map(u => lootTable.add(u.name, 1, u.weight)); + + const unique = lootTable.roll(); + tempClWithNewUniques.add(unique); + loot.add(unique); + } + } + return loot; +} diff --git a/src/lib/bso/turaelsTrials.ts b/src/lib/bso/turaelsTrials.ts index 6e1ca261803..ce23b183cf0 100644 --- a/src/lib/bso/turaelsTrials.ts +++ b/src/lib/bso/turaelsTrials.ts @@ -1,13 +1,13 @@ -import { increaseNumByPercent, Time } from 'e'; +import { Time, increaseNumByPercent } from 'e'; import { Bank } from 'oldschooljs'; +import { formatDuration } from '@oldschoolgg/toolkit'; import { trackClientBankStats, userStatsBankUpdate } from '../../mahoji/mahojiSettings'; import { degradeChargeBank } from '../degradeableItems'; import { ChargeBank } from '../structures/Banks'; -import { TuraelsTrialsOptions } from '../types/minions'; +import type { TuraelsTrialsOptions } from '../types/minions'; import addSubTaskToActivityTask from '../util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../util/calcMaxTripLength'; -import { formatDuration } from '../util/smallUtils'; export const TuraelsTrialsMethods = ['melee', 'mage', 'range'] as const; export type TuraelsTrialsMethod = (typeof TuraelsTrialsMethods)[number]; @@ -21,8 +21,8 @@ export function calculateTuraelsTrialsInput({ isUsingBloodFury: boolean; method: TuraelsTrialsMethod; }) { - let timePerKill = Time.Minute * 2.9; - let quantity = Math.floor(maxTripLength / timePerKill); + const timePerKill = Time.Minute * 2.9; + const quantity = Math.floor(maxTripLength / timePerKill); const duration = Math.floor(timePerKill * quantity); const minutesRoundedUp = Math.ceil(duration / Time.Minute); @@ -123,7 +123,7 @@ export async function turaelsTrialsStartCommand(user: MUser, channelID: string, m: method }); - let response = `${user.minionName} is now participating in Turaels Trials, it'll take around ${formatDuration( + const response = `${user.minionName} is now participating in Turaels Trials, it'll take around ${formatDuration( task.duration )} to finish. diff --git a/src/lib/bsoOpenables.ts b/src/lib/bsoOpenables.ts index 5d85ae9f4d6..02c522a66dc 100644 --- a/src/lib/bsoOpenables.ts +++ b/src/lib/bsoOpenables.ts @@ -13,11 +13,11 @@ import { theatreOfBloodNormalUniques, toaCL } from './data/CollectionsExport'; -import { baseHolidayItems, PartyhatTable } from './data/holidayItems'; -import { allTrophyItems } from './data/trophies'; +import { PartyhatTable, baseHolidayItems } from './data/holidayItems'; +import { allTrophyItems } from './data/itemAliases'; import { keyCrates } from './keyCrates'; import { FishTable } from './minions/data/killableMonsters/custom/SeaKraken'; -import { UnifiedOpenable } from './openables'; +import type { UnifiedOpenable } from './openables'; import { PaintBoxTable } from './paintColors'; import { ChimplingImpling, EternalImpling, InfernalImpling, MysteryImpling } from './simulation/customImplings'; import { RuneTable } from './simulation/seedTable'; @@ -1065,7 +1065,7 @@ function randomEquippable(): number { } function findMysteryBoxItem(table: number[]): number { - let result = randArrItem(table); + const result = randArrItem(table); if (cantBeDropped.includes(result)) return findMysteryBoxItem(table); if (result >= 40_000 && result <= 50_000) return findMysteryBoxItem(table); return result; @@ -1090,7 +1090,7 @@ export function getMysteryBoxItem( ): Bank { const mrEDroprate = clAdjustedDroprate(user, 'Mr. E', MR_E_DROPRATE_FROM_UMB_AND_TMB, 1.2); const table = tradeables ? tmbTable : umbTable; - let loot = new Bank(); + const loot = new Bank(); const elligibleLeaguesRewards = leaguesUnlockedMysteryBoxItems .filter(i => totalLeaguesPoints >= i.unlockedAt) diff --git a/src/lib/busyCounterCache.ts b/src/lib/busyCounterCache.ts index 0c15b44fc5a..f48785e313f 100644 --- a/src/lib/busyCounterCache.ts +++ b/src/lib/busyCounterCache.ts @@ -16,7 +16,7 @@ export function baseModifyBusyCounter(map: Map, userID: string, return newCounter; } -export function getBusyCounter(userID: string) { +function getBusyCounter(userID: string) { return globalClient.busyCounterCache.get(userID) ?? 0; } export function modifyBusyCounter(userID: string, amount: -1 | 1) { diff --git a/src/lib/clues/clueReqs.ts b/src/lib/clues/clueReqs.ts index 94e49e244be..d62fe239418 100644 --- a/src/lib/clues/clueReqs.ts +++ b/src/lib/clues/clueReqs.ts @@ -1,10 +1,10 @@ -import { Bank } from 'oldschooljs'; +import type { Bank } from 'oldschooljs'; import type { Skills } from '../types'; -import { ClueTier } from './clueTiers'; +import type { ClueTier } from './clueTiers'; import { allStashUnitTiers } from './stashUnits'; -export interface ClueReq { +interface ClueReq { itemCost?: Bank; skillReqs?: Partial; stashUnitID?: number; diff --git a/src/lib/clues/clueTiers.ts b/src/lib/clues/clueTiers.ts index 292cc2bed81..e268e16d1b8 100644 --- a/src/lib/clues/clueTiers.ts +++ b/src/lib/clues/clueTiers.ts @@ -1,26 +1,26 @@ import { Time } from 'e'; import { Clues } from 'oldschooljs'; -import { BeginnerCasket, BeginnerClueTable } from 'oldschooljs/dist/simulation/clues/Beginner'; -import { EasyCasket, EasyClueTable } from 'oldschooljs/dist/simulation/clues/Easy'; -import { EliteCasket, EliteClueTable } from 'oldschooljs/dist/simulation/clues/Elite'; -import { HardCasket, HardClueTable } from 'oldschooljs/dist/simulation/clues/Hard'; -import { MasterCasket, MasterClueTable } from 'oldschooljs/dist/simulation/clues/Master'; -import { MediumCasket, MediumClueTable } from 'oldschooljs/dist/simulation/clues/Medium'; +import type { BeginnerCasket } from 'oldschooljs/dist/simulation/clues/Beginner'; +import { BeginnerClueTable } from 'oldschooljs/dist/simulation/clues/Beginner'; +import type { EasyCasket } from 'oldschooljs/dist/simulation/clues/Easy'; +import { EasyClueTable } from 'oldschooljs/dist/simulation/clues/Easy'; +import type { EliteCasket } from 'oldschooljs/dist/simulation/clues/Elite'; +import { EliteClueTable } from 'oldschooljs/dist/simulation/clues/Elite'; +import type { HardCasket } from 'oldschooljs/dist/simulation/clues/Hard'; +import { HardClueTable } from 'oldschooljs/dist/simulation/clues/Hard'; +import type { MasterCasket } from 'oldschooljs/dist/simulation/clues/Master'; +import { MasterClueTable } from 'oldschooljs/dist/simulation/clues/Master'; +import type { MediumCasket } from 'oldschooljs/dist/simulation/clues/Medium'; +import { MediumClueTable } from 'oldschooljs/dist/simulation/clues/Medium'; -import { ElderClue, ElderClueTable } from '../simulation/elderClue'; +import { type ElderClue, ElderClueTable } from '../simulation/elderClue'; import { GrandmasterClueTable } from '../simulation/grandmasterClue'; import itemID from '../util/itemID'; import resolveItems from '../util/resolveItems'; -import { beginnerReqs, ClueReqs } from './clueReqs'; -import { - beginnerStashes, - easyStashes, - eliteStashes, - hardStashes, - masterStashes, - mediumStashes, - StashUnitTier -} from './stashUnits'; +import type { ClueReqs } from './clueReqs'; +import { beginnerReqs } from './clueReqs'; +import type { StashUnitTier } from './stashUnits'; +import { beginnerStashes, easyStashes, eliteStashes, hardStashes, masterStashes, mediumStashes } from './stashUnits'; const { Beginner, Easy, Medium, Hard, Elite, Master } = Clues; diff --git a/src/lib/clues/clueUtils.ts b/src/lib/clues/clueUtils.ts index b4fce9ffd0a..1ce9e08c3d2 100644 --- a/src/lib/clues/clueUtils.ts +++ b/src/lib/clues/clueUtils.ts @@ -1,5 +1,5 @@ import { ButtonBuilder, ButtonStyle } from 'discord.js'; -import { Bank } from 'oldschooljs'; +import type { Bank } from 'oldschooljs'; import { BitField } from '../constants'; import { ClueTiers } from './clueTiers'; diff --git a/src/lib/clues/stashUnits.ts b/src/lib/clues/stashUnits.ts index f092bd418c0..be502db3c0d 100644 --- a/src/lib/clues/stashUnits.ts +++ b/src/lib/clues/stashUnits.ts @@ -1,5 +1,7 @@ import { Bank } from 'oldschooljs'; +import { allTeamCapes } from 'oldschooljs/dist/data/itemConstants'; +import { deepResolveItems, resolveItems } from 'oldschooljs/dist/util/util'; import { barrowsItemArr, boaters, @@ -12,9 +14,7 @@ import { runeHeraldicShields, stoles } from '../data/CollectionsExport'; -import { allTeamCapes } from '../data/misc'; -import resolveItems, { deepResolveItems } from '../util/resolveItems'; -import { ClueTier } from './clueTiers'; +import type { ClueTier } from './clueTiers'; export interface IStashUnit { id: number; @@ -612,4 +612,4 @@ export const allStashUnitTiers = [ masterStashes ]; -export const allStashUnitsFlat = allStashUnitTiers.map(i => i.units).flat(); +export const allStashUnitsFlat = allStashUnitTiers.flatMap(i => i.units); diff --git a/src/lib/collectionLogTask.ts b/src/lib/collectionLogTask.ts index 9f47d250ad3..23c18c65eff 100644 --- a/src/lib/collectionLogTask.ts +++ b/src/lib/collectionLogTask.ts @@ -1,14 +1,17 @@ -import { Canvas, SKRSContext2D } from '@napi-rs/canvas'; +import type { SKRSContext2D } from '@napi-rs/canvas'; +import { Canvas } from '@napi-rs/canvas'; import { formatItemStackQuantity, generateHexColorForCashStack } from '@oldschoolgg/toolkit'; +import type { CommandResponse } from '@oldschoolgg/toolkit'; import { calcWhatPercent, objectEntries } from 'e'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; -import { Bank, Util } from 'oldschooljs'; +import type { Bank } from 'oldschooljs'; +import { Util } from 'oldschooljs'; -import { allCollectionLogs, getCollection, getTotalCl, UserStatsDataNeededForCL } from '../lib/data/Collections'; -import { IToReturnCollection } from '../lib/data/CollectionsExport'; +import { allCollectionLogs, getCollection, getTotalCl } from '../lib/data/Collections'; +import type { IToReturnCollection } from '../lib/data/CollectionsExport'; import { fillTextXTimesInCtx, getClippedRegion, measureTextWidth } from '../lib/util/canvasUtil'; import getOSItem from '../lib/util/getOSItem'; -import { IBgSprite } from './bankImage'; +import type { IBgSprite } from './bankImage'; +import type { MUserStats } from './structures/MUserStats'; export const collectionLogTypes = [ { name: 'collection', description: 'Normal Collection Log' }, @@ -31,15 +34,7 @@ class CollectionLogTask { return bankImageGenerator.drawBorder(ctx, sprite); } - drawSquare( - ctx: SKRSContext2D, - x: number, - y: number, - w: number, - h: number, - pixelSize: number = 1, - hollow: boolean = true - ) { + drawSquare(ctx: SKRSContext2D, x: number, y: number, w: number, h: number, pixelSize = 1, hollow = true) { ctx.save(); if (hollow) { ctx.translate(0.5, 0.5); @@ -106,8 +101,8 @@ class CollectionLogTask { user: MUser; collection: string; type: CollectionLogType; - flags: { [key: string]: string | number }; - stats: UserStatsDataNeededForCL | null; + flags: { [key: string]: string | number | undefined }; + stats: MUserStats | null; collectionLog?: IToReturnCollection; }): Promise { const { sprite } = bankImageGenerator.getBgAndSprite(options.user.user.bankBackground, options.user); @@ -119,7 +114,7 @@ class CollectionLogTask { options.type = 'tame'; } - let { collection, type, user, flags } = options; + const { collection, type, user, flags } = options; let collectionLog: IToReturnCollection | undefined | false = undefined; @@ -150,7 +145,7 @@ class CollectionLogTask { attachment: Buffer.from( collectionLog.collection .map(i => { - let _i = getOSItem(i); + const _i = getOSItem(i); const _q = (collectionLog as IToReturnCollection).userItems.amount(_i.id); if (_q === 0 && !flags.missing) return undefined; return `${flags.nq || flags.missing ? '' : `${_q}x `}${_i.name}`; @@ -166,7 +161,7 @@ class CollectionLogTask { // Disable tall flag when not showing left list if (flags.nl && flags.tall) { - delete flags.tall; + flags.tall = undefined; } const userCollectionBank = collectionLog.userItems; @@ -205,8 +200,6 @@ class CollectionLogTask { ) ); - debugLog('Generating a CL image', { collection, ...flags, type, user_id: user.id }); - // Create base canvas const canvas = new Canvas(canvasWidth, canvasHeight); // Get the canvas context @@ -241,10 +234,10 @@ class CollectionLogTask { type === 'sacrifice' ? 'Sacrifice' : type === 'collection' - ? 'Collection' - : type === 'tame' - ? 'Tame Collection' - : 'Bank' + ? 'Collection' + : type === 'tame' + ? 'Tame Collection' + : 'Bank' } Log - ${userTotalCl[1].toLocaleString()}/${userTotalCl[0].toLocaleString()} / ${calcWhatPercent( userTotalCl[1], userTotalCl[0] @@ -374,7 +367,7 @@ class CollectionLogTask { ctx.fillStyle = '#FF981F'; this.drawText(ctx, (drawnSoFar = collectionLog.isActivity ? 'Completions: ' : 'Kills: '), 0, 25); let pixelLevel = 25; - for (let [type, value] of objectEntries(collectionLog.completions)) { + for (const [type, value] of objectEntries(collectionLog.completions)) { if ( measureTextWidth(ctx, drawnSoFar) + measureTextWidth(ctx, ` / ${type}: `) + @@ -413,12 +406,12 @@ class CollectionLogTask { ctx.save(); ctx.font = '16px OSRSFontCompact'; ctx.fillStyle = generateHexColorForCashStack(totalPrice); - let value = Util.toKMB(totalPrice); + const value = Util.toKMB(totalPrice); this.drawText(ctx, value, ctx.canvas.width - 15 - ctx.measureText(value).width, 75 + 25); ctx.restore(); if (leftListCanvas && !fullSize) { - if (!Boolean(flags.tall)) { + if (!flags.tall) { let selectedPos = 8; const listItemSize = 15; for (const name of Object.keys(collectionLog.leftList!)) { diff --git a/src/lib/colosseum.ts b/src/lib/colosseum.ts new file mode 100644 index 00000000000..18cd4c14579 --- /dev/null +++ b/src/lib/colosseum.ts @@ -0,0 +1,686 @@ +import { exponentialPercentScale, formatDuration, mentionCommand } from '@oldschoolgg/toolkit'; +import { UserError } from '@oldschoolgg/toolkit'; +import { GeneralBank, type GeneralBankType } from '@oldschoolgg/toolkit'; +import { + Time, + calcPercentOfNum, + calcWhatPercent, + clamp, + increaseNumByPercent, + objectEntries, + objectValues, + percentChance, + randInt, + reduceNumByPercent, + sumArr +} from 'e'; +import { Bank, LootTable } from 'oldschooljs'; +import type { EquipmentSlot } from 'oldschooljs/dist/meta/types'; + +import { userStatsBankUpdate } from '../mahoji/mahojiSettings'; +import { degradeChargeBank } from './degradeableItems'; +import type { GearSetupType } from './gear/types'; +import { trackLoot } from './lootTrack'; +import { QuestID } from './minions/data/quests'; +import { ChargeBank } from './structures/Bank'; +import type { ItemBank, Skills } from './types'; +import type { ColoTaskOptions } from './types/minions'; +import addSubTaskToActivityTask from './util/addSubTaskToActivityTask'; +import resolveItems from './util/resolveItems'; +import { formatSkillRequirements, itemNameFromID } from './util/smallUtils'; +import { updateBankSetting } from './util/updateBankSetting'; + +function combinedChance(percentages: number[]): number { + const failureProbabilities = percentages.map(p => (100 - p) / 100); + const combinedFailureProbability = failureProbabilities.reduce((acc, prob) => acc * prob, 1); + const combinedSuccessProbability = 1 - combinedFailureProbability; + return combinedSuccessProbability * 100; +} + +interface Wave { + waveNumber: number; + enemies: string[]; + reinforcements?: string[]; + table: LootTable; +} + +export const colosseumWaves: Wave[] = [ + { + waveNumber: 1, + enemies: ['Fremennik Warband', 'Serpent Shaman'], + reinforcements: ['Jaguar Warrior'], + table: new LootTable().every('Sunfire splinters', 80) + }, + { + waveNumber: 2, + enemies: ['Fremennik Warband', 'Serpent Shaman', 'Javelin Colossus'], + reinforcements: ['Jaguar Warrior'], + table: new LootTable() + .add('Rune platebody', 1) + .add('Death rune', 150) + .add('Chaos rune', 150) + .add('Sunfire splinters', 150) + .add('Cannonball', 80) + .add('Rune kiteshield', 4) + .add('Rune chainbody', 1) + }, + { + waveNumber: 3, + enemies: ['Fremennik Warband', 'Serpent Shaman', 'Javelin Colossus'], + reinforcements: ['Jaguar Warrior'], + table: new LootTable() + .add('Rune platebody', 1, 9) + .add('Death rune', 150, 9) + .add('Chaos rune', 150, 9) + .add('Sunfire splinters', 150, 9) + .add('Cannonball', 80, 9) + .add('Rune kiteshield', 4, 9) + .add('Rune chainbody', 1, 9) + .add('Onyx bolts', 30) + .add('Snapdragon seed', 1) + .add('Dragon platelegs', 1) + .add('Sunfire splinters', 500) + .add('Earth orb', 80) + .add('Rune 2h sword', 2) + .add('Gold ore', 150) + }, + { + waveNumber: 4, + enemies: ['Fremennik Warband', 'Serpent Shaman', 'Manticore'], + reinforcements: ['Jaguar Warrior', 'Serpent Shaman'], + table: new LootTable() + .add('Rune platebody', 1, 5535) + .add('Death rune', 150, 5535) + .add('Chaos rune', 150, 5535) + .add('Sunfire splinters', 150, 5535) + .add('Cannonball', 80, 5535) + .add('Rune kiteshield', 4, 5535) + .add('Rune chainbody', 1, 5535) + .add('Onyx bolts', 30, 615) + .add('Snapdragon seed', 1, 615) + .add('Dragon platelegs', 1, 615) + .add('Sunfire splinters', 500, 615) + .add('Earth orb', 80, 615) + .add('Rune 2h sword', 2, 615) + .add('Gold ore', 150, 615) + .add('Echo crystal', 1, 126) + .add('Sunfire fanatic helm', 1, 70) + .add('Sunfire fanatic cuirass', 1, 70) + .add('Sunfire fanatic chausses', 1, 70) + .add('Echo crystal', 2, 14) + .add('Echo crystal', 3, 14) + }, + { + waveNumber: 5, + enemies: ['Fremennik Warband', 'Serpent Shaman', 'Javelin Colossus', 'Manticore'], + reinforcements: ['Jaguar Warrior', 'Serpent Shaman'], + table: new LootTable() + .add('Onyx bolts', 30, 2725) + .add('Snapdragon seed', 1, 2725) + .add('Dragon platelegs', 1, 2725) + .add('Sunfire splinters', 500, 2725) + .add('Earth orb', 80, 2725) + .add('Rune 2h sword', 2, 2725) + .add('Gold ore', 150, 2725) + .add('Echo crystal', 1, 63) + .add('Sunfire fanatic helm', 1, 35) + .add('Sunfire fanatic cuirass', 1, 35) + .add('Sunfire fanatic chausses', 1, 35) + .add('Echo crystal', 2, 7) + .add('Echo crystal', 3, 7) + }, + { + waveNumber: 6, + enemies: ['Fremennik Warband', 'Serpent Shaman', 'Javelin Colossus', 'Manticore'], + reinforcements: ['Jaguar Warrior', 'Serpent Shaman'], + table: new LootTable() + .add('Onyx bolts', 30, 2375) + .add('Snapdragon seed', 1, 2375) + .add('Dragon platelegs', 1, 2375) + .add('Sunfire splinters', 500, 2375) + .add('Earth orb', 80, 2375) + .add('Rune 2h sword', 2, 2375) + .add('Gold ore', 150, 2375) + .add('Echo crystal', 1, 63) + .add('Sunfire fanatic helm', 1, 35) + .add('Sunfire fanatic cuirass', 1, 35) + .add('Sunfire fanatic chausses', 1, 35) + .add('Echo crystal', [2, 3], 7) + }, + { + waveNumber: 7, + enemies: ['Fremennik Warband', 'Javelin Colossus', 'Manticore', 'Shockwave Colossus'], + reinforcements: ['Minotaur'], + table: new LootTable() + .add('Onyx bolts', 30, 5832) + .add('Snapdragon seed', 1, 5832) + .add('Dragon platelegs', 1, 5832) + .add('Sunfire splinters', 500, 5832) + .add('Earth orb', 80, 5832) + .add('Rune 2h sword', 2, 5832) + .add('Gold ore', 150, 5832) + .add('Dragon bolts (unf)', 200, 756) + .add('Ranarr seed', 4, 756) + .add('Sunfire splinters', 1100, 756) + .add('Dragon arrowtips', 150, 756) + .add('Adamantite ore', 100, 756) + .add('Death rune', 250, 756) + .add('Echo crystal', 1, 189) + .add('Sunfire fanatic helm', 1, 105) + .add('Sunfire fanatic cuirass', 1, 105) + .add('Sunfire fanatic chausses', 1, 105) + .add('Tonalztics of ralos (uncharged)', 1, 35) + .add('Echo crystal', [2, 3], 21) + }, + { + waveNumber: 8, + enemies: ['Fremennik Warband', 'Javelin Colossus', 'Manticore', 'Shockwave Colossus'], + reinforcements: ['Minotaur'], + table: new LootTable() + .add('Onyx bolts', 30, 14_472) + .add('Snapdragon seed', 1, 14_472) + .add('Dragon platelegs', 1, 14_472) + .add('Sunfire splinters', 500, 14_472) + .add('Earth orb', 80, 14_472) + .add('Rune 2h sword', 2, 14_472) + .add('Gold ore', 150, 14_472) + .add('Onyx bolts', 50, 1876) + .add('Dragon platelegs', 2, 1876) + .add('Sunfire splinters', 1750, 1876) + .add('Rune kiteshield', 5, 1876) + .add('Runite ore', 12, 1876) + .add('Death rune', 300, 1876) + .add('Echo crystal', 1, 567) + .add('Sunfire fanatic helm', 1, 315) + .add('Sunfire fanatic cuirass', 1, 315) + .add('Sunfire fanatic chausses', 1, 315) + .add('Tonalztics of ralos (uncharged)', 1, 105) + .add('Echo crystal', [2, 3], 63) + }, + { + waveNumber: 9, + enemies: ['Fremennik Warband', 'Javelin Colossus', 'Manticore'], + reinforcements: ['Minotaur'], + table: new LootTable() + .add('Dragon bolts (unf)', 200, 3180) + .add('Ranarr seed', 4, 3180) + .add('Sunfire splinters', 1100, 3180) + .add('Dragon arrowtips', 150, 3180) + .add('Adamantite ore', 100, 3180) + .add('Death rune', 250, 3180) + .add('Onyx bolts', 75, 424) + .add('Sunfire splinters', 2500, 424) + .add('Dragon platelegs', 3, 424) + .add('Death rune', 300, 424) + .add('Rune warhammer', 5, 424) + .add('Echo crystal', 1, 135) + .add('Sunfire fanatic helm', 1, 75) + .add('Sunfire fanatic cuirass', 1, 75) + .add('Sunfire fanatic chausses', 1, 75) + .add('Tonalztics of ralos (uncharged)', 1, 25) + .add('Echo crystal', [2, 3], 15) + }, + { + waveNumber: 10, + enemies: ['Fremennik Warband', 'Javelin Colossus', 'Manticore'], + reinforcements: ['Minotaur', 'Serpent Shaman'], + table: new LootTable() + .add('Onyx bolts', 50, 2340) + .add('Dragon platelegs', 2, 2340) + .add('Sunfire splinters', 1750, 2340) + .add('Rune kiteshield', 5, 2340) + .add('Runite ore', 12, 2340) + .add('Death rune', 300, 2340) + .add('Onyx bolts', 100, 312) + .add('Rune warhammer', 8, 312) + .add('Dragon platelegs', 3, 312) + .add('Sunfire splinters', 3500, 312) + .add('Dragon arrowtips', 250, 312) + .add('Echo crystal', 1, 135) + .add('Sunfire fanatic helm', 1, 75) + .add('Sunfire fanatic cuirass', 1, 75) + .add('Sunfire fanatic chausses', 1, 75) + .add('Tonalztics of ralos (uncharged)', 1, 25) + .add('Echo crystal', [2, 3], 15) + }, + { + waveNumber: 11, + enemies: ['Fremennik Warband', 'Javelin Colossus', 'Manticore', 'Shockwave Colossus'], + reinforcements: ['Minotaur', 'Serpent Shaman'], + table: new LootTable() + .add('Onyx bolts', 75, 360) + .add('Sunfire splinters', 2500, 360) + .add('Dragon platelegs', 3, 360) + .add('Death rune', 300, 360) + .add('Rune warhammer', 5, 360) + .add('Cannonball', 2000, 50) + .add('Dragon plateskirt', 5, 50) + .add('Dragon arrowtips', 350, 50) + .add('Onyx bolts', 150, 50) + .add('Echo crystal', 1, 27) + .add('Sunfire fanatic helm', 1, 15) + .add('Sunfire fanatic cuirass', 1, 15) + .add('Sunfire fanatic chausses', 1, 15) + .add('Tonalztics of ralos (uncharged)', 1, 5) + .add('Echo crystal', [2, 3], 3) + }, + { + waveNumber: 12, + enemies: ['Sol Heredit'], + table: new LootTable() + .every("Dizana's quiver (uncharged)") + .tertiary(200, 'Smol heredit') + .add('Onyx bolts', 100, 792) + .add('Rune warhammer', 8, 792) + .add('Dragon platelegs', 3, 792) + .add('Sunfire splinters', 3500, 792) + .add('Dragon arrowtips', 250, 792) + .add('Echo crystal', 1, 135) + .add('Uncut onyx', 1, 110) + .add('Dragon plateskirt', 5, 110) + .add('Rune 2h sword', 9, 110) + .add('Runite ore', 35, 110) + .add('Sunfire fanatic helm', 1, 75) + .add('Sunfire fanatic cuirass', 1, 75) + .add('Sunfire fanatic chausses', 1, 75) + .add('Tonalztics of ralos (uncharged)', 1, 25) + .add('Echo crystal', [2, 3], 15) + } +]; + +function calculateDeathChance(waveKC: number, hasBF: boolean, hasSGS: boolean): number { + const cappedKc = Math.min(Math.max(waveKC, 0), 150); + const baseChance = 80; + const kcReduction = 80 * (1 - Math.exp(-1.5 * cappedKc)); + + let newChance = baseChance - kcReduction; + + if (hasSGS) { + newChance = reduceNumByPercent(newChance, 5); + } + if (hasBF) { + newChance = reduceNumByPercent(newChance, 5); + } + + return clamp(newChance, 1, 80); +} + +export class ColosseumWaveBank extends GeneralBank { + constructor(initialBank?: GeneralBankType) { + super({ + initialBank, + allowedKeys: colosseumWaves.map(i => i.waveNumber), + valueSchema: { floats: true, min: 0, max: 999_999 } + }); + } +} + +function calculateTimeInMs(waveTwelveKC: number): number { + const points: { kc: number; timeInMinutes: number }[] = [ + { kc: 0, timeInMinutes: 60 }, + { kc: 1, timeInMinutes: 45 }, + { kc: 1, timeInMinutes: 35 }, + { kc: 5, timeInMinutes: 30 }, + { kc: 10, timeInMinutes: 25 }, + { kc: 50, timeInMinutes: 19 }, + { kc: 100, timeInMinutes: 16 }, + { kc: 300, timeInMinutes: 15 } + ]; + + if (waveTwelveKC <= points[0].kc) return points[0].timeInMinutes * 60 * 1000; + if (waveTwelveKC >= points[points.length - 1].kc) return points[points.length - 1].timeInMinutes * 60 * 1000; + + for (let i = 0; i < points.length - 1; i++) { + const point1 = points[i]; + const point2 = points[i + 1]; + if (waveTwelveKC >= point1.kc && waveTwelveKC <= point2.kc) { + const slope = (point2.timeInMinutes - point1.timeInMinutes) / (point2.kc - point1.kc); + return (point1.timeInMinutes + slope * (waveTwelveKC - point1.kc)) * 60 * 1000; + } + } + + return 0; +} + +function calculateGlory(kcBank: ColosseumWaveBank, wave: Wave) { + const waveKCSkillBank = new ColosseumWaveBank(); + for (const [waveNumber, kc] of kcBank.entries()) { + waveKCSkillBank.add(waveNumber, clamp(calcWhatPercent(kc, 30 - waveNumber), 1, 100)); + } + const kcSkill = waveKCSkillBank.amount(wave.waveNumber) ?? 0; + const totalKCSkillPercent = sumArr(waveKCSkillBank.entries().map(ent => ent[1])) / waveKCSkillBank.length(); + const expSkill = exponentialPercentScale(totalKCSkillPercent); + const maxPossibleGlory = 60_000; + const ourMaxGlory = calcPercentOfNum(expSkill, maxPossibleGlory); + const wavePerformance = exponentialPercentScale((totalKCSkillPercent + kcSkill) / 2); + const glory = randInt(calcPercentOfNum(wavePerformance, ourMaxGlory), ourMaxGlory); + return glory; +} + +interface ColosseumResult { + diedAt: number | null; + loot: Bank | null; + maxGlory: number; + addedWaveKCBank: ColosseumWaveBank; + fakeDuration: number; + realDuration: number; + totalDeathChance: number; + deathChances: number[]; + scytheCharges: number; + venatorBowCharges: number; + bloodFuryCharges: number; +} + +export const startColosseumRun = (options: { + kcBank: ColosseumWaveBank; + hasScythe: boolean; + hasTBow: boolean; + hasVenBow: boolean; + hasBF: boolean; + hasClaws: boolean; + hasSGS: boolean; + hasTorture: boolean; + scytheCharges: number; + venatorBowCharges: number; + bloodFuryCharges: number; +}): ColosseumResult => { + const waveTwelveKC = options.kcBank.amount(12); + + const bank = new Bank(); + + const addedWaveKCBank = new ColosseumWaveBank(); + let waveDuration = calculateTimeInMs(waveTwelveKC) / 12; + + if (!options.hasScythe) { + waveDuration = increaseNumByPercent(waveDuration, 10); + } + if (!options.hasTBow) { + waveDuration = increaseNumByPercent(waveDuration, 10); + } + if (!options.hasVenBow) { + waveDuration = increaseNumByPercent(waveDuration, 7); + } + if (!options.hasClaws) { + waveDuration = increaseNumByPercent(waveDuration, 4); + } + if (!options.hasTorture) { + waveDuration = increaseNumByPercent(waveDuration, 5); + } + + const fakeDuration = 12 * waveDuration; + const deathChances: number[] = []; + let realDuration = 0; + let maxGlory = 0; + + // Calculate charges used + const scytheCharges = 300; + const calculateVenCharges = () => 50; + + for (const wave of colosseumWaves) { + realDuration += waveDuration; + const kcForThisWave = options.kcBank.amount(wave.waveNumber); + maxGlory = Math.max(calculateGlory(options.kcBank, wave), maxGlory); + const deathChance = calculateDeathChance(kcForThisWave, options.hasBF, options.hasSGS); + deathChances.push(deathChance); + + if (percentChance(deathChance)) { + return { + diedAt: wave.waveNumber, + loot: null, + maxGlory: 0, + addedWaveKCBank, + fakeDuration, + realDuration, + totalDeathChance: combinedChance(deathChances), + deathChances, + scytheCharges: options.hasScythe ? scytheCharges : 0, + venatorBowCharges: options.hasVenBow ? calculateVenCharges() : 0, + bloodFuryCharges: options.hasBF ? scytheCharges * 3 : 0 + }; + } + addedWaveKCBank.add(wave.waveNumber); + bank.add(wave.table.roll()); + if (wave.waveNumber === 12) { + return { + diedAt: null, + loot: bank, + maxGlory, + addedWaveKCBank, + fakeDuration, + realDuration, + totalDeathChance: combinedChance(deathChances), + deathChances, + + scytheCharges: options.hasScythe ? scytheCharges : 0, + venatorBowCharges: options.hasVenBow ? calculateVenCharges() : 0, + bloodFuryCharges: options.hasBF ? scytheCharges * 3 : 0 + }; + } + } + + throw new Error('Colosseum run did not end correctly.'); +}; + +export async function colosseumCommand(user: MUser, channelID: string) { + if (user.minionIsBusy) { + return `${user.usernameOrMention} is busy`; + } + + if (!user.user.finished_quest_ids.includes(QuestID.ChildrenOfTheSun)) { + return `You need to complete the "Children of the Sun" quest before you can enter the Colosseum. Send your minion to do the quest using: ${mentionCommand( + globalClient, + 'activities', + 'quest' + )}.`; + } + + const skillReqs: Skills = { + attack: 90, + strength: 90, + defence: 90, + prayer: 80, + ranged: 90, + magic: 94, + hitpoints: 90 + }; + + if (!user.hasSkillReqs(skillReqs)) { + return `You need ${formatSkillRequirements(skillReqs)} to enter the Colosseum.`; + } + + const requiredItems: Partial>>> = { + melee: { + head: resolveItems(['Torva full helm', 'Neitiznot faceguard', 'Justiciar faceguard']), + cape: resolveItems(['Infernal cape', 'Fire cape']), + neck: resolveItems(['Amulet of blood fury', 'Amulet of torture']), + body: resolveItems(['Torva platebody', 'Bandos chestplate']), + legs: resolveItems(['Torva platelegs', 'Bandos tassets']), + feet: resolveItems(['Primordial boots']), + ring: resolveItems(['Ultor ring', 'Berserker ring (i)']) + }, + range: { + cape: resolveItems(["Dizana's quiver", "Ava's assembler"]), + head: resolveItems(['Masori mask (f)', 'Masori mask', 'Armadyl helmet']), + neck: resolveItems(['Necklace of anguish']), + body: resolveItems(['Masori body (f)', 'Masori body', 'Armadyl chestplate']), + legs: resolveItems(['Masori chaps (f)', 'Masori chaps', 'Armadyl chainskirt']), + feet: resolveItems(['Pegasian boots']), + ring: resolveItems(['Venator ring', 'Archers ring (i)']) + } + }; + + const meleeWeapons = resolveItems(['Scythe of vitur', 'Blade of saeldor (c)']); + const rangeWeapons = resolveItems(['Twisted bow', 'Bow of faerdhinen (c)']); + + for (const [gearType, gearNeeded] of objectEntries(requiredItems)) { + const gear = user.gear[gearType]; + if (!gearNeeded) continue; + for (const items of objectValues(gearNeeded)) { + if (!items) continue; + if (!items.some(g => gear.hasEquipped(g))) { + return `You need one of these equipped in your ${gearType} setup to enter the Colosseum: ${items + .map(itemNameFromID) + .join(', ')}.`; + } + } + } + + if (!meleeWeapons.some(i => user.gear.melee.hasEquipped(i, true, true))) { + return `You need one of these equipped in your melee setup to enter the Colosseum: ${meleeWeapons + .map(itemNameFromID) + .join(', ')}.`; + } + + if (!rangeWeapons.some(i => user.gear.range.hasEquipped(i, true, true))) { + return `You need one of these equipped in your range setup to enter the Colosseum: ${rangeWeapons + .map(itemNameFromID) + .join(', ')}.`; + } + + const messages: string[] = []; + + const hasBF = user.gear.melee.hasEquipped('Amulet of blood fury', true, false); + const hasScythe = user.gear.melee.hasEquipped('Scythe of vitur', true, true); + const hasTBow = user.gear.range.hasEquipped('Twisted bow', true, true); + function calculateVenCharges() { + return 50; + } + const hasVenBow = user.hasEquippedOrInBank('Venator bow') && user.user.venator_bow_charges >= calculateVenCharges(); + const hasClaws = user.hasEquippedOrInBank('Dragon claws'); + const hasSGS = user.hasEquippedOrInBank('Saradomin godsword'); + const hasTorture = !hasBF && user.gear.melee.hasEquipped('Amulet of torture'); + const scytheCharges = 300; + const bloodFuryCharges = scytheCharges * 3; + const venatorBowCharges = calculateVenCharges(); + + const res = startColosseumRun({ + kcBank: new ColosseumWaveBank((await user.fetchStats({ colo_kc_bank: true })).colo_kc_bank as ItemBank), + hasScythe, + hasTBow, + hasVenBow, + hasBF, + hasClaws, + hasSGS, + hasTorture, + scytheCharges, + venatorBowCharges, + bloodFuryCharges + }); + const minutes = res.realDuration / Time.Minute; + + const chargeBank = new ChargeBank(); + const cost = new Bank().add('Saradomin brew(4)', 6).add('Super restore(4)', 8).add('Super combat potion(4)'); + + if (user.bank.has('Ranging potion(4)')) { + cost.add('Ranging potion(4)'); + } else if (user.bank.has('Bastion potion(4)')) { + cost.add('Bastion potion(4)'); + } else { + return 'You need to have a Ranging potion(4) or Bastion potion(4) in your bank.'; + } + + if (hasScythe) { + messages.push('10% boost for Scythe'); + chargeBank.add('scythe_of_vitur_charges', scytheCharges); + } else { + messages.push('Missed 10% Scythe boost. If you have one, charge it and equip to melee.'); + } + if (hasTBow) { + messages.push('10% boost for TBow'); + const arrowsNeeded = Math.ceil(minutes * 3); + cost.add('Dragon arrow', arrowsNeeded); + } else { + messages.push( + 'Missed 10% TBow boost. If you have one, equip it to range. You also need dragon arrows equipped.' + ); + } + if (hasVenBow) { + messages.push('7% boost for Venator bow'); + chargeBank.add('venator_bow_charges', calculateVenCharges()); + cost.add('Dragon arrow', 50); + } else { + messages.push( + 'Missed 7% Venator bow boost. If you have one, charge it and keep it in your bank. You also need at least 50 dragon arrows equipped.' + ); + } + + if (hasClaws) { + messages.push('4% boost for Dragon claws'); + } else { + messages.push('Missed 4% Dragon claws boost.'); + } + + if (hasTorture) { + messages.push('5% boost for Torture'); + } else { + messages.push('Missed 5% Torture boost.'); + } + + if (user.gear.melee.hasEquipped('Amulet of blood fury')) { + chargeBank.add('blood_fury_charges', bloodFuryCharges); + messages.push('-5% death chance for blood fury'); + } else { + messages.push('Missed -5% death chance for blood fury. If you have one, add charges and equip it to melee.'); + } + + if (hasSGS) { + messages.push('-5% death chance for Saradomin godsword'); + } else { + messages.push('Missed -5% death chance boost for Saradomin godsword.'); + } + + const realCost = new Bank(); + try { + const result = await user.specialRemoveItems(cost); + realCost.add(result.realCost); + } catch (err: any) { + if (err instanceof UserError) { + return err.message; + } + throw err; + } + messages.push(`Removed ${realCost}`); + + await updateBankSetting('colo_cost', realCost); + await userStatsBankUpdate(user, 'colo_cost', realCost); + await trackLoot({ + totalCost: realCost, + id: 'colo', + type: 'Minigame', + changeType: 'cost', + users: [ + { + id: user.id, + cost: realCost + } + ] + }); + + if (chargeBank.length() > 0) { + const hasChargesResult = user.hasCharges(chargeBank); + if (!hasChargesResult.hasCharges) { + return hasChargesResult.fullUserString!; + } + + const degradeResults = await degradeChargeBank(user, chargeBank); + messages.push(degradeResults.map(i => i.userMessage).join(', ')); + } + + await addSubTaskToActivityTask({ + userID: user.id, + channelID, + duration: res.realDuration, + type: 'Colosseum', + fakeDuration: res.fakeDuration, + maxGlory: res.maxGlory, + diedAt: res.diedAt ?? undefined, + loot: res.loot?.bank, + scytheCharges: res.scytheCharges, + venatorBowCharges: res.venatorBowCharges, + bloodFuryCharges: res.bloodFuryCharges + }); + + return `${user.minionName} is now attempting the Colosseum. They will finish in around ${formatDuration( + res.fakeDuration + )}, unless they die early. ${messages.join(', ')}`; +} diff --git a/src/lib/combat_achievements/caUtils.ts b/src/lib/combat_achievements/caUtils.ts index f5092311fb7..fee5ce5feb7 100644 --- a/src/lib/combat_achievements/caUtils.ts +++ b/src/lib/combat_achievements/caUtils.ts @@ -1,10 +1,10 @@ -import { CAViewType } from '../../mahoji/commands/ca'; +import type { CAViewType } from '../../mahoji/commands/ca'; import type { ActivityTaskData, MonsterActivityTaskOptions } from '../types/minions'; -import { CombatAchievement } from './combatAchievements'; +import type { CombatAchievement } from './combatAchievements'; export function isCertainMonsterTrip(monsterID: number) { return (data: ActivityTaskData) => - data.type === 'MonsterKilling' && (data as MonsterActivityTaskOptions).monsterID === monsterID; + data.type === 'MonsterKilling' && (data as MonsterActivityTaskOptions).mi === monsterID; } interface CombatAchievementGroup { diff --git a/src/lib/combat_achievements/combatAchievements.ts b/src/lib/combat_achievements/combatAchievements.ts index 0565bd60285..5aef785fb0c 100644 --- a/src/lib/combat_achievements/combatAchievements.ts +++ b/src/lib/combat_achievements/combatAchievements.ts @@ -1,12 +1,12 @@ -import { activity_type_enum } from '@prisma/client'; +import type { activity_type_enum } from '@prisma/client'; import { deepClone, notEmpty, roll, sumArr } from 'e'; -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; -import { Requirements } from '../structures/Requirements'; -import { ActivityTaskData, TOAOptions } from '../types/minions'; +import type { Requirements } from '../structures/Requirements'; +import type { ActivityTaskData, TOAOptions } from '../types/minions'; import { assert } from '../util'; import getOSItem from '../util/getOSItem'; -import { TripFinishEffect } from '../util/handleTripFinish'; +import type { TripFinishEffect } from '../util/handleTripFinish'; import { easyCombatAchievements } from './easy'; import { eliteCombatAchievements } from './elite'; import { grandmasterCombatAchievements } from './grandmaster'; @@ -152,21 +152,24 @@ for (const [, val] of entries) { assert(val.tasks.length === val.length, `${val.name} has ${val.tasks.length} tasks, but length is ${val.length}`); } -export const allCombatAchievementTasks = entries.map(i => i[1].tasks).flat(); +export const allCombatAchievementTasks = entries.flatMap(i => i[1].tasks); -const allCATaskIDs = entries.map(i => i[1].tasks.map(t => t.id)).flat(); +const allCATaskIDs = entries.flatMap(i => i[1].tasks.map(t => t.id)); assert(allCATaskIDs.length === new Set(allCATaskIDs).size); assert(sumArr(Object.values(CombatAchievements).map(i => i.length)) === allCATaskIDs.length); -const indexesWithRng = entries.map(i => i[1].tasks.filter(t => 'rng' in t)).flat(); +const indexesWithRng = entries.flatMap(i => i[1].tasks.filter(t => 'rng' in t)); -export const combatAchievementTripEffect: TripFinishEffect['fn'] = async ({ data, messages }) => { +export const combatAchievementTripEffect = async ({ data, messages, user }: Parameters[0]) => { const dataCopy = deepClone(data); if (dataCopy.type === 'Inferno' && !dataCopy.diedPreZuk && !dataCopy.diedZuk) { - (dataCopy as any).quantity = 1; + (dataCopy as any).q = 1; } - if (!('quantity' in dataCopy)) return; - let quantity = Number(dataCopy.quantity); - if (isNaN(quantity)) return; + if (dataCopy.type === 'Colosseum') { + (dataCopy as any).q = 1; + } + if (!('q' in dataCopy)) return; + let quantity = Number(dataCopy.q); + if (Number.isNaN(quantity)) return; if (data.type === 'TombsOfAmascut') { const wipedRoom = (data as TOAOptions).wipedRoom ?? 0; @@ -180,8 +183,8 @@ export const combatAchievementTripEffect: TripFinishEffect['fn'] = async ({ data } } - const users = await Promise.all( - ('users' in data ? (data.users as string[]) : [data.userID]).map(id => mUserFetch(id)) + const users: MUser[] = await Promise.all( + 'users' in data ? (data.users as string[]).map(id => mUserFetch(id)) : [user] ); for (const user of users) { diff --git a/src/lib/combat_achievements/easy.ts b/src/lib/combat_achievements/easy.ts index 2b5cd47f667..ce1f106cfa8 100644 --- a/src/lib/combat_achievements/easy.ts +++ b/src/lib/combat_achievements/easy.ts @@ -1,12 +1,12 @@ import { Monsters } from 'oldschooljs'; +import { resolveItems } from 'oldschooljs/dist/util/util'; import { warmGear } from '../data/filterables'; import { SkillsEnum } from '../skilling/types'; import { Requirements } from '../structures/Requirements'; import getOSItem from '../util/getOSItem'; -import resolveItems from '../util/resolveItems'; import { isCertainMonsterTrip } from './caUtils'; -import { type CombatAchievement } from './combatAchievements'; +import type { CombatAchievement } from './combatAchievements'; export const easyCombatAchievements: CombatAchievement[] = [ { diff --git a/src/lib/combat_achievements/elite.ts b/src/lib/combat_achievements/elite.ts index 441a1da5cdc..7000c5a5dcc 100644 --- a/src/lib/combat_achievements/elite.ts +++ b/src/lib/combat_achievements/elite.ts @@ -1,13 +1,13 @@ import { Monsters } from 'oldschooljs'; -import { demonBaneWeapons, MIMIC_MONSTER_ID, NIGHTMARE_ID, PHOSANI_NIGHTMARE_ID, ZALCANO_ID } from '../constants'; +import { MIMIC_MONSTER_ID, NIGHTMARE_ID, PHOSANI_NIGHTMARE_ID, ZALCANO_ID, demonBaneWeapons } from '../constants'; import { NexMonster } from '../nex'; -import { anyoneDiedInTOARaid } from '../simulation/toa'; import { SkillsEnum } from '../skilling/types'; import { Requirements } from '../structures/Requirements'; -import { GauntletOptions, NightmareActivityTaskOptions, TOAOptions } from '../types/minions'; +import type { ActivityTaskData, GauntletOptions, NightmareActivityTaskOptions, TOAOptions } from '../types/minions'; +import { anyoneDiedInTOARaid } from '../util'; import { isCertainMonsterTrip } from './caUtils'; -import { type CombatAchievement } from './combatAchievements'; +import type { CombatAchievement } from './combatAchievements'; export const eliteCombatAchievements: CombatAchievement[] = [ { @@ -1485,5 +1485,40 @@ export const eliteCombatAchievements: CombatAchievement[] = [ [Monsters.Zulrah.id]: 75 } }) + }, + { + id: 1129, + name: 'I was here first!', + desc: 'Kill a Jaguar Warrior using a Claw-type weapon special attack.', + type: 'mechanical', + monster: 'Colosseum', + rng: { + chancePerKill: 5, + hasChance: 'Colosseum' + } + }, + { + id: 1130, + name: 'Denied', + desc: 'Complete Wave 7 without the Minotaur ever healing other enemies.', + type: 'mechanical', + monster: 'Colosseum', + rng: { + chancePerKill: 12, + hasChance: (data: ActivityTaskData) => + data.type === 'Colosseum' && (!data.diedAt || (Boolean(data.diedAt) && data.diedAt > 7)) + } + }, + { + id: 1131, + name: 'Furball', + desc: 'Complete Wave 4 without taking avoidable damage from a Manticore.', + type: 'perfection', + monster: 'Colosseum', + rng: { + chancePerKill: 12, + hasChance: (data: ActivityTaskData) => + data.type === 'Colosseum' && (!data.diedAt || (Boolean(data.diedAt) && data.diedAt > 4)) + } } ]; diff --git a/src/lib/combat_achievements/grandmaster.ts b/src/lib/combat_achievements/grandmaster.ts index 4e80b29d212..e0a689ed4ee 100644 --- a/src/lib/combat_achievements/grandmaster.ts +++ b/src/lib/combat_achievements/grandmaster.ts @@ -1,18 +1,20 @@ +import { Time } from 'e'; import { Monsters } from 'oldschooljs'; import { PHOSANI_NIGHTMARE_ID } from '../constants'; -import { anyoneDiedInTOARaid } from '../simulation/toa'; import { Requirements } from '../structures/Requirements'; -import { +import type { + ActivityTaskData, GauntletOptions, NexTaskOptions, NightmareActivityTaskOptions, RaidsOptions, - TheatreOfBloodTaskOptions, - TOAOptions + TOAOptions, + TheatreOfBloodTaskOptions } from '../types/minions'; +import { anyoneDiedInTOARaid } from '../util'; import { isCertainMonsterTrip } from './caUtils'; -import { type CombatAchievement } from './combatAchievements'; +import type { CombatAchievement } from './combatAchievements'; export const grandmasterCombatAchievements: CombatAchievement[] = [ { @@ -1026,5 +1028,62 @@ export const grandmasterCombatAchievements: CombatAchievement[] = [ chancePerKill: 110, hasChance: isCertainMonsterTrip(Monsters.Zulrah.id) } + }, + { + id: 3090, + name: 'Colosseum Speed-Runner', + desc: 'Complete the Colosseum with a total time of 24:00 or less.', + type: 'speed', + monster: 'Colosseum', + rng: { + chancePerKill: 1, + hasChance: (data: ActivityTaskData) => + data.type === 'Colosseum' && !data.diedAt && data.duration < Time.Minute * 24 + } + }, + { + id: 3091, + name: 'Slow Dancing in the Sand', + desc: 'Defeat Sol Heredit without running during the fight with him.', + type: 'restriction', + monster: 'Colosseum', + rng: { + chancePerKill: 15, + hasChance: (data: ActivityTaskData) => data.type === 'Colosseum' && !data.diedAt + } + }, + { + id: 3092, + name: 'Reinforcements', + desc: 'Defeat Sol Heredit with "Bees II", "Quartet" and "Solarflare II" modifiers active.', + type: 'mechanical', + monster: 'Colosseum', + rng: { + chancePerKill: 30, + hasChance: (data: ActivityTaskData) => data.type === 'Colosseum' && !data.diedAt + } + }, + { + id: 3093, + name: 'Perfect Footwork', + desc: 'Defeat Sol Heredit without taking any damage from his Spear, Shield, Grapple or Triple Attack.', + type: 'perfection', + monster: 'Colosseum', + rng: { + chancePerKill: 20, + hasChance: (data: ActivityTaskData) => data.type === 'Colosseum' && !data.diedAt + } + }, + { + id: 3094, + name: 'Colosseum Grand Champion', + desc: 'Defeat Sol Heredit 10 times.', + type: 'kill_count', + monster: 'Colosseum', + requirements: new Requirements().add({ + minigames: { + colosseum: 10 + } + }) } ]; diff --git a/src/lib/combat_achievements/hard.ts b/src/lib/combat_achievements/hard.ts index b5d51bfb57c..1522239efb2 100644 --- a/src/lib/combat_achievements/hard.ts +++ b/src/lib/combat_achievements/hard.ts @@ -1,11 +1,11 @@ import { Monsters } from 'oldschooljs'; -import { demonBaneWeapons, NIGHTMARE_ID } from '../constants'; +import { NIGHTMARE_ID, demonBaneWeapons } from '../constants'; import { anglerOutfit } from '../data/CollectionsExport'; import { Requirements } from '../structures/Requirements'; -import { TOAOptions } from '../types/minions'; +import type { TOAOptions } from '../types/minions'; import { isCertainMonsterTrip } from './caUtils'; -import { type CombatAchievement } from './combatAchievements'; +import type { CombatAchievement } from './combatAchievements'; export const hardCombatAchievements: CombatAchievement[] = [ { @@ -597,7 +597,7 @@ export const hardCombatAchievements: CombatAchievement[] = [ monster: 'Tempoross', desc: 'Subdue Tempoross, getting rewarded with 10 reward permits from a single Tempoross fight.', rng: { - chancePerKill: 30, + chancePerKill: 5, hasChance: data => data.type === 'Tempoross' } }, diff --git a/src/lib/combat_achievements/master.ts b/src/lib/combat_achievements/master.ts index fda3775891c..4eaf20b8bb1 100644 --- a/src/lib/combat_achievements/master.ts +++ b/src/lib/combat_achievements/master.ts @@ -1,20 +1,22 @@ +import { Time } from 'e'; import { Monsters } from 'oldschooljs'; +import { resolveItems } from 'oldschooljs/dist/util/util'; import { NIGHTMARE_ID, PHOSANI_NIGHTMARE_ID } from '../constants'; import { NexMonster } from '../nex'; -import { anyoneDiedInTOARaid } from '../simulation/toa'; import { Requirements } from '../structures/Requirements'; -import { +import type { + ActivityTaskData, GauntletOptions, MonsterActivityTaskOptions, NightmareActivityTaskOptions, RaidsOptions, - TheatreOfBloodTaskOptions, - TOAOptions + TOAOptions, + TheatreOfBloodTaskOptions } from '../types/minions'; -import resolveItems from '../util/resolveItems'; +import { anyoneDiedInTOARaid } from '../util'; import { isCertainMonsterTrip } from './caUtils'; -import { type CombatAchievement } from './combatAchievements'; +import type { CombatAchievement } from './combatAchievements'; export const masterCombatAchievements: CombatAchievement[] = [ { @@ -1402,7 +1404,7 @@ export const masterCombatAchievements: CombatAchievement[] = [ rng: { chancePerKill: 33, hasChance: data => - isCertainMonsterTrip(Monsters.Vorkath.id)(data) && (data as MonsterActivityTaskOptions).quantity >= 5 + isCertainMonsterTrip(Monsters.Vorkath.id)(data) && (data as MonsterActivityTaskOptions).q >= 5 } }, { @@ -1461,5 +1463,63 @@ export const masterCombatAchievements: CombatAchievement[] = [ chancePerKill: 75, hasChance: isCertainMonsterTrip(Monsters.Zulrah.id) } + }, + { + id: 2129, + name: 'One-off', + desc: "Complete Wave 11 with either 'Red Flag', 'Dynamic Duo', or 'Doom II' active.", + type: 'mechanical', + monster: 'Colosseum', + rng: { + chancePerKill: 15, + hasChance: (data: ActivityTaskData) => + data.type === 'Colosseum' && (!data.diedAt || (Boolean(data.diedAt) && data.diedAt > 11)) + } + }, + { + id: 2130, + name: 'Showboating', + desc: 'Defeat Sol Heredit after using Fortis Salute to the north, east, south and west of the arena while he is below 10% hitpoints.', + type: 'mechanical', + monster: 'Colosseum', + rng: { + chancePerKill: 15, + hasChance: (data: ActivityTaskData) => data.type === 'Colosseum' && !data.diedAt + } + }, + { + id: 2131, + name: 'I Brought Mine Too', + desc: 'Defeat Sol Heredit using only a Spear, Hasta or Halberd.', + type: 'restriction', + monster: 'Colosseum', + rng: { + chancePerKill: 15, + hasChance: (data: ActivityTaskData) => data.type === 'Colosseum' && !data.diedAt + } + }, + { + id: 2132, + name: 'Sportsmanship', + desc: 'Defeat Sol Heredit once.', + type: 'kill_count', + monster: 'Colosseum', + requirements: new Requirements().add({ + minigames: { + colosseum: 1 + } + }) + }, + { + id: 2133, + name: 'Colosseum Speed-Chaser', + desc: 'Complete the Colosseum with a total time of 28:00 or less.', + type: 'speed', + monster: 'Colosseum', + rng: { + chancePerKill: 1, + hasChance: (data: ActivityTaskData) => + data.type === 'Colosseum' && !data.diedAt && data.duration < Time.Minute * 28 + } } ]; diff --git a/src/lib/combat_achievements/medium.ts b/src/lib/combat_achievements/medium.ts index cbbc56bc37c..30d8255701e 100644 --- a/src/lib/combat_achievements/medium.ts +++ b/src/lib/combat_achievements/medium.ts @@ -3,7 +3,7 @@ import { Monsters } from 'oldschooljs'; import { SkillsEnum } from '../skilling/types'; import { Requirements } from '../structures/Requirements'; import { isCertainMonsterTrip } from './caUtils'; -import { type CombatAchievement } from './combatAchievements'; +import type { CombatAchievement } from './combatAchievements'; export const mediumCombatAchievements: CombatAchievement[] = [ { @@ -431,7 +431,7 @@ export const mediumCombatAchievements: CombatAchievement[] = [ monster: 'Skotizo', desc: 'Kill Skotizo with no altars active.', rng: { - chancePerKill: 15, + chancePerKill: 5, hasChance: isCertainMonsterTrip(Monsters.Skotizo.id) } }, diff --git a/src/lib/compCape.ts b/src/lib/compCape.ts index 6ef5cd8cc66..159cb0b6c77 100644 --- a/src/lib/compCape.ts +++ b/src/lib/compCape.ts @@ -1,10 +1,9 @@ +import { writeFileSync } from 'node:fs'; import { toTitleCase } from '@oldschoolgg/toolkit'; -import { tame_growth, UserStats } from '@prisma/client'; +import { type UserStats, tame_growth } from '@prisma/client'; import { calcWhatPercent, objectEntries, sumArr } from 'e'; -import { writeFileSync } from 'fs'; import { Bank, Items } from 'oldschooljs'; -import { getPOH } from '../mahoji/lib/abstracted_commands/pohCommand'; import { divinationEnergies } from './bso/divination'; import { ClueTiers } from './clues/clueTiers'; import { BitField } from './constants'; @@ -120,10 +119,10 @@ import { stealingCreationCL, templeTrekkingCL, temporossCL, - theatreOfBLoodCL, theGauntletCL, theInfernoCL, theNightmareCL, + theatreOfBLoodCL, thermonuclearSmokeDevilCL, tinkeringWorshopCL, titheFarmCL, @@ -146,14 +145,15 @@ import { creatablesCL } from './data/createables'; import { kibbleCL } from './data/kibble'; import { getSimilarItems } from './data/similarItems'; import { slayerMasksHelmsCL } from './data/slayerMaskHelms'; -import { diariesObject, diaryTiers } from './diaries'; +import { diaries, diariesObject } from './diaries'; import { growablePetsCL } from './growablePets'; import { implingsCL } from './implings'; import { inventionCL } from './invention/inventions'; import { allLeagueTasks, leagueTasks } from './leagues/leagues'; import { BSOMonsters } from './minions/data/killableMonsters/custom/customMonsters'; -import { getPOHObject, PoHObjects } from './poh'; -import { getFarmingInfo } from './skilling/functions/getFarmingInfo'; +import { type DiaryID, type DiaryTierName, diaryTiers } from './minions/types'; +import { PoHObjects, getPOHObject } from './poh'; +import { getFarmingInfoFromUser } from './skilling/functions/getFarmingInfo'; import Skillcapes from './skilling/skillcapes'; import Agility from './skilling/skills/agility'; import { cookingCL } from './skilling/skills/cooking/cooking'; @@ -164,9 +164,9 @@ import { fletchingCL } from './skilling/skills/fletching/fletchables'; import { herbloreCL } from './skilling/skills/herblore/mixables'; import { smithingCL } from './skilling/skills/smithing/smithables'; import { slayerUnlockableRewards } from './slayer/slayerUnlocks'; -import { RequirementFailure, Requirements } from './structures/Requirements'; -import { tameFeedableItems, TameSpeciesID } from './tames'; -import { ItemBank } from './types'; +import { type RequirementFailure, Requirements } from './structures/Requirements'; +import { TameSpeciesID, tameFeedableItems } from './tames'; +import type { ItemBank } from './types'; import { itemID, itemNameFromID } from './util'; import resolveItems from './util/resolveItems'; @@ -338,21 +338,10 @@ const skillingRequirements = new Requirements() .add({ name: 'Complete Implings CL', clRequirement: implingsCL }) .add({ name: 'Grow 5 Spirit trees', - has: async ({ user }) => { - if (user.bitfield.includes(BitField.GrewFiveSpiritTrees)) { - return true; - } - const info = await getFarmingInfo(user.id); + has: ({ user }) => { + const info = getFarmingInfoFromUser(user.user); const hasFive = info.patches.spirit.lastQuantity >= 5; - if (hasFive && !user.bitfield.includes(BitField.GrewFiveSpiritTrees)) { - await user.update({ - bitfield: { - push: BitField.GrewFiveSpiritTrees - } - }); - } - - return hasFive; + return hasFive || user.bitfield.includes(BitField.GrewFiveSpiritTrees); } }); @@ -407,8 +396,7 @@ const cluesRequirements = new Requirements() }) .add({ name: 'Collect/Complete/Open a Grandmaster clue', - has: async ({ user }) => { - const { clueCounts } = await user.calcActualClues(); + has: ({ clueCounts }) => { if (clueCounts.Grandmaster === 0) { return 'You need to Collect/Complete/Open a Grandmaster clue'; } @@ -416,8 +404,7 @@ const cluesRequirements = new Requirements() }) .add({ name: 'Collect/Complete/Open a Elder clue', - has: async ({ user }) => { - const { clueCounts } = await user.calcActualClues(); + has: ({ clueCounts }) => { if (clueCounts.Elder === 0) { return 'You need to Collect/Complete/Open a Elder clue'; } @@ -530,7 +517,7 @@ miscRequirements }) .add({ name: 'Unlock all Slayer unlocks', - has: async ({ user, roboChimpUser }) => { + has: ({ user, roboChimpUser }) => { const hasAll = roboChimpUser.leagues_completed_tasks_ids.includes(4103) || user.user.slayer_unlocks.length >= slayerUnlockableRewards.length || @@ -567,8 +554,7 @@ miscRequirements }) .add({ name: 'Build the highest tier (level requirement) item in every POH Slot', - has: async ({ user }) => { - const poh = await getPOH(user.id); + has: ({ poh }) => { const failures: RequirementFailure[] = []; for (const [key, val] of objectEntries(poh)) { if (key === 'user_id' || key === 'background_id' || key === 'altar') continue; @@ -689,8 +675,7 @@ const tameRequirements = new Requirements() }) .add({ name: 'Obtain, hatch, and fully grow a Monkey Tame', - has: async ({ user }) => { - const tames = await user.getTames(); + has: ({ tames }) => { if (!tames.some(t => t.species.id === TameSpeciesID.Monkey && t.growthStage === tame_growth.adult)) { return 'You need to obtain, hatch, and grow to adult a Monkey Tame.'; } @@ -699,8 +684,7 @@ const tameRequirements = new Requirements() }) .add({ name: 'Obtain, hatch, and fully grow a Igne Tame', - has: async ({ user }) => { - const tames = await user.getTames(); + has: ({ tames }) => { if (!tames.some(t => t.species.id === TameSpeciesID.Igne && t.growthStage === tame_growth.adult)) { return 'You need to obtain, hatch, and grow to adult a Igne Tame.'; } @@ -709,8 +693,7 @@ const tameRequirements = new Requirements() }) .add({ name: 'Feed a Monkey tame all items that provide a boost', - has: async ({ user }) => { - const tames = await user.getTames(); + has: ({ tames }) => { const itemsToBeFed = tameFeedableItems.filter(i => i.tameSpeciesCanBeFedThis.includes(TameSpeciesID.Monkey) ); @@ -731,8 +714,7 @@ const tameRequirements = new Requirements() }) .add({ name: 'Feed a Igne tame all items that provide a boost', - has: async ({ user }) => { - const tames = await user.getTames(); + has: ({ tames }) => { const itemsToBeFed = tameFeedableItems.filter(i => i.tameSpeciesCanBeFedThis.includes(TameSpeciesID.Igne)); const oneTameHasAll = tames @@ -751,9 +733,7 @@ const tameRequirements = new Requirements() }) .add({ name: 'Equip a Igne tame with the BiS items', - has: async ({ user }) => { - const tames = await user.getTames(); - + has: ({ tames }) => { const oneTameHasAll = tames .filter(t => t.species.id === TameSpeciesID.Igne) .some(tame => { @@ -769,10 +749,16 @@ const tameRequirements = new Requirements() }); const diaryRequirements = new Requirements(); -for (const [key, b] of objectEntries(diariesObject)) { +for (const [, b] of objectEntries(diariesObject)) { diaryRequirements.add({ name: `Complete the ${b.name} achievement diary`, - diaryRequirement: diaryTiers.map(i => [key, i]) + diaryRequirement: diaries.flatMap(i => { + const res: [DiaryID, DiaryTierName][] = []; + for (const a of diaryTiers) { + res.push([i.id, a]); + } + return res; + }) }); } diaryRequirements.add({ name: 'Complete Achievement Diary CL', clRequirement: diariesCL }); @@ -784,7 +770,7 @@ export const compCapeTrimmedRequirements = new Requirements() }) .add({ name: 'Complete all Leagues tasks', - has: async ({ roboChimpUser }) => { + has: ({ roboChimpUser }) => { const hasAll = roboChimpUser.leagues_completed_tasks_ids.length === allLeagueTasks.length && allLeagueTasks.every(t => roboChimpUser.leagues_completed_tasks_ids.includes(t.id)); @@ -854,13 +840,12 @@ export const compCapeCategories = [ const allCLItemsCheckedFor = compCapeCategories .map(i => i.requirements.requirements) .flat(2) - .map(req => { + .flatMap(req => { if ('clRequirement' in req) { return Array.isArray(req.clRequirement) ? req.clRequirement : req.clRequirement.items().map(i => i[0].id); } return []; - }) - .flat(); + }); const overallItemsNotCheckedFor = Items.array() .map(i => i.id) diff --git a/src/lib/constants.ts b/src/lib/constants.ts index fbb7c5e3db8..1ea31a6f879 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -1,23 +1,16 @@ +import { execSync } from 'node:child_process'; import path from 'node:path'; - -import { Image } from '@napi-rs/canvas'; -import { StoreBitfield } from '@oldschoolgg/toolkit'; -import { Prisma } from '@prisma/client'; -import { execSync } from 'child_process'; -import { - APIButtonComponent, - APIInteractionDataResolvedChannel, - APIRole, - ButtonBuilder, - ButtonStyle, - ComponentType -} from 'discord.js'; +import { isMainThread } from 'node:worker_threads'; +import type { Image } from '@napi-rs/canvas'; +import { PerkTier, SimpleTable, StoreBitfield, dateFm } from '@oldschoolgg/toolkit'; +import type { CommandOptions } from '@oldschoolgg/toolkit'; +import type { Prisma } from '@prisma/client'; +import type { APIButtonComponent, APIInteractionDataResolvedChannel, APIRole } from 'discord.js'; +import { ButtonBuilder, ButtonStyle, ComponentType } from 'discord.js'; import * as dotenv from 'dotenv'; import { Time } from 'e'; -import { CommandOptions } from 'mahoji/dist/lib/types'; -import { all, create } from 'mathjs'; import { Items } from 'oldschooljs'; -import { convertLVLtoXP } from 'oldschooljs/dist/util/util'; +import { convertLVLtoXP, getItemOrThrow } from 'oldschooljs/dist/util/util'; import { z } from 'zod'; import { DISCORD_SETTINGS, production } from '../config'; @@ -25,15 +18,16 @@ import type { AbstractCommand } from '../mahoji/lib/inhibitors'; import { customItems } from './customItems/util'; import { SkillsEnum } from './skilling/types'; import type { ActivityTaskData } from './types/minions'; -import getOSItem from './util/getOSItem'; import resolveItems from './util/resolveItems'; -import { dateFm } from './util/smallUtils'; -export const BotID = DISCORD_SETTINGS.BotID ?? '729244028989603850'; +import '../lib/data/itemAliases'; + +export { PerkTier }; const TestingMainChannelID = DISCORD_SETTINGS.Channels?.TestingMain ?? '944924763405574174'; export const BOT_TYPE: 'BSO' | 'OSB' = 'BSO' as 'BSO' | 'OSB'; +export const BOT_TYPE_LOWERCASE: 'bso' | 'osb' = BOT_TYPE.toLowerCase() as 'bso' | 'osb'; export const Channel = { General: DISCORD_SETTINGS.Channels?.General ?? '342983479501389826', @@ -55,8 +49,8 @@ export const Channel = { ? '346304390858145792' : '1154056119019393035' : production - ? '792691343284764693' - : '1154056119019393035', + ? '792691343284764693' + : '1154056119019393035', // BSO Channels BSOGeneral: DISCORD_SETTINGS.Channels?.BSOGeneral ?? '792691343284764693', BSOChannel: DISCORD_SETTINGS.Channels?.BSOChannel ?? '732207379818479756', @@ -82,10 +76,13 @@ export const Roles = { TopSlayer: DISCORD_SETTINGS.Roles?.TopSlayer ?? '867967551819358219', TopInventor: '992799099801833582', TopLeagues: '1005417171112972349', - EventOrganizer: '1149907536749801542' + EventOrganizer: '1149907536749801542', + TopTamer: '1054356709222666240', + TopMysterious: '1074592096968785960', + TopGlobalCL: '848966773885763586' }; -export const enum DefaultPingableRoles { +export enum DefaultPingableRoles { // Tester roles: Tester = '682052620809928718', BSOTester = '829368646182371419', @@ -93,7 +90,7 @@ export const enum DefaultPingableRoles { BSOMass = '759573020464906242' } -export const enum Emoji { +export enum Emoji { MoneyBag = '<:MoneyBag:493286312854683654>', OSBot = '<:OSBot:601768469905801226>', Joy = '😂', @@ -213,7 +210,7 @@ export enum ActivityGroup { Minigame = 'Minigame' } -export const enum Events { +export enum Events { Error = 'error', Log = 'log', Verbose = 'verbose', @@ -226,37 +223,6 @@ export const enum Events { export const COINS_ID = 995; -export const enum PerkTier { - /** - * Boosters - */ - One = 1, - /** - * Tier 1 Patron - */ - Two = 2, - /** - * Tier 2 Patron, Contributors, Mods - */ - Three = 3, - /** - * Tier 3 Patron - */ - Four = 4, - /** - * Tier 4 Patron - */ - Five = 5, - /** - * Tier 5 Patron - */ - Six = 6, - /** - * Tier 6 Patron - */ - Seven = 7 -} - export enum BitField { IsPatronTier1 = 2, IsPatronTier2 = 3, @@ -264,7 +230,6 @@ export enum BitField { IsPatronTier4 = 5, IsPatronTier5 = 6, isModerator = 7, - isContributor = 8, BypassAgeRestriction = 9, HasHosidiusWallkit = 10, HasPermanentEventBackgrounds = 11, @@ -275,7 +240,6 @@ export enum BitField { HasDexScroll = 16, HasArcaneScroll = 17, HasTornPrayerScroll = 18, - IsWikiContributor = 19, HasSlepeyTablet = 20, IsPatronTier6 = 21, DisableBirdhouseRunButton = 22, @@ -345,9 +309,7 @@ interface BitFieldData { } export const BitFieldData: Record = { - [BitField.IsWikiContributor]: { name: 'Wiki Contributor', protected: true, userConfigurable: false }, [BitField.isModerator]: { name: 'Moderator', protected: true, userConfigurable: false }, - [BitField.isContributor]: { name: 'Contributor', protected: true, userConfigurable: false }, [BitField.HasPermanentTierOne]: { name: 'Permanent Tier 1', protected: false, userConfigurable: false }, [BitField.HasPermanentSpawnLamp]: { name: 'Permanent Spawn Lamp', protected: false, userConfigurable: false }, @@ -568,15 +530,6 @@ export const BitFieldData: Record = { } } as const; -export const enum PatronTierID { - One = '4608201', - Two = '4608226', - Three = '4720356', - Four = '5262065', - Five = '5262216', - Six = '8091554' -} - export const BadgesEnum = { Developer: 0, Booster: 1, @@ -590,7 +543,9 @@ export const BadgesEnum = { TopSkiller: 9, TopCollector: 10, TopMinigame: 11, - SotWTrophy: 12 + SotWTrophy: 12, + Slayer: 13, + TopGiveawayer: 14 } as const; export const badges: { [key: number]: string } = { @@ -606,7 +561,9 @@ export const badges: { [key: number]: string } = { [BadgesEnum.TopSkiller]: Emoji.Skiller, [BadgesEnum.TopCollector]: Emoji.CollectionLog, [BadgesEnum.TopMinigame]: Emoji.MinigameIcon, - [BadgesEnum.SotWTrophy]: Emoji.SOTW + [BadgesEnum.SotWTrophy]: Emoji.SOTWTrophy, + [BadgesEnum.Slayer]: Emoji.Slayer, + [BadgesEnum.TopGiveawayer]: Emoji.SantaHat }; export const MAX_XP = 5_000_000_000; @@ -668,45 +625,6 @@ export const mahojiInformationalButtons: APIButtonComponent[] = buttonSource.map export const PATRON_ONLY_GEAR_SETUP = 'Sorry - but the `other` gear setup is only available for Tier 3 Patrons (and higher) to use.'; -export const scaryEatables = [ - { - item: getOSItem('Candy teeth'), - healAmount: 3 - }, - { - item: getOSItem('Toffeet'), - healAmount: 5 - }, - { - item: getOSItem('Chocolified skull'), - healAmount: 8 - }, - { - item: getOSItem('Rotten sweets'), - healAmount: 9 - }, - { - item: getOSItem('Hairyfloss'), - healAmount: 12 - }, - { - item: getOSItem('Eyescream'), - healAmount: 13 - }, - { - item: getOSItem('Goblinfinger soup'), - healAmount: 20 - }, - { - item: getOSItem("Benny's brain brew"), - healAmount: 50 - }, - { - item: getOSItem('Roasted newt'), - healAmount: 120 - } -]; - export const projectiles = { arrow: { items: resolveItems(['Adamant arrow', 'Rune arrow', 'Amethyst arrow', 'Dragon arrow', 'Hellfire arrow']), @@ -740,8 +658,7 @@ export const projectiles = { export type ProjectileType = keyof typeof projectiles; export const PHOSANI_NIGHTMARE_ID = 9416; - -export const COMMANDS_TO_NOT_TRACK = [['minion', ['k', 'kill', 'clue', 'info']]]; +const COMMANDS_TO_NOT_TRACK = [['minion', ['k', 'kill', 'clue', 'info']]]; export function shouldTrackCommand(command: AbstractCommand, args: CommandOptions) { if (!Array.isArray(args)) return true; for (const [name, subs] of COMMANDS_TO_NOT_TRACK) { @@ -753,7 +670,7 @@ export function shouldTrackCommand(command: AbstractCommand, args: CommandOption } function compressMahojiArgs(options: CommandOptions) { - let newOptions: Record = {}; + const newOptions: Record = {}; for (const [key, val] of Object.entries(options) as [ keyof CommandOptions, CommandOptions[keyof CommandOptions] @@ -809,8 +726,6 @@ export const TWITCHERS_GLOVES = ['egg', 'ring', 'seed', 'clue'] as const; export type TwitcherGloves = (typeof TWITCHERS_GLOVES)[number]; export const busyImmuneCommands = ['admin', 'rp']; -export const usernameCache = new Map(); -export const badgesCache = new Map(); export const minionBuyButton = new ButtonBuilder() .setCustomId('BUY_MINION') .setLabel('Buy Minion') @@ -819,28 +734,26 @@ export const FormattedCustomEmoji = //; export const IVY_MAX_TRIP_LENGTH_BOOST = Time.Minute * 25; export const chompyHats = [ - [getOSItem('Chompy bird hat (ogre bowman)'), 30], - [getOSItem('Chompy bird hat (bowman)'), 40], - [getOSItem('Chompy bird hat (ogre yeoman)'), 50], - [getOSItem('Chompy bird hat (yeoman)'), 70], - [getOSItem('Chompy bird hat (ogre marksman)'), 95], - [getOSItem('Chompy bird hat (marksman)'), 125], - [getOSItem('Chompy bird hat (ogre woodsman)'), 170], - [getOSItem('Chompy bird hat (woodsman)'), 225], - [getOSItem('Chompy bird hat (ogre forester)'), 300], - [getOSItem('Chompy bird hat (forester)'), 400], - [getOSItem('Chompy bird hat (ogre bowmaster)'), 550], - [getOSItem('Chompy bird hat (bowmaster)'), 700], - [getOSItem('Chompy bird hat (ogre expert)'), 1000], - [getOSItem('Chompy bird hat (expert)'), 1300], - [getOSItem('Chompy bird hat (ogre dragon archer)'), 1700], - [getOSItem('Chompy bird hat (dragon archer)'), 2250], - [getOSItem('Chompy bird hat (expert ogre dragon archer)'), 3000], - [getOSItem('Chompy bird hat (expert dragon archer)'), 4000] + [getItemOrThrow('Chompy bird hat (ogre bowman)'), 30], + [getItemOrThrow('Chompy bird hat (bowman)'), 40], + [getItemOrThrow('Chompy bird hat (ogre yeoman)'), 50], + [getItemOrThrow('Chompy bird hat (yeoman)'), 70], + [getItemOrThrow('Chompy bird hat (ogre marksman)'), 95], + [getItemOrThrow('Chompy bird hat (marksman)'), 125], + [getItemOrThrow('Chompy bird hat (ogre woodsman)'), 170], + [getItemOrThrow('Chompy bird hat (woodsman)'), 225], + [getItemOrThrow('Chompy bird hat (ogre forester)'), 300], + [getItemOrThrow('Chompy bird hat (forester)'), 400], + [getItemOrThrow('Chompy bird hat (ogre bowmaster)'), 550], + [getItemOrThrow('Chompy bird hat (bowmaster)'), 700], + [getItemOrThrow('Chompy bird hat (ogre expert)'), 1000], + [getItemOrThrow('Chompy bird hat (expert)'), 1300], + [getItemOrThrow('Chompy bird hat (ogre dragon archer)'), 1700], + [getItemOrThrow('Chompy bird hat (dragon archer)'), 2250], + [getItemOrThrow('Chompy bird hat (expert ogre dragon archer)'), 3000], + [getItemOrThrow('Chompy bird hat (expert dragon archer)'), 4000] ] as const; -export const secretItems: number[] = resolveItems([]); - export const toaPurpleItems = resolveItems([ "Tumeken's guardian", "Tumeken's shadow (uncharged)", @@ -874,24 +787,41 @@ export const minionActivityCache: Map = new Map(); export const ParsedCustomEmojiWithGroups = /(?a?):(?[^:]+):(?\d{17,20})/; const globalConfigSchema = z.object({ - patreonToken: z.coerce.string().default(''), - patreonCampaignID: z.coerce.number().int().default(1), - patreonWebhookSecret: z.coerce.string().default(''), - httpPort: z.coerce.number().int().default(8080), - clientID: z.string().min(15).max(25), - geAdminChannelID: z.string().default('') + clientID: z.string().min(10).max(25), + geAdminChannelID: z.string().default(''), + redisPort: z.coerce.number().int().optional(), + botToken: z.string().min(1), + isCI: z.coerce.boolean().default(false), + isProduction: z.coerce.boolean().default(production), + testingServerID: z.string(), + timeZone: z.literal('UTC') }); dotenv.config({ path: path.resolve(process.cwd(), process.env.TEST ? '.env.test' : '.env') }); +if (!process.env.BOT_TOKEN && !process.env.CI) { + throw new Error( + `You need to specify the BOT_TOKEN environment variable, copy your bot token from your config.ts and put it in the ".env" file like so:\n\nBOT_TOKEN=your_token_here` + ); +} + +const OLDSCHOOLGG_TESTING_SERVER_ID = '940758552425955348'; +const isProduction = process.env.NODE_ENV === 'production'; + export const globalConfig = globalConfigSchema.parse({ - patreonToken: process.env.PATREON_TOKEN, - patreonCampaignID: process.env.PATREON_CAMPAIGN_ID, - patreonWebhookSecret: process.env.PATREON_WEBHOOK_SECRET, - httpPort: process.env.HTTP_PORT, clientID: process.env.CLIENT_ID, - geAdminChannelID: process.env.GE_ADMIN_CHANNEL_ID + geAdminChannelID: isProduction ? '830145040495411210' : '1042760447830536212', + redisPort: process.env.REDIS_PORT, + botToken: process.env.BOT_TOKEN, + isCI: process.env.CI, + isProduction, + testingServerID: process.env.TESTING_SERVER_ID ?? OLDSCHOOLGG_TESTING_SERVER_ID, + timeZone: process.env.TZ }); +if ((process.env.NODE_ENV === 'production') !== globalConfig.isProduction || production !== globalConfig.isProduction) { + throw new Error('The NODE_ENV and isProduction variables must match'); +} + export const ONE_TRILLION = 1_000_000_000_000; export const gloriesInventorySize = 26; export const gloriesInventoryTime = Time.Minute * 2.2; @@ -919,7 +849,7 @@ export const OSB_VIRTUS_IDS = [26_241, 26_243, 26_245]; export const YETI_ID = 129_521; export const KING_GOLDEMAR_GUARD_ID = 30_913; -const gitHash = execSync('git rev-parse HEAD').toString().trim(); +export const gitHash = execSync('git rev-parse HEAD').toString().trim(); const gitRemote = BOT_TYPE === 'BSO' ? 'gc/oldschoolbot-secret' : 'oldschoolgg/oldschoolbot'; const GIT_BRANCH = BOT_TYPE === 'BSO' ? 'bso' : 'master'; @@ -974,4 +904,31 @@ export const gearValidationChecks = new Set(); export const BSO_MAX_TOTAL_LEVEL = 3120; -export const mathjs = create(all); +export const winterTodtPointsTable = new SimpleTable() + .add(420) + .add(470) + .add(500) + .add(505) + .add(510) + .add(520) + .add(550) + .add(560) + .add(590) + .add(600) + .add(620) + .add(650) + .add(660) + .add(670) + .add(680) + .add(700) + .add(720) + .add(740) + .add(750) + .add(780) + .add(850); + +if (!process.env.TEST && isMainThread) { + console.log( + `Starting... Git[${gitHash}] ClientID[${globalConfig.clientID}] Production[${globalConfig.isProduction}]` + ); +} diff --git a/src/lib/crons.ts b/src/lib/crons.ts index 65976a8bd97..8bac9f0c2fb 100644 --- a/src/lib/crons.ts +++ b/src/lib/crons.ts @@ -2,7 +2,7 @@ import { schedule } from 'node-cron'; import { analyticsTick } from './analytics'; import { syncPrescence } from './doubleLoot'; -import { prisma } from './settings/prisma'; +import { cacheGEPrices } from './marketPrices'; import { cacheCleanup } from './util/cachedUserIDs'; import { syncSlayerMaskLeaderboardCache } from './util/slayerMaskLeaderboard'; @@ -11,11 +11,8 @@ export function initCrons() { * Capture economy item data */ schedule('0 */6 * * *', async () => { - debugLog('Economy Item Insert', { - type: 'INSERT_ECONOMY_ITEM' - }); await prisma.$queryRawUnsafe(`INSERT INTO economy_item -SELECT item_id::integer, SUM(qty)::bigint FROM +SELECT item_id::integer, SUM(qty)::bigint FROM ( SELECT id, (jdata).key AS item_id, (jdata).value::text::bigint AS qty FROM (select id, json_each(bank) AS jdata FROM users) AS banks ) @@ -35,7 +32,6 @@ GROUP BY item_id;`); * prescence */ schedule('0 * * * *', () => { - debugLog('Set Activity cronjob starting'); syncPrescence(); }); @@ -43,11 +39,15 @@ GROUP BY item_id;`); * Delete all voice channels */ schedule('0 0 */1 * *', async () => { - debugLog('Cache cleanup cronjob starting'); cacheCleanup(); }); schedule('0 0 * * *', async () => { syncSlayerMaskLeaderboardCache(); }); + + schedule('35 */48 * * *', async () => { + debugLog('cacheGEPrices cronjob starting'); + await cacheGEPrices(); + }); } diff --git a/src/lib/customItems/customItems.ts b/src/lib/customItems/customItems.ts index 7e3bfb58ddf..e45c4730e70 100644 --- a/src/lib/customItems/customItems.ts +++ b/src/lib/customItems/customItems.ts @@ -13,10 +13,10 @@ import './dwarven'; import './moktang'; import './leagues'; -import { EquipmentSlot, ItemRequirements } from 'oldschooljs/dist/meta/types'; +import { EquipmentSlot, type ItemRequirements } from 'oldschooljs/dist/meta/types'; import getOSItem from '../util/getOSItem'; -import { maxedRequirements, setCustomItem, UN_EQUIPPABLE } from './util'; +import { UN_EQUIPPABLE, maxedRequirements, setCustomItem } from './util'; setCustomItem(1579, "Thieves' armband", 'Rune gloves', {}, 100_000); setCustomItem( @@ -11696,7 +11696,8 @@ setCustomItem( 'Coal', { customItemData: { - cantDropFromMysteryBoxes: true + cantDropFromMysteryBoxes: true, + isDiscontinued: true }, buy_limit: 100 }, @@ -11709,7 +11710,8 @@ setCustomItem( 'Coal', { customItemData: { - cantDropFromMysteryBoxes: true + cantDropFromMysteryBoxes: true, + isDiscontinued: true } }, 1 @@ -12468,3 +12470,317 @@ setCustomItem( }, 100_000 ); + +setCustomItem(73_185, 'Frost mask', 'Bronze full helm', {}, 100_000); + +setCustomItem( + 73_186, + 'Nex plushie', + 'Coal', + { + tradeable: false + }, + 100_000 +); + +setCustomItem(73_187, 'Dunce hat', 'Bronze full helm', {}, 100_000); + +setCustomItem(73_188, 'Dunce top', 'Bronze platebody', {}, 100_000); + +setCustomItem(73_189, 'Dunce legs', 'Bronze platelegs', {}, 100_000); + +setCustomItem(73_190, 'Dunce gloves', 'Bronze gloves', {}, 100_000); + +setCustomItem(73_191, 'Dunce shoes', 'Bronze boots', {}, 100_000); + +setCustomItem(73_192, 'Chilli chocolate', 'Coal', {}, 100_000); + +setCustomItem(73_193, 'Zak plushie', 'Coal', { tradeable: false }, 100_000); + +setCustomItem( + 73_200, + 'Birthday crate (s6)', + 'Coal', + { + customItemData: { + cantDropFromMysteryBoxes: true, + cantBeSacrificed: true, + isDiscontinued: true + } + }, + 100_000 +); + +setCustomItem( + 73_201, + 'Birthday crate key (s6)', + 'Coal', + { + customItemData: { + cantDropFromMysteryBoxes: true, + cantBeSacrificed: true, + isDiscontinued: true + } + }, + 100_000 +); + +setCustomItem( + 73_202, + 'Ethereal partyhat', + 'Red partyhat', + { + customItemData: { + cantDropFromMysteryBoxes: true, + isDiscontinued: true + } + }, + 1_000_000 +); + +setCustomItem( + 73_203, + 'Swan hat', + 'Red partyhat', + { + customItemData: { + cantDropFromMysteryBoxes: true, + isDiscontinued: true + } + }, + 1_000_000 +); + +setCustomItem( + 73_204, + 'Swan scarf', + 'Amulet of strength', + { + customItemData: { + cantDropFromMysteryBoxes: true, + isDiscontinued: true + } + }, + 1_000_000 +); + +setCustomItem( + 73_205, + 'Rose tinted glasses', + 'Red partyhat', + { + customItemData: { + cantDropFromMysteryBoxes: true, + isDiscontinued: true + } + }, + 1_000_000 +); + +setCustomItem( + 73_206, + 'Blabberbeak jumper', + 'Bronze platebody', + { + customItemData: { + cantDropFromMysteryBoxes: true, + isDiscontinued: true + } + }, + 1_000_000 +); + +setCustomItem( + 73_207, + 'BSO banner', + 'Bronze dagger', + { + customItemData: { + cantDropFromMysteryBoxes: true, + isDiscontinued: true + } + }, + 1_000_000 +); + +setCustomItem( + 73_208, + 'Blueberry birthday cake', + 'Coal', + { + customItemData: { + cantDropFromMysteryBoxes: true, + isDiscontinued: true + } + }, + 1_000_000 +); + +setCustomItem( + 73_209, + 'Gambling skillcape', + 'Red cape', + { + customItemData: { + cantDropFromMysteryBoxes: true, + isDiscontinued: true + } + }, + 1_000_000 +); + +setCustomItem( + 73_210, + 'Raw plopper bacon', + 'Coal', + { + customItemData: { + cantDropFromMysteryBoxes: true + } + }, + 1000 +); + +setCustomItem( + 73_211, + 'Cooked plopper bacon', + 'Coal', + { + customItemData: { + cantDropFromMysteryBoxes: true + } + }, + 1000 +); + +setCustomItem( + 73_212, + 'Monkey cape', + 'Red cape', + { + customItemData: { + cantDropFromMysteryBoxes: true + } + }, + 100_000 +); + +setCustomItem( + 73_213, + 'BSO flowers', + 'Coal', + { + customItemData: { + cantDropFromMysteryBoxes: true + } + }, + 100_000 +); + +setCustomItem( + 73_214, + 'Ceremonial top', + 'Bronze platebody', + { + customItemData: { + cantDropFromMysteryBoxes: true + } + }, + 100_000 +); + +setCustomItem( + 73_215, + 'Ceremonial legs', + 'Bronze platelegs', + { + customItemData: { + cantDropFromMysteryBoxes: true + } + }, + 100_000 +); + +setCustomItem( + 73_216, + 'Ceremonial boots', + 'Bronze boots', + { + customItemData: { + cantDropFromMysteryBoxes: true + } + }, + 100_000 +); + +setCustomItem( + 73_217, + 'Ceremonial cape', + 'Red cape', + { + customItemData: { + cantDropFromMysteryBoxes: true + } + }, + 100_000 +); + +setCustomItem( + 73_218, + 'Ceremonial hat', + 'Bronze full helm', + { + customItemData: { + cantDropFromMysteryBoxes: true + } + }, + 100_000 +); + +setCustomItem( + 73_219, + 'Plopper nose', + 'Bronze full helm', + { + customItemData: { + cantDropFromMysteryBoxes: true + } + }, + 100_000 +); + +setCustomItem( + 73_220, + 'Hoppy plushie', + 'Coal', + { + customItemData: { + cantDropFromMysteryBoxes: true + } + }, + 100_000 +); + +setCustomItem( + 73_221, + 'Dice plushie', + 'Bronze dagger', + { + customItemData: { + cantDropFromMysteryBoxes: true + } + }, + 100_000 +); + +setCustomItem( + 73_222, + 'Offhand dice plushie', + 'Bronze kiteshield', + { + customItemData: { + cantDropFromMysteryBoxes: true + } + }, + 100_000 +); diff --git a/src/lib/customItems/util.ts b/src/lib/customItems/util.ts index e30e48183d3..4bd1fad908f 100644 --- a/src/lib/customItems/util.ts +++ b/src/lib/customItems/util.ts @@ -1,8 +1,8 @@ import { cleanString as deepCleanString } from '@oldschoolgg/toolkit'; -import { DeepPartial } from '@sapphire/utilities'; +import type { DeepPartial } from '@sapphire/utilities'; import deepMerge from 'deepmerge'; import { Items } from 'oldschooljs'; -import { Item, ItemRequirements } from 'oldschooljs/dist/meta/types'; +import type { Item, ItemRequirements } from 'oldschooljs/dist/meta/types'; import { itemNameMap } from 'oldschooljs/dist/structures/Items'; import { cleanString } from 'oldschooljs/dist/util/cleanString'; @@ -35,14 +35,10 @@ export function setCustomItem(id: number, name: string, baseItem: string, newIte if (hasSet.some(i => i.name === name) && name !== 'Smokey') { throw new Error(`Tried to add 2 custom items with same name, called ${name}`); } - if ( - newItemData && - newItemData.customItemData && - newItemData.customItemData.superTradeableButTradeableOnGE && - !newItemData.customItemData.isSuperUntradeable - ) { + if (newItemData?.customItemData?.superTradeableButTradeableOnGE && !newItemData.customItemData.isSuperUntradeable) { throw new Error('Tried to add a custom item with superTradeableButTradeableOnGE, but not isSuperUntradeable'); } + const data: Item = deepMerge({ ...getOSItem(baseItem) }, { ...newItemData, name, id }) as Item; data.price = price || 1; diff --git a/src/lib/data/Collections.ts b/src/lib/data/Collections.ts index 8d3e4d97caf..f6ab509895a 100644 --- a/src/lib/data/Collections.ts +++ b/src/lib/data/Collections.ts @@ -1,29 +1,30 @@ -import { AttachmentBuilder } from 'discord.js'; import { calcWhatPercent, isObject, notEmpty, removeFromArr, sumArr, uniqueArr } from 'e'; import { Bank, Clues, Monsters } from 'oldschooljs'; -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; import { ChambersOfXeric } from 'oldschooljs/dist/simulation/misc/ChambersOfXeric'; -import Monster from 'oldschooljs/dist/structures/Monster'; +import type Monster from 'oldschooljs/dist/structures/Monster'; import { divinationEnergies, portents } from '../bso/divination'; -import { ClueTier, ClueTiers } from '../clues/clueTiers'; -import { CollectionLogType } from '../collectionLogTask'; +import { type ClueTier, ClueTiers } from '../clues/clueTiers'; +import type { CollectionLogType } from '../collectionLogTask'; import { PHOSANI_NIGHTMARE_ID, ZALCANO_ID } from '../constants'; import { discontinuedDyes, dyedItems } from '../dyedItems'; import { growablePetsCL } from '../growablePets'; import { implingsCL } from '../implings'; import { inventionCL } from '../invention/inventions'; import { keyCrates } from '../keyCrates'; -import killableMonsters, { effectiveMonsters, NightmareMonster } from '../minions/data/killableMonsters'; +import killableMonsters, { NightmareMonster } from '../minions/data/killableMonsters'; +import { AkumuLootTable } from '../minions/data/killableMonsters/custom/bosses/Akumu'; import { Ignecarus } from '../minions/data/killableMonsters/custom/bosses/Ignecarus'; import { - kalphiteKingLootTable, - KalphiteKingMonster + KalphiteKingMonster, + kalphiteKingLootTable } from '../minions/data/killableMonsters/custom/bosses/KalphiteKing'; import KingGoldemar from '../minions/data/killableMonsters/custom/bosses/KingGoldemar'; import { MOKTANG_ID, MoktangLootTable } from '../minions/data/killableMonsters/custom/bosses/Moktang'; import { Naxxus, NaxxusLootTableFinishable } from '../minions/data/killableMonsters/custom/bosses/Naxxus'; import { VasaMagus } from '../minions/data/killableMonsters/custom/bosses/VasaMagus'; +import { VenatrixLootTable } from '../minions/data/killableMonsters/custom/bosses/Venatrix'; import { BSOMonsters } from '../minions/data/killableMonsters/custom/customMonsters'; import { sepulchreFloors } from '../minions/data/sepulchre'; import { @@ -32,7 +33,7 @@ import { MediumEncounterLoot, rewardTokens } from '../minions/data/templeTrekking'; -import { nexLootTable, NexMonster } from '../nex'; +import { NexMonster, nexLootTable } from '../nex'; import type { MinigameName } from '../settings/minigames'; import { ElderClueTable } from '../simulation/elderClue'; import { GrandmasterClueTable } from '../simulation/grandmasterClue'; @@ -46,9 +47,11 @@ import smithables from '../skilling/skills/smithing/smithables'; import { SkillsEnum } from '../skilling/types'; import { MUserStats } from '../structures/MUserStats'; import type { ItemBank } from '../types'; -import { fetchStatsForCL, stringMatches } from '../util'; +import { stringMatches } from '../util'; +import getOSItem from '../util/getOSItem'; import resolveItems from '../util/resolveItems'; -import { makeTable, shuffleRandom } from '../util/smallUtils'; +import { shuffleRandom } from '../util/smallUtils'; +import type { FormatProgressFunction, ICollection, ILeftListStatus, IToReturnCollection } from './CollectionsExport'; import { abyssalDragonCL, abyssalSireCL, @@ -109,7 +112,6 @@ import { fishingTrawlerCL, fistOfGuthixCL, forestryCL, - FormatProgressFunction, fossilIslandNotesCL, generalGraardorCL, giantMoleCL, @@ -122,10 +124,7 @@ import { hallowedSepulchreCL, hesporiCL, holidayCL, - ICollection, ignecarusCL, - ILeftListStatus, - IToReturnCollection, kalphiteKingCL, kalphiteQueenCL, kingBlackDragonCL, @@ -173,13 +172,13 @@ import { stealingCreationCL, templeTrekkingCL, temporossCL, - theatreOfBLoodCL, theGauntletCL, theInfernoCL, theLeviathanCL, theNightmareCL, - thermonuclearSmokeDevilCL, theWhispererCL, + theatreOfBLoodCL, + thermonuclearSmokeDevilCL, tinkeringWorshopCL, titheFarmCL, toaCL, @@ -311,14 +310,12 @@ export const allCollectionLogs: ICollection = { }, allItems: (() => { return [ - ...new Set( - ...[ - Monsters.GeneralGraardor.allItems, - Monsters.CommanderZilyana.allItems, - Monsters.Kreearra.allItems, - Monsters.KrilTsutsaroth.allItems - ] - ) + ...new Set([ + ...Monsters.GeneralGraardor.allItems, + ...Monsters.CommanderZilyana.allItems, + ...Monsters.Kreearra.allItems, + ...Monsters.KrilTsutsaroth.allItems + ]) ]; })(), items: [ @@ -426,6 +423,25 @@ export const allCollectionLogs: ICollection = { items: fightCavesCL, fmtProg: kcProg(Monsters.TzTokJad) }, + 'Fortis Colosseum': { + kcActivity: { + Default: async (_, minigameScores) => + minigameScores.find(i => i.minigame.column === 'colosseum')!.score + }, + alias: ['colosseum'], + items: resolveItems([ + 'Smol heredit', + "Dizana's quiver (uncharged)", + 'Sunfire fanatic cuirass', + 'Sunfire fanatic chausses', + 'Sunfire fanatic helm', + 'Echo crystal', + 'Tonalztics of ralos (uncharged)', + 'Sunfire splinters', + 'Uncut onyx' + ]), + fmtProg: ({ minigames }) => `${minigames.colosseum} KC` + }, 'The Gauntlet': { alias: ['gauntlet', 'crystalline hunllef', 'hunllef'], kcActivity: { @@ -462,6 +478,10 @@ export const allCollectionLogs: ICollection = { fmtProg: kcProg(Monsters.Hespori) }, 'The Inferno': { + kcActivity: { + Default: async (_, minigameScores) => + minigameScores.find(i => i.minigame.column === 'inferno')!.score + }, alias: ['zuk', 'inferno'], items: theInfernoCL, fmtProg: ({ minigames }) => `${minigames.inferno} KC` @@ -681,13 +701,13 @@ export const allCollectionLogs: ICollection = { }, Akumu: { alias: ['akumu'], - allItems: akumuCL, + allItems: AkumuLootTable.allItems, items: akumuCL, fmtProg: kcProg(BSOMonsters.Akumu.id) }, Venatrix: { alias: ['venatrix'], - allItems: venatrixCL, + allItems: VenatrixLootTable.allItems, items: venatrixCL, fmtProg: kcProg(BSOMonsters.Venatrix.id) }, @@ -722,9 +742,9 @@ export const allCollectionLogs: ICollection = { kcActivity: { Default: async (_, minigameScores) => minigameScores.find(i => i.minigame.column === 'tombs_of_amascut')!.score, - Entry: async (_, __, { stats }) => stats.getToaKCs().entryKC, - Normal: async (_, __, { stats }) => stats.getToaKCs().normalKC, - Expert: async (_, __, { stats }) => stats.getToaKCs().expertKC + Entry: async (_, __, stats) => stats.getToaKCs().entryKC, + Normal: async (_, __, stats) => stats.getToaKCs().normalKC, + Expert: async (_, __, stats) => stats.getToaKCs().expertKC }, items: toaCL, isActivity: true, @@ -785,21 +805,19 @@ export const allCollectionLogs: ICollection = { }, allItems: (() => { return [ - ...new Set( - ...[ - Monsters.RevenantImp.allItems, - Monsters.RevenantGoblin.allItems, - Monsters.RevenantPyrefiend.allItems, - Monsters.RevenantHobgoblin.allItems, - Monsters.RevenantCyclops.allItems, - Monsters.RevenantHellhound.allItems, - Monsters.RevenantDemon.allItems, - Monsters.RevenantOrk.allItems, - Monsters.RevenantDarkBeast.allItems, - Monsters.RevenantKnight.allItems, - Monsters.RevenantDragon.allItems - ] - ) + ...new Set([ + ...Monsters.RevenantImp.allItems, + ...Monsters.RevenantGoblin.allItems, + ...Monsters.RevenantPyrefiend.allItems, + ...Monsters.RevenantHobgoblin.allItems, + ...Monsters.RevenantCyclops.allItems, + ...Monsters.RevenantHellhound.allItems, + ...Monsters.RevenantDemon.allItems, + ...Monsters.RevenantOrk.allItems, + ...Monsters.RevenantDarkBeast.allItems, + ...Monsters.RevenantKnight.allItems, + ...Monsters.RevenantDragon.allItems + ]) ]; })(), items: revenantsCL @@ -1398,10 +1416,7 @@ export const allCollectionLogs: ICollection = { items: resolveItems([ ...divinersOutfit, ...portents.map(p => p.item.id), - ...divinationEnergies - .map(i => [i.boon?.id, i.item.id]) - .flat(1) - .filter(notEmpty), + ...divinationEnergies.flatMap(i => [i.boon?.id, i.item.id]).filter(notEmpty), 'Divine egg', 'Jar of memories', 'Doopy' @@ -1833,7 +1848,9 @@ export const allCollectionLogs: ICollection = { for (const crate of keyCrates) { allCollectionLogs.Discontinued.activities[crate.item.name] = { alias: [crate.item.name.toLowerCase()], - items: resolveItems([crate.item.id, crate.key.id, ...crate.table.allItems]), + items: resolveItems([crate.item.id, crate.key.id, ...crate.table.allItems]).filter( + i => !getOSItem(i).customItemData?.isSecret + ), counts: false }; } @@ -1847,7 +1864,7 @@ export const allDroppedItems = uniqueArr([ ) .flat(100), ...Object.values(Monsters) - .map(m => (m && m.allItems ? m.allItems : [])) + .map(m => (m?.allItems ? m.allItems : [])) .flat(100) ]); @@ -1895,13 +1912,8 @@ export function calcCLDetails(user: MUser | Bank) { } // Get the left list to be added to the cls -function getLeftList( - userBank: Bank, - checkCategory: string, - allItems: boolean = false, - removeCoins = false -): ILeftListStatus { - let leftList: ILeftListStatus = {}; +function getLeftList(userBank: Bank, checkCategory: string, allItems = false, removeCoins = false): ILeftListStatus { + const leftList: ILeftListStatus = {}; for (const [category, entries] of Object.entries(allCollectionLogs)) { if (category === checkCategory) { // Sort list by alphabetical order @@ -1923,20 +1935,9 @@ function getLeftList( return leftList; } -export interface UserStatsDataNeededForCL { - sacrificedBank: Bank; - titheFarmsCompleted: number; - lapsScores: ItemBank; - openableScores: Bank; - kcBank: ItemBank; - highGambles: number; - gotrRiftSearches: number; - stats: MUserStats; -} - type CLType = 'sacrifice' | 'bank' | 'collection' | 'temp' | 'tame' | 'disassembly'; -export async function getBank(user: MUser, type: CLType, userStats: UserStatsDataNeededForCL | MUserStats | null) { +export function getBank(user: MUser, type: CLType, userStats: MUserStats | null): Bank { switch (type) { case 'collection': return new Bank(user.cl); @@ -1946,8 +1947,8 @@ export async function getBank(user: MUser, type: CLType, userStats: UserStatsDat if (!userStats) return new Bank(); return new Bank(userStats.sacrificedBank); case 'tame': { - const { getUsersTamesCollectionLog } = await import('../util/getUsersTameCL'); - return getUsersTamesCollectionLog(user.id); + if (!userStats) return new Bank(); + return new Bank(userStats.userStats.tame_cl_bank as ItemBank); } case 'temp': return new Bank(user.user.temp_cl as ItemBank); @@ -1957,43 +1958,18 @@ export async function getBank(user: MUser, type: CLType, userStats: UserStatsDat } } -export async function getTotalCl( - user: MUser, - logType: CLType, - userStats: UserStatsDataNeededForCL | MUserStats | null -) { +export async function getTotalCl(user: MUser, logType: CLType, userStats: MUserStats | null) { let result = undefined; try { - result = getUserClData(await getBank(user, logType, userStats), allCLItemsFiltered); + result = getUserClData(getBank(user, logType, userStats), allCLItemsFiltered); } catch (_e) { await user.repairBrokenItems(); - const newBank = await getBank(user, logType, userStats); + const newBank = getBank(user, logType, userStats); result = getUserClData(newBank, allCLItemsFiltered); } return result; } -export function getPossibleOptions() { - const roles: [string, string, string][] = []; - const categories: [string, string, string][] = []; - const activities: [string, string, string][] = []; - - // Get categories and enabled activities - for (const [category, entries] of Object.entries(allCollectionLogs)) { - categories.push(['General', category, entries.alias ? entries.alias.join(', ') : '']); - for (const [activityName, attributes] of Object.entries(entries.activities)) { - categories.push(['Activity', activityName, attributes.alias ? attributes.alias.join(', ') : '']); - } - } - - // get monsters - for (const monster of effectiveMonsters) { - categories.push(['Monsters', monster.name, monster.aliases ? monster.aliases.join(', ') : '']); - } - const normalTable = makeTable(['Type', 'name: ', 'Alias'], [...categories, ...activities, ...roles]); - return new AttachmentBuilder(Buffer.from(normalTable), { name: 'possible_logs.txt' }); -} - export function getCollectionItems( collection: string, allItems: boolean, @@ -2018,12 +1994,9 @@ export function getCollectionItems( } let _items: number[] = []; - let _clName: string = ''; + let _clName = ''; loop: for (const [category, entries] of Object.entries(allCollectionLogs)) { - if ( - stringMatches(category, collection) || - (entries.alias && entries.alias.some(a => stringMatches(a, collection))) - ) { + if (stringMatches(category, collection) || entries.alias?.some(a => stringMatches(a, collection))) { _clName = category; _items = uniqueArr( Object.values(entries.activities) @@ -2034,10 +2007,7 @@ export function getCollectionItems( break; } for (const [activityName, attributes] of Object.entries(entries.activities)) { - if ( - stringMatches(activityName, collection) || - (attributes.alias && attributes.alias.find(a => stringMatches(a, collection))) - ) { + if (stringMatches(activityName, collection) || attributes.alias?.find(a => stringMatches(a, collection))) { _clName = activityName; _items = [ ...new Set([...attributes.items, ...(allItems && attributes.allItems ? attributes.allItems : [])]) @@ -2053,7 +2023,7 @@ export function getCollectionItems( ); if (_monster) { _clName = _monster.name; - _items = uniqueArr(Monsters.get(_monster!.id)!.allItems); + _items = uniqueArr(Monsters.get(_monster?.id)!.allItems); } } if (removeCoins && _items.includes(995)) _items = removeFromArr(_items, 995); @@ -2076,7 +2046,7 @@ for (const mon of killableMonsters) allClNames.push(mon.name); export async function getCollection(options: { user: MUser; search: string; - flags: { [key: string]: string | number }; + flags: { [key: string]: string | number | undefined }; logType?: CollectionLogType; }): Promise { let { user, search, flags, logType } = options; @@ -2088,15 +2058,15 @@ export async function getCollection(options: { } const minigameScores = await user.fetchMinigameScores(); - const userStats = await fetchStatsForCL(user); - const userCheckBank = await getBank(user, logType, userStats); + const userStats = await MUserStats.fromID(user.id); + const userCheckBank = getBank(user, logType, userStats); let clItems = getCollectionItems(search, allItems, logType === 'sacrifice'); if (clItems.length >= 500) { flags.missing = 'missing'; } - if (Boolean(flags.missing)) { + if (flags.missing) { clItems = clItems.filter(i => !userCheckBank.has(i)); } @@ -2126,7 +2096,7 @@ export async function getCollection(options: { } for (const [category, entries] of Object.entries(allCollectionLogs)) { - if (stringMatches(category, search) || (entries.alias && entries.alias.some(a => stringMatches(a, search)))) { + if (stringMatches(category, search) || entries.alias?.some(a => stringMatches(a, search))) { return { category, name: category, @@ -2139,10 +2109,7 @@ export async function getCollection(options: { }; } for (const [activityName, attributes] of Object.entries(entries.activities)) { - if ( - stringMatches(activityName, search) || - (attributes.alias && attributes.alias.find(a => stringMatches(a, search))) - ) { + if (stringMatches(activityName, search) || attributes.alias?.find(a => stringMatches(a, search))) { let userKC: Record | undefined = { Default: 0 }; // Defaults to the activity name @@ -2208,9 +2175,9 @@ export async function getCollection(options: { return false; } -export const allCollectionLogsFlat = Object.values(allCollectionLogs) - .map(i => Object.entries(i.activities).map(entry => ({ ...entry[1], name: entry[0] }))) - .flat(); +export const allCollectionLogsFlat = Object.values(allCollectionLogs).flatMap(i => + Object.entries(i.activities).map(entry => ({ ...entry[1], name: entry[0] })) +); export function isCLItem(item: Item | number | [Item, number]): boolean { if (Array.isArray(item)) return isCLItem(item[0]); @@ -2218,7 +2185,5 @@ export function isCLItem(item: Item | number | [Item, number]): boolean { } export const bossCLItems = Object.values({ - ...allCollectionLogs['PvM'].activities -}) - .map(i => i.items) - .flat(); + ...allCollectionLogs.PvM.activities +}).flatMap(i => i.items); diff --git a/src/lib/data/CollectionsExport.ts b/src/lib/data/CollectionsExport.ts index cb87c32cd03..9ddeef70875 100644 --- a/src/lib/data/CollectionsExport.ts +++ b/src/lib/data/CollectionsExport.ts @@ -1,15 +1,15 @@ -import { Minigame } from '@prisma/client'; +import type { Minigame } from '@prisma/client'; import { Bank } from 'oldschooljs'; -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; +import { resolveItems } from 'oldschooljs/dist/util/util'; import { growablePets } from '../growablePets'; import { stoneSpirits } from '../minions/data/stoneSpirits'; -import { MinigameScore } from '../settings/minigames'; +import type { MinigameScore } from '../settings/minigames'; +import type { MUserStats } from '../structures/MUserStats'; import getOSItem from '../util/getOSItem'; import { assert } from '../util/logError'; -import resolveItems from '../util/resolveItems'; import { LampTable } from '../xpLamps'; -import { UserStatsDataNeededForCL } from './Collections'; import { gracefulCapes, gracefulFeet, @@ -18,7 +18,7 @@ import { gracefulLegs, gracefulTops } from './gracefulVariants'; -import { allHolidayItems, PartyhatTable } from './holidayItems'; +import { PartyhatTable, allHolidayItems } from './holidayItems'; export interface IToReturnCollection { category: string; @@ -41,7 +41,7 @@ export interface IKCActivity { [key: string]: | string | string[] - | ((user: MUser, minigameScores: MinigameScore[], stats: UserStatsDataNeededForCL) => Promise); + | ((user: MUser, minigameScores: MinigameScore[], stats: MUserStats) => Promise); } export type FormatProgressFunction = ({ @@ -53,10 +53,10 @@ export type FormatProgressFunction = ({ user: MUser; getKC: (id: number) => Promise; minigames: Minigame; - stats: UserStatsDataNeededForCL; + stats: MUserStats; }) => string | string[] | Promise; -export interface ICollectionActivity { +interface ICollectionActivity { [key: string]: { // If the collection will count towards the collection log counter counts?: false; @@ -207,27 +207,12 @@ export const alchemicalHydraCL = resolveItems([ 'Alchemical hydra heads' ]); -export const karilsItems = resolveItems([ - "Karil's coif", - "Karil's leathertop", - "Karil's leatherskirt", - "Karil's crossbow" -]); -export const ahrimsItems = resolveItems(["Ahrim's hood", "Ahrim's robetop", "Ahrim's robeskirt", "Ahrim's staff"]); -export const dharokItems = resolveItems([ - "Dharok's helm", - "Dharok's platebody", - "Dharok's platelegs", - "Dharok's greataxe" -]); -export const guthansItems = resolveItems([ - "Guthan's helm", - "Guthan's platebody", - "Guthan's chainskirt", - "Guthan's warspear" -]); -export const toragsItems = resolveItems(["Torag's helm", "Torag's platebody", "Torag's platelegs", "Torag's hammers"]); -export const veracsItems = resolveItems(["Verac's helm", "Verac's brassard", "Verac's plateskirt", "Verac's flail"]); +const karilsItems = resolveItems(["Karil's coif", "Karil's leathertop", "Karil's leatherskirt", "Karil's crossbow"]); +const ahrimsItems = resolveItems(["Ahrim's hood", "Ahrim's robetop", "Ahrim's robeskirt", "Ahrim's staff"]); +const dharokItems = resolveItems(["Dharok's helm", "Dharok's platebody", "Dharok's platelegs", "Dharok's greataxe"]); +const guthansItems = resolveItems(["Guthan's helm", "Guthan's platebody", "Guthan's chainskirt", "Guthan's warspear"]); +const toragsItems = resolveItems(["Torag's helm", "Torag's platebody", "Torag's platelegs", "Torag's hammers"]); +const veracsItems = resolveItems(["Verac's helm", "Verac's brassard", "Verac's plateskirt", "Verac's flail"]); export const barrowsItemArr = [karilsItems, ahrimsItems, guthansItems, toragsItems, veracsItems, dharokItems]; export const barrowsChestCL = resolveItems([ ...karilsItems, @@ -385,7 +370,8 @@ export const kalphiteQueenCL = resolveItems([ 'Kq head', 'Jar of sand', 'Dragon 2h sword', - 'Dragon chainbody' + 'Dragon chainbody', + 'Dragon pickaxe' ]); export const kingBlackDragonCL = resolveItems([ 'Prince black dragon', @@ -427,12 +413,12 @@ export const spiritAnglerOutfit = resolveItems([ export const temporossCL = resolveItems([ 'Tiny tempor', 'Big harpoonfish', + ...spiritAnglerOutfit, 'Tome of water (empty)', 'Soaked page', 'Tackle box', 'Fish barrel', 'Dragon harpoon', - ...spiritAnglerOutfit, 'Spirit flakes' ]); export const thermonuclearSmokeDevilCL = resolveItems([ @@ -502,7 +488,7 @@ export const chambersOfXericMetamorphPets = resolveItems([ 'Vespina' ]); export const tobMetamorphPets = resolveItems(["Lil' Maiden", "Lil' Bloat", "Lil' Nylo", "Lil' Sot", "Lil' Xarp"]); -export const toaMetamorphPets = resolveItems(['Zebo', "Tumeken's guardian", 'Kephriti', 'Babi', 'Akkhito']); +const toaMetamorphPets = resolveItems(['Zebo', "Tumeken's guardian", 'Kephriti', 'Babi', 'Akkhito']); export const chambersOfXericNormalCL = resolveItems([ 'Olmlet', 'Twisted bow', @@ -1732,7 +1718,8 @@ export const allPetsCL = resolveItems([ "Lil'viathan", 'Butch', 'Baron', - 'Scurry' + 'Scurry', + 'Smol heredit' ]); export const camdozaalCL = resolveItems([ 'Barronite mace', @@ -2020,9 +2007,9 @@ export const slayerCL = resolveItems([ 'Mystic gloves (dusk)', 'Mystic boots (dusk)', 'Basilisk jaw', - // "Dagon'hai hat", - // "Dagon'hai robe top", - // "Dagon'hai robe bottom", + "Dagon'hai hat", + "Dagon'hai robe top", + "Dagon'hai robe bottom", 'Blood shard', ...stoneSpirits.map(i => i.spirit.id), 'Brackish blade', @@ -2652,7 +2639,7 @@ export const gorajanArcherOutfit = resolveItems([ 'Gorajan archer boots' ]); -export const metamorphPets = resolveItems([ +const metamorphPets = resolveItems([ 'Little parasite', 'Dark squirrel', 'Baby mole-rat', @@ -2672,7 +2659,7 @@ export const allPetIDs = [ ...discontinuedCustomPetsCL, ...chambersOfXericMetamorphPets, ...tobMetamorphPets, - ...growablePets.map(petSeries => petSeries.stages).flat(1), + ...growablePets.flatMap(petSeries => petSeries.stages), ...resolveItems(['Little parasite', 'Dark squirrel', 'Black swan', 'Abyssal protector']), ...metamorphPets, ...toaMetamorphPets, diff --git a/src/lib/data/bso.commands.json b/src/lib/data/bso.commands.json new file mode 100644 index 00000000000..d523c7b5dfb --- /dev/null +++ b/src/lib/data/bso.commands.json @@ -0,0 +1,622 @@ +[ + { + "name": "activities", + "desc": "Miscellaneous activities you can do.", + "subOptions": [ + "plank_make", + "chompy_hunt", + "champions_challenge", + "warriors_guild", + "camdozaal", + "collect", + "quest", + "decant", + "charge", + "fight_caves", + "inferno", + "birdhouses", + "aerial_fishing", + "enchant", + "bury", + "scatter", + "puro_puro", + "alch", + "cast", + "underwater", + "other" + ] + }, + { + "name": "ask", + "desc": "Ask a yes/no question to the bot and receive an answer.", + "subOptions": [] + }, + { + "name": "bank", + "desc": "See your minions bank.", + "subOptions": [] + }, + { + "name": "bingo", + "desc": "Bingo!", + "subOptions": ["make_team", "leave_team", "view", "leaderboard", "create_bingo", "manage_bingo", "items"] + }, + { + "name": "bossrecords", + "desc": "Shows your OSRS boss records.", + "subOptions": [] + }, + { + "name": "bs", + "desc": "Search your minions bank.", + "subOptions": [] + }, + { + "name": "bsominigames", + "desc": "Send your minion to do various bso minigames.", + "subOptions": [ + "baxtorian_bathhouses", + "monkey_rumble", + "ourania_delivery_service", + "fishing_contest", + "fist_of_guthix", + "stealing_creation", + "tinkering_workshop", + "balthazars_big_bonanza", + "divine_dominion", + "guthixian_cache", + "turaels_trials" + ] + }, + { + "name": "build", + "desc": "Sends your minion to train Construction by building things.", + "examples": ["/build name:Crude chair"], + "subOptions": [] + }, + { + "name": "buy", + "desc": "Allows you to purchase items.", + "subOptions": [] + }, + { + "name": "ca", + "desc": "Combat Achievements", + "subOptions": ["view", "claim"] + }, + { + "name": "casket", + "desc": "Simulate opening lots of clues caskets.", + "subOptions": [] + }, + { + "name": "choose", + "desc": "Have the bot make a choice from a list of things.", + "examples": ["/choose list:First option, second option, third option"], + "subOptions": [] + }, + { + "name": "chop", + "desc": "Chop logs using the Woodcutting skill.", + "examples": ["/chop name:Logs"], + "subOptions": [] + }, + { + "name": "cl", + "desc": "See your Collection Log.", + "examples": ["/cl name:Boss"], + "subOptions": [] + }, + { + "name": "claim", + "desc": "Claim prizes, rewards and other things.", + "subOptions": [] + }, + { + "name": "clue", + "desc": "Send your minion to complete clue scrolls.", + "examples": ["/clue tier:easy"], + "subOptions": [] + }, + { + "name": "completion", + "desc": "Completionist tasks.", + "subOptions": ["view_all_tasks", "check"] + }, + { + "name": "config", + "desc": "Commands configuring settings and options.", + "subOptions": ["server", "user"] + }, + { + "name": "cook", + "desc": "Cook things using the cooking skill.", + "examples": ["/cook name:Shrimp"], + "subOptions": [] + }, + { + "name": "craft", + "desc": "Craft items with the Crafting skill.", + "examples": ["/craft name:Onyx necklace"], + "subOptions": [] + }, + { + "name": "create", + "desc": "Allows you to create items, like godswords or spirit shields - and pack barrows armor sets.", + "subOptions": [] + }, + { + "name": "data", + "desc": "View various pieces of data.", + "examples": ["/data name:Personal Activity Types"], + "subOptions": [] + }, + { + "name": "dg", + "desc": "The Dungeoneering skill.", + "subOptions": ["start", "stats", "buy"] + }, + { + "name": "divination", + "desc": "The divination skill.", + "subOptions": ["harvest_memories", "portent", "charge_portent", "toggle_portent"] + }, + { + "name": "drop", + "desc": "Drop items from your bank.", + "examples": ["/drop items:10 trout, 5 coal"], + "subOptions": [] + }, + { + "name": "droprate", + "desc": "Check the droprate of something.", + "subOptions": [] + }, + { + "name": "fake", + "desc": "Generate fake images of getting loot.", + "subOptions": [] + }, + { + "name": "fakepm", + "desc": "Generate fake images of PMs.", + "subOptions": [] + }, + { + "name": "farming", + "desc": "Allows you to do Farming related things.", + "subOptions": [ + "check_patches", + "plant", + "auto_farm", + "auto_farm_filter", + "default_compost", + "always_pay", + "harvest", + "tithe_farm", + "compost_bin", + "contract" + ] + }, + { + "name": "finish", + "desc": "Simulate finishing a CL.", + "subOptions": [] + }, + { + "name": "fish", + "desc": "Send your minion to fish fish.", + "examples": ["/fish name:Shrimp"], + "subOptions": [] + }, + { + "name": "fletch", + "desc": "Fletch items with the Fletching skill.", + "examples": ["/craft name:Onyx necklace"], + "subOptions": [] + }, + { + "name": "gamble", + "desc": "Partake in various gambling activities.", + "subOptions": ["item", "dice", "duel", "lucky_pick", "slots", "hot_cold", "give_random_item"] + }, + { + "name": "ge", + "desc": "Exchange grandly with other players on the bot!", + "subOptions": ["buy", "sell", "my_listings", "cancel", "stats", "price", "view"] + }, + { + "name": "gear", + "desc": "Manage, equip, unequip your gear.", + "subOptions": ["equip", "unequip", "stats", "pet", "view", "swap", "best_in_slot"] + }, + { + "name": "gearpresets", + "desc": "Manage, equip, unequip your gear presets.", + "subOptions": ["view", "equip", "create", "edit", "delete"] + }, + { + "name": "gift", + "desc": "Create gifts for other users, or open one you received.", + "subOptions": ["open", "list", "create", "send"] + }, + { + "name": "giveaway", + "desc": "Giveaway items from your ban to other players.", + "examples": ["/giveaway items:10 trout, 5 coal time:1h"], + "subOptions": ["start", "list"] + }, + { + "name": "gp", + "desc": "See your current GP balance.", + "subOptions": [] + }, + { + "name": "help", + "desc": "Get information and help with the bot.", + "subOptions": [] + }, + { + "name": "hunt", + "desc": "Hunt creatures with the Hunter skill.", + "examples": ["/hunt name:Ferret"], + "subOptions": [] + }, + { + "name": "ic", + "desc": "Hand in random items for rewards.", + "subOptions": ["info", "send", "skip"] + }, + { + "name": "invention", + "desc": "The invention skill.", + "subOptions": ["disassemble", "research", "invent", "tools", "details", "materials", "group"] + }, + { + "name": "invite", + "desc": "Shows the invite link for the bot.", + "subOptions": [] + }, + { + "name": "k", + "desc": "Send your minion to kill things.", + "examples": ["/k name:zulrah"], + "subOptions": [] + }, + { + "name": "kc", + "desc": "See your OSRS kc for a monster/boss.", + "examples": ["/kc name:General Graardor"], + "subOptions": [] + }, + { + "name": "kibble", + "desc": "Make kibble from herbs and crops.", + "examples": ["/kibble name:Simple kibble quantity:100"], + "subOptions": [] + }, + { + "name": "kill", + "desc": "Simulate killing monsters.", + "subOptions": [] + }, + { + "name": "laps", + "desc": "Do laps on Agility courses to train Agility.", + "examples": ["/laps name:Ardougne rooftop course"], + "subOptions": [] + }, + { + "name": "lb", + "desc": "Simulate killing monsters.", + "subOptions": [ + "kc", + "farming_contracts", + "inferno", + "challenges", + "sacrifice", + "minigames", + "hunter_catches", + "agility_laps", + "gp", + "skills", + "opens", + "cl", + "item_contract_streak", + "leagues", + "clues", + "movers", + "global", + "completion", + "combat_achievements", + "mastery" + ] + }, + { + "name": "leagues", + "desc": "Manage your Leagues progress.", + "subOptions": ["check", "view_task", "claim", "view_all_tasks"] + }, + { + "name": "light", + "desc": "Light logs to train Firemaking.", + "examples": ["/light name:Logs"], + "subOptions": [] + }, + { + "name": "loot", + "desc": "View your loot tracker data.", + "examples": ["/loot view name:Nex"], + "subOptions": ["view", "reset"] + }, + { + "name": "lottery", + "desc": "Win big!", + "subOptions": ["buy_tickets", "deposit_items", "info", "prices"] + }, + { + "name": "m", + "desc": "See your current minion status and helpful buttons.", + "subOptions": [] + }, + { + "name": "mass", + "desc": "Arrange to mass bosses, killing them as a group.", + "examples": ["/mass name:General graardor"], + "subOptions": [] + }, + { + "name": "megaduck", + "desc": "Mega duck!.", + "subOptions": [] + }, + { + "name": "mine", + "desc": "Send your minion to mine things.", + "examples": ["/mine name:Runite ore"], + "subOptions": [] + }, + { + "name": "minigames", + "desc": "Send your minion to do various minigames.", + "subOptions": [ + "barb_assault", + "castle_wars", + "lms", + "pest_control", + "fishing_trawler", + "mage_arena", + "mage_arena_2", + "gnome_restaurant", + "temple_trek", + "sepulchre", + "gauntlet", + "mage_training_arena", + "mahogany_homes", + "tears_of_guthix", + "pyramid_plunder", + "rogues_den", + "soul_wars", + "volcanic_mine", + "agility_arena", + "trouble_brewing", + "giants_foundry", + "gotr", + "nmz", + "shades_of_morton" + ] + }, + { + "name": "minion", + "desc": "Manage and control your minion.", + "subOptions": [ + "buy", + "status", + "cracker", + "stats", + "achievementdiary", + "bankbg", + "lamp", + "cancel", + "set_icon", + "set_name", + "level", + "kc", + "ironman", + "charge", + "daily", + "train", + "pat", + "blowpipe", + "info", + "peak", + "feed_hammy", + "mastery" + ] + }, + { + "name": "mix", + "desc": "Mix potions to train Herblore.", + "examples": ["/mix name:Prayer potion"], + "subOptions": [] + }, + { + "name": "nursery", + "desc": "Manage your tame nursery.", + "subOptions": ["build", "fuel", "add_egg", "check"] + }, + { + "name": "offer", + "desc": "Offer bones or bird eggs.", + "examples": ["/offer name:Dragon bones"], + "subOptions": [] + }, + { + "name": "open", + "desc": "Open an item (caskets, keys, boxes, etc).", + "subOptions": [] + }, + { + "name": "paint", + "desc": "Paint an item.", + "subOptions": [] + }, + { + "name": "patreon", + "desc": "Shows the patreon link for the bot, where you can donate.", + "subOptions": [] + }, + { + "name": "pay", + "desc": "Send GP to another user.", + "subOptions": [] + }, + { + "name": "poh", + "desc": "Allows you to access and build in your POH.", + "examples": ["/poh build:Demonic throne"], + "subOptions": ["view", "wallkit", "build", "destroy", "mount_item", "items"] + }, + { + "name": "poll", + "desc": "Create a reaction poll.", + "subOptions": [] + }, + { + "name": "price", + "desc": "Looks up the price of an item.", + "subOptions": [] + }, + { + "name": "raid", + "desc": "Send your minion to do raids - CoX or ToB.", + "subOptions": ["cox", "tob", "toa", "doa"] + }, + { + "name": "rates", + "desc": "Check rates of various skills/activities.", + "subOptions": ["minigames", "tames", "xphr", "monster", "misc"] + }, + { + "name": "redeem", + "desc": "Redeem a code you received.", + "subOptions": [] + }, + { + "name": "roll", + "desc": "Roll a random number from 1, up to a limit.", + "subOptions": [] + }, + { + "name": "rp", + "desc": "Admin tools second set", + "subOptions": ["action", "player", "user_event"] + }, + { + "name": "runecraft", + "desc": "Sends your minion to craft runes with essence, or craft tiaras.", + "examples": ["/runecraft"], + "flags": ["minion", "skilling"], + "subOptions": [] + }, + { + "name": "sacrifice", + "desc": "Sacrifice items from your bank to the bot.", + "examples": ["/sacrifice items:10k trout, 5 coal"], + "flags": ["minion"], + "subOptions": [] + }, + { + "name": "sell", + "desc": "Sell items from your bank to the bot for GP.", + "examples": ["/sell items:10k trout, 5 coal"], + "flags": ["minion"], + "subOptions": [] + }, + { + "name": "simulate", + "desc": "Simulate various OSRS related things.", + "examples": ["/simulate cox quantity:1"], + "subOptions": ["cox", "petroll", "colosseum"] + }, + { + "name": "slayer", + "desc": "Slayer skill commands", + "subOptions": ["autoslay", "new_task", "manage", "rewards", "status"] + }, + { + "name": "smelt", + "desc": "Smelt ores/items.", + "examples": ["/smelt runite bar", "/smelt runite bar [quantity: 1]"], + "subOptions": [] + }, + { + "name": "smith", + "desc": "Smith things using the Smithing skill.", + "examples": ["/smith name:Bronze platebody"], + "subOptions": [] + }, + { + "name": "steal", + "desc": "Sends your minion to steal to train Thieving.", + "examples": ["/steal name:Man"], + "subOptions": [] + }, + { + "name": "tames", + "desc": "Manage your tames.", + "examples": ["/tames select 1"], + "subOptions": [ + "status", + "list", + "set_name", + "cancel", + "merge", + "feed", + "kill", + "collect", + "select", + "view", + "equip", + "unequip", + "cast", + "activity", + "clue", + "set_custom_image" + ] + }, + { + "name": "testershop", + "desc": "Buy things using your testing points.", + "subOptions": [] + }, + { + "name": "tokkulshop", + "desc": "Buy or sell items from the Tzhaar shops.", + "examples": ["/tokkulshop buy Obsidian platebody", "/tokkulshop sell Chaos rune 5000"], + "flags": ["minion"], + "subOptions": ["buy", "sell"] + }, + { + "name": "tools", + "desc": "Various tools and miscellaneous commands.", + "subOptions": ["patron", "user", "stash_units"] + }, + { + "name": "trade", + "desc": "Allows you to trade items with other players.", + "subOptions": [] + }, + { + "name": "trivia", + "desc": "Try to answer a random trivia question!", + "examples": ["/trivia", "/trivia duel:@Magnaboy"], + "subOptions": [] + }, + { + "name": "use", + "desc": "Use items/things.", + "examples": ["/use name:Mithril seeds"], + "subOptions": [] + } +] diff --git a/src/lib/data/buyables/aerialFishBuyables.ts b/src/lib/data/buyables/aerialFishBuyables.ts index a92948164c5..ee1751df4b0 100644 --- a/src/lib/data/buyables/aerialFishBuyables.ts +++ b/src/lib/data/buyables/aerialFishBuyables.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { Buyable } from './buyables'; +import type { Buyable } from './buyables'; export const aerialFishBuyables: Buyable[] = [ { diff --git a/src/lib/data/buyables/bsoBuyables.ts b/src/lib/data/buyables/bsoBuyables.ts index e182cf343eb..813450f75b4 100644 --- a/src/lib/data/buyables/bsoBuyables.ts +++ b/src/lib/data/buyables/bsoBuyables.ts @@ -1,11 +1,11 @@ -import { mentionCommand } from '@oldschoolgg/toolkit'; +import { isAtleastThisOld, mentionCommand } from '@oldschoolgg/toolkit'; import { Time } from 'e'; import { Bank } from 'oldschooljs'; import { calculateCompCapeProgress } from '../../bso/calculateCompCapeProgress'; import { compCapeCreatableBank } from '../../skilling/skillcapes'; import { isAtleastThisOld } from '../../util'; -import { Buyable } from './buyables'; +import type { Buyable } from './buyables'; import { circusBuyables } from './circusBuyables'; import { fistOfGuthixBuyables } from './fistOfGuthixBuyables'; import { keyCrateBuyables } from './keyCrateBuyables'; @@ -106,7 +106,7 @@ export const bsoBuyables: Buyable[] = [ outputItems: new Bank().add('Completionist cape').add('Completionist hood'), itemCost: compCapeCreatableBank, customReq: async user => { - let { totalPercentUntrimmed } = await calculateCompCapeProgress(user); + const { totalPercentUntrimmed } = await calculateCompCapeProgress(user); if (totalPercentUntrimmed < 100) { return [ false, @@ -127,7 +127,7 @@ export const bsoBuyables: Buyable[] = [ outputItems: new Bank().add('Completionist cape (t)').add('Completionist hood (t)'), itemCost: new Bank().add('Completionist cape').add('Completionist hood'), customReq: async user => { - let { totalPercentTrimmed } = await calculateCompCapeProgress(user); + const { totalPercentTrimmed } = await calculateCompCapeProgress(user); if (totalPercentTrimmed < 100) { return [ false, diff --git a/src/lib/data/buyables/buyables.ts b/src/lib/data/buyables/buyables.ts index e4261297497..5e2d986b100 100644 --- a/src/lib/data/buyables/buyables.ts +++ b/src/lib/data/buyables/buyables.ts @@ -1,11 +1,11 @@ import { Bank } from 'oldschooljs'; -import { QuestID } from '../../../mahoji/lib/abstracted_commands/questCommand'; import { chompyHats } from '../../constants'; -import { MinigameName } from '../../settings/settings'; +import { QuestID } from '../../minions/data/quests'; +import type { MinigameName } from '../../settings/minigames'; import { soteSkillRequirements } from '../../skilling/functions/questRequirements'; -import { MUserStats } from '../../structures/MUserStats'; -import { Skills } from '../../types'; +import type { MUserStats } from '../../structures/MUserStats'; +import type { Skills } from '../../types'; import { allTeamCapes } from '../misc'; import { aerialFishBuyables } from './aerialFishBuyables'; import { bsoBuyables } from './bsoBuyables'; @@ -494,7 +494,7 @@ const questBuyables: Buyable[] = [ { name: 'Monkey', outputItems: new Bank({ - 19_556: 1 + 19556: 1 }), gpCost: 1_000_000, qpRequired: 182 @@ -805,7 +805,7 @@ const Buyables: Buyable[] = [ name: 'Feather', aliases: ['feather'], gpCost: 50, - ironmanPrice: 3 + ironmanPrice: 4 }, { name: 'Shield right half', @@ -1142,6 +1142,15 @@ const Buyables: Buyable[] = [ return toaKCs.expertKC >= 25 ? [true] : [false, 'You need a 25 Expert KC in Tombs of Amascut to buy this.']; } }, + { + name: 'Lockpick', + gpCost: 5000, + ironmanPrice: 500, + skillsNeeded: { + agility: 50, + thieving: 50 + } + }, ...sepulchreBuyables, ...constructionBuyables, ...hunterBuyables, @@ -1194,4 +1203,3 @@ for (const cape of allTeamCapes) { } export default Buyables; -export { Buyables }; diff --git a/src/lib/data/buyables/canifisClothes.ts b/src/lib/data/buyables/canifisClothes.ts index c26c71bf01f..9cbe9d7ac89 100644 --- a/src/lib/data/buyables/canifisClothes.ts +++ b/src/lib/data/buyables/canifisClothes.ts @@ -1,4 +1,4 @@ -import { Buyable } from './buyables'; +import type { Buyable } from './buyables'; export const canifisClothes: Buyable[] = [ { diff --git a/src/lib/data/buyables/capes.ts b/src/lib/data/buyables/capes.ts index 642ee2b2f37..b7e5bf99c6c 100644 --- a/src/lib/data/buyables/capes.ts +++ b/src/lib/data/buyables/capes.ts @@ -1,10 +1,11 @@ import { Bank } from 'oldschooljs'; -import { MAX_QP } from '../../../mahoji/lib/abstracted_commands/questCommand'; import { expertCapesSource } from '../../bso/expertCapes'; import { diaries, userhasDiaryTier } from '../../diaries'; +import { MAX_QP } from '../../minions/data/quests'; import { musicCapeRequirements } from '../../musicCape'; -import { Buyable } from './buyables'; +import { Requirements } from '../../structures/Requirements'; +import type { Buyable } from './buyables'; export const capeBuyables: Buyable[] = [ { @@ -88,7 +89,7 @@ export const capeBuyables: Buyable[] = [ }), gpCost: 99_000, customReq: async user => { - const meetsReqs = await musicCapeRequirements.check(user); + const meetsReqs = await musicCapeRequirements.check(await Requirements.fetchRequiredData(user)); if (!meetsReqs.hasAll) { return [false, `You don't meet the requirements to buy this: \n${meetsReqs.rendered}`]; } @@ -102,13 +103,12 @@ export const capeBuyables: Buyable[] = [ }), gpCost: 99_000, customReq: async user => { - const meetsReqs = await musicCapeRequirements.check(user); - if (!meetsReqs.hasAll) { - return [false, `You don't meet the requirements to buy this: \n${meetsReqs.rendered}`]; - } if (user.QP < MAX_QP) { return [false, "You can't buy this because you haven't completed all the quests!"]; } + if (!user.cl.has('Music cape')) { + return [false, 'You need to own the regular Music cape first.']; + } for (const diary of diaries.map(d => d.elite)) { const [has] = await userhasDiaryTier(user, diary); if (!has) { diff --git a/src/lib/data/buyables/castleWars.ts b/src/lib/data/buyables/castleWars.ts index bf416158d2c..f15dbeb78ab 100644 --- a/src/lib/data/buyables/castleWars.ts +++ b/src/lib/data/buyables/castleWars.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { Buyable } from './buyables'; +import type { Buyable } from './buyables'; const items = [ ['Red decorative full helm', 5], diff --git a/src/lib/data/buyables/circusBuyables.ts b/src/lib/data/buyables/circusBuyables.ts index fc58b05005e..43ebf7c287f 100644 --- a/src/lib/data/buyables/circusBuyables.ts +++ b/src/lib/data/buyables/circusBuyables.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { Buyable } from './buyables'; +import type { Buyable } from './buyables'; export const circusBuyables: Buyable[] = [ { diff --git a/src/lib/data/buyables/fistOfGuthixBuyables.ts b/src/lib/data/buyables/fistOfGuthixBuyables.ts index 8e295396baa..f0cf6a28a92 100644 --- a/src/lib/data/buyables/fistOfGuthixBuyables.ts +++ b/src/lib/data/buyables/fistOfGuthixBuyables.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { Buyable } from './buyables'; +import type { Buyable } from './buyables'; export const fistOfGuthixBuyables: Buyable[] = [ { diff --git a/src/lib/data/buyables/forestryBuyables.ts b/src/lib/data/buyables/forestryBuyables.ts index 31f5cd49833..5522eb2460d 100644 --- a/src/lib/data/buyables/forestryBuyables.ts +++ b/src/lib/data/buyables/forestryBuyables.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { Buyable } from './buyables'; +import type { Buyable } from './buyables'; export const forestryBuyables: Buyable[] = [ { diff --git a/src/lib/data/buyables/frem.ts b/src/lib/data/buyables/frem.ts index fac910330c7..be0d01574be 100644 --- a/src/lib/data/buyables/frem.ts +++ b/src/lib/data/buyables/frem.ts @@ -1,4 +1,4 @@ -import { Buyable } from './buyables'; +import type { Buyable } from './buyables'; export const fremennikClothes: Buyable[] = [ { diff --git a/src/lib/data/buyables/gnomeClothes.ts b/src/lib/data/buyables/gnomeClothes.ts index c79d04db476..71407041932 100644 --- a/src/lib/data/buyables/gnomeClothes.ts +++ b/src/lib/data/buyables/gnomeClothes.ts @@ -1,4 +1,4 @@ -import { Buyable } from './buyables'; +import type { Buyable } from './buyables'; export const gnomeClothes: Buyable[] = [ { diff --git a/src/lib/data/buyables/guardiansOfTheRifBuyables.ts b/src/lib/data/buyables/guardiansOfTheRifBuyables.ts index 34136adbd9a..26b49f906d3 100644 --- a/src/lib/data/buyables/guardiansOfTheRifBuyables.ts +++ b/src/lib/data/buyables/guardiansOfTheRifBuyables.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { Buyable } from './buyables'; +import type { Buyable } from './buyables'; export const guardiansOfTheRiftBuyables: Buyable[] = [ { diff --git a/src/lib/data/buyables/keyCrateBuyables.ts b/src/lib/data/buyables/keyCrateBuyables.ts index d21300d1888..7d07ac77ca3 100644 --- a/src/lib/data/buyables/keyCrateBuyables.ts +++ b/src/lib/data/buyables/keyCrateBuyables.ts @@ -1,5 +1,5 @@ import { keyCrates } from '../../keyCrates'; -import { Buyable } from './buyables'; +import type { Buyable } from './buyables'; export const keyCrateBuyables: Buyable[] = []; diff --git a/src/lib/data/buyables/mairinsMarketBuyables.ts b/src/lib/data/buyables/mairinsMarketBuyables.ts index 218ffcfa5d2..f16079085dc 100644 --- a/src/lib/data/buyables/mairinsMarketBuyables.ts +++ b/src/lib/data/buyables/mairinsMarketBuyables.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { Buyable } from './buyables'; +import type { Buyable } from './buyables'; export const mairinsMarketBuyables: Buyable[] = [ { diff --git a/src/lib/data/buyables/mining.ts b/src/lib/data/buyables/mining.ts index 2359b3ca603..0de8307b0af 100644 --- a/src/lib/data/buyables/mining.ts +++ b/src/lib/data/buyables/mining.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { Buyable } from './buyables'; +import type { Buyable } from './buyables'; export const miningBuyables: Buyable[] = [ { diff --git a/src/lib/data/buyables/monkeyRumbleBuyables.ts b/src/lib/data/buyables/monkeyRumbleBuyables.ts index 7dc0c050787..904a671dba3 100644 --- a/src/lib/data/buyables/monkeyRumbleBuyables.ts +++ b/src/lib/data/buyables/monkeyRumbleBuyables.ts @@ -2,7 +2,7 @@ import { Bank } from 'oldschooljs'; import { monkeyTiers } from '../../monkeyRumble'; import getOSItem from '../../util/getOSItem'; -import { Buyable } from './buyables'; +import type { Buyable } from './buyables'; export const monkeyRumbleBuyables: Buyable[] = [ { diff --git a/src/lib/data/buyables/perdu.ts b/src/lib/data/buyables/perdu.ts index e82c32f261d..634187522e2 100644 --- a/src/lib/data/buyables/perdu.ts +++ b/src/lib/data/buyables/perdu.ts @@ -3,7 +3,7 @@ import { Bank } from 'oldschooljs'; import { itemNameFromID } from '../../util'; import resolveItems from '../../util/resolveItems'; import { diariesCL } from '../CollectionsExport'; -import { Buyable } from './buyables'; +import type { Buyable } from './buyables'; export const perduBuyables: Buyable[] = diariesCL.map(itemName => ({ name: itemNameFromID(itemName)!, diff --git a/src/lib/data/buyables/runes.ts b/src/lib/data/buyables/runes.ts index 328bf40af0d..507654200c6 100644 --- a/src/lib/data/buyables/runes.ts +++ b/src/lib/data/buyables/runes.ts @@ -1,4 +1,4 @@ -import { Buyable } from './buyables'; +import type { Buyable } from './buyables'; export const runeBuyables: Buyable[] = [ { diff --git a/src/lib/data/buyables/shootingStarsBuyables.ts b/src/lib/data/buyables/shootingStarsBuyables.ts index ad01c2c4e75..3edd6df8f1e 100644 --- a/src/lib/data/buyables/shootingStarsBuyables.ts +++ b/src/lib/data/buyables/shootingStarsBuyables.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { Buyable } from './buyables'; +import type { Buyable } from './buyables'; export const shootingStarsBuyables: Buyable[] = [ { diff --git a/src/lib/data/buyables/skillCapeBuyables.ts b/src/lib/data/buyables/skillCapeBuyables.ts index eb4489126e4..81bca7cb2ba 100644 --- a/src/lib/data/buyables/skillCapeBuyables.ts +++ b/src/lib/data/buyables/skillCapeBuyables.ts @@ -2,7 +2,7 @@ import { toTitleCase } from '@oldschoolgg/toolkit'; import { Bank } from 'oldschooljs'; import Skillcapes from '../../skilling/skillcapes'; -import { Buyable } from './buyables'; +import type { Buyable } from './buyables'; export const skillCapeBuyables: Buyable[] = []; @@ -12,7 +12,7 @@ for (const skillcape of Skillcapes) { outputItems: (user: MUser) => { const output = new Bank().add(skillcape.hood); - if (user.countSkillsAtleast99() > 1) { + if (user.countSkillsAtLeast99() > 1) { output.add(skillcape.trimmed); } else { output.add(skillcape.untrimmed); diff --git a/src/lib/data/buyables/slayerBuyables.ts b/src/lib/data/buyables/slayerBuyables.ts index f76eb9bbdf3..5b3be3554e8 100644 --- a/src/lib/data/buyables/slayerBuyables.ts +++ b/src/lib/data/buyables/slayerBuyables.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { Buyable } from './buyables'; +import type { Buyable } from './buyables'; // Most prices >=10k are x10, < 10k = x100 export const slayerBuyables: Buyable[] = [ diff --git a/src/lib/data/buyables/stealingCreationBuyables.ts b/src/lib/data/buyables/stealingCreationBuyables.ts index 196f9a6b2a3..05de23bd5bf 100644 --- a/src/lib/data/buyables/stealingCreationBuyables.ts +++ b/src/lib/data/buyables/stealingCreationBuyables.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { Buyable } from './buyables'; +import type { Buyable } from './buyables'; export const stealingCreationBuyables: Buyable[] = [ { diff --git a/src/lib/data/buyables/titheFarmBuyables.ts b/src/lib/data/buyables/titheFarmBuyables.ts index ddd808241e9..72f211ba879 100644 --- a/src/lib/data/buyables/titheFarmBuyables.ts +++ b/src/lib/data/buyables/titheFarmBuyables.ts @@ -1,4 +1,4 @@ -import { ItemBank } from '../../types'; +import type { ItemBank } from '../../types'; import itemID from '../../util/itemID'; interface TitheFarmBuyable { diff --git a/src/lib/data/buyables/troubleBrewingShop.ts b/src/lib/data/buyables/troubleBrewingShop.ts index e08081619b8..4cd98c3281a 100644 --- a/src/lib/data/buyables/troubleBrewingShop.ts +++ b/src/lib/data/buyables/troubleBrewingShop.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { Buyable } from './buyables'; +import type { Buyable } from './buyables'; export const troubleBrewingBuyables: Buyable[] = [ { diff --git a/src/lib/data/buyables/veteranCapeBuyables.ts b/src/lib/data/buyables/veteranCapeBuyables.ts index 6c4f9a04bcc..df4a03d3915 100644 --- a/src/lib/data/buyables/veteranCapeBuyables.ts +++ b/src/lib/data/buyables/veteranCapeBuyables.ts @@ -3,7 +3,7 @@ import { Time } from 'e'; import { Bank } from 'oldschooljs'; import getOSItem from '../../util/getOSItem'; -import { Buyable } from './buyables'; +import type { Buyable } from './buyables'; const veteranCapeSrc = [ { diff --git a/src/lib/data/cox.ts b/src/lib/data/cox.ts index 86ace373a47..6f5bbe69aaa 100644 --- a/src/lib/data/cox.ts +++ b/src/lib/data/cox.ts @@ -1,29 +1,28 @@ import { + Time, calcPercentOfNum, calcWhatPercent, increaseNumByPercent, percentChance, randInt, reduceNumByPercent, - shuffleArr, - Time + shuffleArr } from 'e'; import { Bank } from 'oldschooljs'; -import { Item } from 'oldschooljs/dist/meta/types'; -import { ChambersOfXericOptions } from 'oldschooljs/dist/simulation/misc/ChambersOfXeric'; +import type { Item } from 'oldschooljs/dist/meta/types'; +import type { ChambersOfXericOptions } from 'oldschooljs/dist/simulation/misc/ChambersOfXeric'; import { checkUserCanUseDegradeableItem } from '../degradeableItems'; -import { GearStats } from '../gear/types'; +import type { GearStats } from '../gear'; import { inventionBoosts } from '../invention/inventions'; -import { getMinigameScore } from '../settings/minigames'; import { SkillsEnum } from '../skilling/types'; -import { constructGearSetup, Gear } from '../structures/Gear'; -import { Skills } from '../types'; +import { Gear, constructGearSetup } from '../structures/Gear'; +import type { Skills } from '../types'; import { randomVariation } from '../util'; import getOSItem from '../util/getOSItem'; import { logError } from '../util/logError'; -export const bareMinStats: Skills = { +const bareMinStats: Skills = { attack: 80, strength: 80, defence: 80, @@ -57,7 +56,7 @@ export async function createTeam( users: MUser[], cm: boolean ): Promise> { - let res = []; + const res = []; const isSolo = users.length === 1; for (const u of users) { @@ -75,7 +74,7 @@ export async function createTeam( deathChance -= calcPercentOfNum(total, 10); } - const kc = await getMinigameScore(u.id, cm ? 'raids_challenge_mode' : 'raids'); + const kc = (await u.fetchMinigames())[cm ? 'raids_challenge_mode' : 'raids']; const kcChange = kcPointsEffect(kc); if (kcChange < 0) points = reduceNumByPercent(points, Math.abs(kcChange)); else points = increaseNumByPercent(points, kcChange); @@ -148,14 +147,8 @@ export function calcSetupPercent( } // For melee compare the highest melee attack stat of max setup with the highest melee attack stat of the user if (melee) { - let maxMeleeStat = Math.max( - maxStats['attack_stab'], - Math.max(maxStats['attack_slash'], maxStats['attack_crush']) - ); - let userMeleeStat = Math.max( - userStats['attack_stab'], - Math.max(userStats['attack_slash'], userStats['attack_crush']) - ); + const maxMeleeStat = Math.max(maxStats.attack_stab, Math.max(maxStats.attack_slash, maxStats.attack_crush)); + const userMeleeStat = Math.max(userStats.attack_stab, Math.max(userStats.attack_slash, userStats.attack_crush)); totalPercent += Math.min(100, calcWhatPercent(userMeleeStat, maxMeleeStat)); numKeys++; } @@ -245,14 +238,14 @@ export const minimumCoxSuppliesNeeded = new Bank({ 'Super restore(4)': 5 }); -export async function checkCoxTeam(users: MUser[], cm: boolean, quantity: number = 1): Promise { +export async function checkCoxTeam(users: MUser[], cm: boolean, quantity = 1): Promise { const hasHerbalist = users.some(u => u.skillLevel(SkillsEnum.Herblore) >= 78); if (!hasHerbalist) { - return 'nobody with atleast level 78 Herblore'; + return 'nobody with at least level 78 Herblore'; } const hasFarmer = users.some(u => u.skillLevel(SkillsEnum.Farming) >= 55); if (!hasFarmer) { - return 'nobody with atleast level 55 Farming'; + return 'nobody with at least level 55 Farming'; } const suppliesNeeded = minimumCoxSuppliesNeeded.clone().multiply(quantity); const userWithoutSupplies = users.find(u => !u.bank.has(suppliesNeeded)); @@ -281,7 +274,7 @@ export async function checkCoxTeam(users: MUser[], cm: boolean, quantity: number ) { return `${user.usernameOrMention} doesn't own a Twisted bow, Zaryte bow, Bow of faerdhinen (c) or Dragon hunter crossbow, which is required for Challenge Mode.`; } - const kc = await getMinigameScore(user.id, 'raids'); + const kc = (await user.fetchMinigames()).raids; if (kc < 200) { return `${user.usernameOrMention} doesn't have the 200 KC required for Challenge Mode.`; } @@ -505,7 +498,7 @@ export async function calcCoxDuration( let totalReduction = 0; - let reductions: Record = {}; + const reductions: Record = {}; // Track degradeable items: const degradeableItems: { item: Item; user: MUser; chargesToDegrade: number }[] = []; @@ -554,7 +547,7 @@ export async function calcCoxDuration( } }); - let perc = Math.min(100, userPercentChange / size); + const perc = Math.min(100, userPercentChange / size); totalReduction += perc; reductions[u.id] = perc; @@ -591,7 +584,7 @@ export async function calcCoxDuration( export async function calcCoxInput(u: MUser, solo: boolean) { const items = new Bank(); - const kc = await getMinigameScore(u.id, 'raids'); + const kc = await u.fetchMinigames().then(stats => stats.raids); items.add('Stamina potion(4)', solo ? 2 : 1); let brewsNeeded = Math.max(1, 8 - Math.max(1, Math.ceil((kc + 1) / 30))); diff --git a/src/lib/data/creatables/amrod.ts b/src/lib/data/creatables/amrod.ts index 41416972c7f..398216c7314 100644 --- a/src/lib/data/creatables/amrod.ts +++ b/src/lib/data/creatables/amrod.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { Createable } from '../createables'; +import type { Createable } from '../createables'; export const amrodCreatables: Createable[] = [ { diff --git a/src/lib/data/creatables/armorPacks.ts b/src/lib/data/creatables/armorPacks.ts index 942ec27297b..fcb48303dc4 100644 --- a/src/lib/data/creatables/armorPacks.ts +++ b/src/lib/data/creatables/armorPacks.ts @@ -1,9 +1,9 @@ import { Bank } from 'oldschooljs'; -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; import getOSItem from '../../util/getOSItem'; import itemID from '../../util/itemID'; -import { Createable } from '../createables'; +import type { Createable } from '../createables'; export const armorAndItemPacks: Createable[] = [ // Melee armour sets diff --git a/src/lib/data/creatables/bsoItems.ts b/src/lib/data/creatables/bsoItems.ts index b07019eb01b..d04966f66ee 100644 --- a/src/lib/data/creatables/bsoItems.ts +++ b/src/lib/data/creatables/bsoItems.ts @@ -7,14 +7,14 @@ import { MaterialBank } from '../../invention/MaterialBank'; import { nexBrokenArmorDetails } from '../../nex'; import Skillcapes from '../../skilling/skillcapes'; import { bones } from '../../skilling/skills/prayer'; -import { Bone } from '../../skilling/types'; +import type { Bone } from '../../skilling/types'; import { seaMonkeyStaves } from '../../tames'; import { assert, resolveNameBank, stringMatches } from '../../util'; import getOSItem from '../../util/getOSItem'; import itemID from '../../util/itemID'; import resolveItems from '../../util/resolveItems'; import { brokenPernixOutfit, brokenTorvaOutfit, brokenVirtusOutfit } from '../CollectionsExport'; -import { Createable } from '../createables'; +import type { Createable } from '../createables'; import { divinationCreatables } from './divinationCreatables'; import { ghostCreatables } from './ghostweaveCreatables'; import { slayerMaskCreatables } from './slayerMasks'; @@ -260,7 +260,7 @@ const brokenItems: Createable[] = [ { name: 'Fix fire cape', inputItems: { - 20_445: 1 + 20445: 1 }, outputItems: { [itemID('Fire cape')]: 1 @@ -270,7 +270,7 @@ const brokenItems: Createable[] = [ { name: 'Fix fire max cape', inputItems: { - 20_447: 1 + 20447: 1 }, outputItems: { [itemID('Fire max cape')]: 1 @@ -281,7 +281,7 @@ const brokenItems: Createable[] = [ { name: 'Fix infernal cape', inputItems: { - 21_287: 1 + 21287: 1 }, outputItems: { [itemID('Infernal cape')]: 1 @@ -292,7 +292,7 @@ const brokenItems: Createable[] = [ { name: 'Fix infernal max cape', inputItems: { - 21_289: 1 + 21289: 1 }, outputItems: { [itemID('Infernal max cape')]: 1 @@ -303,7 +303,7 @@ const brokenItems: Createable[] = [ { name: 'Fix assembler max cape', inputItems: { - 21_916: 1 + 21916: 1 }, outputItems: { [itemID('Assembler max cape')]: 1 @@ -314,7 +314,7 @@ const brokenItems: Createable[] = [ { name: 'Fix imbued saradomin cape', inputItems: { - 24_236: 1 + 24236: 1 }, outputItems: { [itemID('Imbued saradomin cape')]: 1 @@ -325,7 +325,7 @@ const brokenItems: Createable[] = [ { name: 'Fix imbued saradomin max cape', inputItems: { - 24_238: 1 + 24238: 1 }, outputItems: { [itemID('Imbued saradomin max cape')]: 1 @@ -336,7 +336,7 @@ const brokenItems: Createable[] = [ { name: 'Fix imbued guthix cape', inputItems: { - 24_240: 1 + 24240: 1 }, outputItems: { [itemID('Imbued guthix cape')]: 1 @@ -347,7 +347,7 @@ const brokenItems: Createable[] = [ { name: 'Fix imbued guthix max cape', inputItems: { - 24_242: 1 + 24242: 1 }, outputItems: { [itemID('Imbued guthix max cape')]: 1 @@ -358,7 +358,7 @@ const brokenItems: Createable[] = [ { name: 'Fix imbued zamorak cape', inputItems: { - 24_244: 1 + 24244: 1 }, outputItems: { [itemID('Imbued zamorak cape')]: 1 @@ -369,7 +369,7 @@ const brokenItems: Createable[] = [ { name: 'Fix imbued zamorak max cape', inputItems: { - 24_246: 1 + 24246: 1 }, outputItems: { [itemID('Imbued zamorak max cape')]: 1 @@ -380,7 +380,7 @@ const brokenItems: Createable[] = [ { name: 'Fix dragon defender', inputItems: { - 20_463: 1 + 20463: 1 }, outputItems: { [itemID('Dragon defender')]: 1 @@ -391,7 +391,7 @@ const brokenItems: Createable[] = [ { name: 'Fix avernic defender', inputItems: { - 22_441: 1 + 22441: 1 }, outputItems: { [itemID('Avernic defender')]: 1 @@ -402,7 +402,7 @@ const brokenItems: Createable[] = [ { name: 'Fix void knight top', inputItems: { - 20_465: 1 + 20465: 1 }, outputItems: { [itemID('Void knight top')]: 1 @@ -413,7 +413,7 @@ const brokenItems: Createable[] = [ { name: 'Fix void knight robe', inputItems: { - 20_469: 1 + 20469: 1 }, outputItems: { [itemID('Void knight robe')]: 1 @@ -424,7 +424,7 @@ const brokenItems: Createable[] = [ { name: 'Fix elite void top', inputItems: { - 20_467: 1 + 20467: 1 }, outputItems: { [itemID('Elite void top')]: 1 @@ -435,7 +435,7 @@ const brokenItems: Createable[] = [ { name: 'Fix elite void robe', inputItems: { - 20_471: 1 + 20471: 1 }, outputItems: { [itemID('Elite void robe')]: 1 @@ -446,7 +446,7 @@ const brokenItems: Createable[] = [ { name: 'Fix void knight gloves', inputItems: { - 20_475: 1 + 20475: 1 }, outputItems: { [itemID('Void knight gloves')]: 1 @@ -457,7 +457,7 @@ const brokenItems: Createable[] = [ { name: 'Fix void mage helm', inputItems: { - 20_477: 1 + 20477: 1 }, outputItems: { [itemID('Void mage helm')]: 1 @@ -468,7 +468,7 @@ const brokenItems: Createable[] = [ { name: 'Fix void ranger helm', inputItems: { - 20_479: 1 + 20479: 1 }, outputItems: { [itemID('Void ranger helm')]: 1 @@ -479,7 +479,7 @@ const brokenItems: Createable[] = [ { name: 'Fix void melee helm', inputItems: { - 20_481: 1 + 20481: 1 }, outputItems: { [itemID('Void melee helm')]: 1 @@ -1195,6 +1195,7 @@ const dragonBoneCreatables: Createable[] = [ const divineWaterBones = [ 'Bones', 'Big bones', + 'Jogre bones', 'Babydragon bones', 'Dragon bones', 'Wyrm bones', diff --git a/src/lib/data/creatables/caCreatables.ts b/src/lib/data/creatables/caCreatables.ts index 4e9aacdcbed..2e01875aac5 100644 --- a/src/lib/data/creatables/caCreatables.ts +++ b/src/lib/data/creatables/caCreatables.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { Createable } from '../createables'; +import type { Createable } from '../createables'; export const caCreatables: Createable[] = [ { diff --git a/src/lib/data/creatables/capes.ts b/src/lib/data/creatables/capes.ts index 3d27c928604..563ef03512a 100644 --- a/src/lib/data/creatables/capes.ts +++ b/src/lib/data/creatables/capes.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { Createable } from '../createables'; +import type { Createable } from '../createables'; export const capeCreatables: Createable[] = [ { diff --git a/src/lib/data/creatables/divinationCreatables.ts b/src/lib/data/creatables/divinationCreatables.ts index 94a9f8db8cb..e1ddb49ba37 100644 --- a/src/lib/data/creatables/divinationCreatables.ts +++ b/src/lib/data/creatables/divinationCreatables.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import { basePortentCost, calcAtomicEnergy, divinationEnergies, portents } from '../../bso/divination'; -import { Createable } from '../createables'; +import type { Createable } from '../createables'; export const divinationCreatables: Createable[] = []; diff --git a/src/lib/data/creatables/dragonfireShields.ts b/src/lib/data/creatables/dragonfireShields.ts index 64c7de6ce37..3535c014d1b 100644 --- a/src/lib/data/creatables/dragonfireShields.ts +++ b/src/lib/data/creatables/dragonfireShields.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import itemID from '../../util/itemID'; -import { Createable } from '../createables'; +import type { Createable } from '../createables'; export const dragonFireShieldCreatables: Createable[] = [ // Uncharged diff --git a/src/lib/data/creatables/dt.ts b/src/lib/data/creatables/dt.ts index 6a79aead27c..3894f8e2800 100644 --- a/src/lib/data/creatables/dt.ts +++ b/src/lib/data/creatables/dt.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { Createable } from '../createables'; +import type { Createable } from '../createables'; const bellatorRing: Createable[] = [ { diff --git a/src/lib/data/creatables/forestryCreatables.ts b/src/lib/data/creatables/forestryCreatables.ts index 7af87edf28f..1ae8457ac3f 100644 --- a/src/lib/data/creatables/forestryCreatables.ts +++ b/src/lib/data/creatables/forestryCreatables.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { Createable } from '../createables'; +import type { Createable } from '../createables'; export const forestryCreatables: Createable[] = [ { diff --git a/src/lib/data/creatables/gracefulOutfits.ts b/src/lib/data/creatables/gracefulOutfits.ts index 2bd4f638749..38689d9c6ea 100644 --- a/src/lib/data/creatables/gracefulOutfits.ts +++ b/src/lib/data/creatables/gracefulOutfits.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import itemID from '../../util/itemID'; -import { Createable } from '../createables'; +import type { Createable } from '../createables'; export const gracefulOutfitCreatables: Createable[] = [ // Normal diff --git a/src/lib/data/creatables/guardiansOfTheRiftCreatables.ts b/src/lib/data/creatables/guardiansOfTheRiftCreatables.ts index 4bd29d81f34..dee501e7522 100644 --- a/src/lib/data/creatables/guardiansOfTheRiftCreatables.ts +++ b/src/lib/data/creatables/guardiansOfTheRiftCreatables.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { Createable } from '../createables'; +import type { Createable } from '../createables'; export const guardiansOfTheRiftCreatables: Createable[] = [ // Red Recolours @@ -108,38 +108,38 @@ export const guardiansOfTheRiftCreatables: Createable[] = [ // Dye swaps { name: 'Abyssal green dye (using Abyssal blue dye)', - inputItems: new Bank({ 'Abyssal green dye': 1 }), - outputItems: new Bank({ 'Abyssal blue dye': 1 }), + inputItems: new Bank({ 'Abyssal blue dye': 1 }), + outputItems: new Bank({ 'Abyssal green dye': 1 }), noCl: true }, { name: 'Abyssal green dye (using Abyssal red dye)', - inputItems: new Bank({ 'Abyssal green dye': 1 }), - outputItems: new Bank({ 'Abyssal red dye': 1 }), + inputItems: new Bank({ 'Abyssal red dye': 1 }), + outputItems: new Bank({ 'Abyssal green dye': 1 }), noCl: true }, { name: 'Abyssal blue dye (using Abyssal green dye)', - inputItems: new Bank({ 'Abyssal blue dye': 1 }), - outputItems: new Bank({ 'Abyssal green dye': 1 }), + inputItems: new Bank({ 'Abyssal green dye': 1 }), + outputItems: new Bank({ 'Abyssal blue dye': 1 }), noCl: true }, { name: 'Abyssal blue dye (using Abyssal red dye)', - inputItems: new Bank({ 'Abyssal blue dye': 1 }), - outputItems: new Bank({ 'Abyssal red dye': 1 }), + inputItems: new Bank({ 'Abyssal red dye': 1 }), + outputItems: new Bank({ 'Abyssal blue dye': 1 }), noCl: true }, { name: 'Abyssal red dye (using Abyssal green dye)', - inputItems: new Bank({ 'Abyssal red dye': 1 }), - outputItems: new Bank({ 'Abyssal green dye': 1 }), + inputItems: new Bank({ 'Abyssal green dye': 1 }), + outputItems: new Bank({ 'Abyssal red dye': 1 }), noCl: true }, { name: 'Abyssal red dye (using Abyssal blue dye)', - inputItems: new Bank({ 'Abyssal red dye': 1 }), - outputItems: new Bank({ 'Abyssal blue dye': 1 }), + inputItems: new Bank({ 'Abyssal blue dye': 1 }), + outputItems: new Bank({ 'Abyssal red dye': 1 }), noCl: true } ]; diff --git a/src/lib/data/creatables/leagueCreatables.ts b/src/lib/data/creatables/leagueCreatables.ts index ba2100a9bb7..3160186b567 100644 --- a/src/lib/data/creatables/leagueCreatables.ts +++ b/src/lib/data/creatables/leagueCreatables.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { Createable } from '../createables'; +import type { Createable } from '../createables'; const toolCreatables: Createable[] = [ { @@ -271,7 +271,7 @@ const voidOrnaments: Createable[] = [ outputItems: new Bank().add('Elite void robe (or)') }, { - name: 'Revert Elite void robe (or))', + name: 'Revert Elite void robe (or)', inputItems: new Bank({ 'Elite void robe (or)': 1 }), diff --git a/src/lib/data/creatables/lms.ts b/src/lib/data/creatables/lms.ts index 3a92d98d55c..2cfbe06ac0b 100644 --- a/src/lib/data/creatables/lms.ts +++ b/src/lib/data/creatables/lms.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { Createable } from '../createables'; +import type { Createable } from '../createables'; export const lmsCreatables: Createable[] = [ // Granite mauls diff --git a/src/lib/data/creatables/moktangCreatables.ts b/src/lib/data/creatables/moktangCreatables.ts index 53abbd3c8df..bef95a0a7d5 100644 --- a/src/lib/data/creatables/moktangCreatables.ts +++ b/src/lib/data/creatables/moktangCreatables.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import { MaterialBank } from '../../invention/MaterialBank'; -import { Createable } from '../createables'; +import type { Createable } from '../createables'; export const moktangCreatables: Createable[] = [ { diff --git a/src/lib/data/creatables/mysticStaves.ts b/src/lib/data/creatables/mysticStaves.ts index 1079cad80cb..71d791c337d 100644 --- a/src/lib/data/creatables/mysticStaves.ts +++ b/src/lib/data/creatables/mysticStaves.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { Createable } from '../createables'; +import type { Createable } from '../createables'; export const mysticStavesCreatables: Createable[] = [ { diff --git a/src/lib/data/creatables/nex.ts b/src/lib/data/creatables/nex.ts index 1c76f31aabc..74f3b1015f3 100644 --- a/src/lib/data/creatables/nex.ts +++ b/src/lib/data/creatables/nex.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { Createable } from '../createables'; +import type { Createable } from '../createables'; export const nexCreatables: Createable[] = [ { diff --git a/src/lib/data/creatables/ornaments.ts b/src/lib/data/creatables/ornaments.ts index 8d381665b26..970c0c713fb 100644 --- a/src/lib/data/creatables/ornaments.ts +++ b/src/lib/data/creatables/ornaments.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { Createable } from '../createables'; +import type { Createable } from '../createables'; export const ornamentKits: Createable[] = [ { @@ -329,13 +329,13 @@ export const ornamentKits: Createable[] = [ 'Rune scimitar ornament kit (guthix)': 1 }), outputItems: new Bank({ - 23_330: 1 + 23330: 1 }) }, { name: 'Revert rune scimitar (guthix)', inputItems: new Bank({ - 23_330: 1 + 23330: 1 }), outputItems: new Bank({ 'Rune scimitar': 1, @@ -350,13 +350,13 @@ export const ornamentKits: Createable[] = [ 'Rune scimitar ornament kit (saradomin)': 1 }), outputItems: new Bank({ - 23_332: 1 + 23332: 1 }) }, { name: 'Revert rune scimitar (saradomin)', inputItems: new Bank({ - 23_332: 1 + 23332: 1 }), outputItems: new Bank({ 'Rune scimitar': 1, @@ -371,13 +371,13 @@ export const ornamentKits: Createable[] = [ 'Rune scimitar ornament kit (zamorak)': 1 }), outputItems: new Bank({ - 23_334: 1 + 23334: 1 }) }, { name: 'Revert rune scimitar (zamorak)', inputItems: new Bank({ - 23_334: 1 + 23334: 1 }), outputItems: new Bank({ 'Rune scimitar': 1, diff --git a/src/lib/data/creatables/shadesOfMorton.ts b/src/lib/data/creatables/shadesOfMorton.ts index 22600f10562..735a213c60c 100644 --- a/src/lib/data/creatables/shadesOfMorton.ts +++ b/src/lib/data/creatables/shadesOfMorton.ts @@ -1,7 +1,6 @@ import { Bank } from 'oldschooljs'; - import getOSItem from '../../util/getOSItem'; -import { Createable } from '../createables'; +import type { Createable } from '../createables'; export const shadesOfMortonCreatables: Createable[] = [ { diff --git a/src/lib/data/creatables/slayer.ts b/src/lib/data/creatables/slayer.ts index 6c4dcb6d6c7..ac65c98a2ef 100644 --- a/src/lib/data/creatables/slayer.ts +++ b/src/lib/data/creatables/slayer.ts @@ -2,7 +2,7 @@ import { Bank } from 'oldschooljs'; import { toKMB } from 'oldschooljs/dist/util/util'; import { SlayerTaskUnlocksEnum } from '../../slayer/slayerUnlocks'; -import { Createable } from '../createables'; +import type { Createable } from '../createables'; export const slayerCreatables: Createable[] = [ { diff --git a/src/lib/data/creatables/slayerMasks.ts b/src/lib/data/creatables/slayerMasks.ts index 3144b20234f..41ac16156a9 100644 --- a/src/lib/data/creatables/slayerMasks.ts +++ b/src/lib/data/creatables/slayerMasks.ts @@ -1,8 +1,8 @@ import { Bank } from 'oldschooljs'; import { SlayerTaskUnlocksEnum } from '../../slayer/slayerUnlocks'; -import { ItemBank } from '../../types'; -import { Createable } from '../createables'; +import type { ItemBank } from '../../types'; +import type { Createable } from '../createables'; import { slayerMaskHelms } from '../slayerMaskHelms'; export const slayerMaskCreatables: Createable[] = []; diff --git a/src/lib/data/creatables/sunMoonCreatables.ts b/src/lib/data/creatables/sunMoonCreatables.ts index 2fcfaa7a930..676e10be157 100644 --- a/src/lib/data/creatables/sunMoonCreatables.ts +++ b/src/lib/data/creatables/sunMoonCreatables.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { Createable } from '../createables'; +import type { Createable } from '../createables'; export const sunMoonCreatables: Createable[] = [ { diff --git a/src/lib/data/creatables/toa.ts b/src/lib/data/creatables/toa.ts index 685aa4ed8b4..e0bf3511a82 100644 --- a/src/lib/data/creatables/toa.ts +++ b/src/lib/data/creatables/toa.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import getOSItem from '../../util/getOSItem'; -import { Createable } from '../createables'; +import type { Createable } from '../createables'; export const toaCreatables: Createable[] = [ { diff --git a/src/lib/data/creatables/tob.ts b/src/lib/data/creatables/tob.ts index 5c00c0114ed..7ce13760011 100644 --- a/src/lib/data/creatables/tob.ts +++ b/src/lib/data/creatables/tob.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { Createable } from '../createables'; +import type { Createable } from '../createables'; export const tobCreatables: Createable[] = [ { diff --git a/src/lib/data/creatablesTable.txt b/src/lib/data/creatablesTable.txt index cf16f411594..79bc325bd86 100644 --- a/src/lib/data/creatablesTable.txt +++ b/src/lib/data/creatablesTable.txt @@ -79,6 +79,7 @@ | Kodai wand | 1x Master wand, 1x Kodai insignia | 1x Kodai wand | 0 | | Salve amulet (e) | 1x Salve amulet | 1x Salve amulet (e) | 0 | | Salve amulet(ei) | 1x Salve amulet(i) | 1x Salve amulet(ei) | 0 | +| Ring of wealth (i) | 1x Ring of wealth, 1x Ring of wealth scroll | 1x Ring of wealth (i) | 50000 | | Strange hallowed tome | 1x Mysterious page 1, 1x Mysterious page 2, 1x Mysterious page 3, 1x Mysterious page 4, 1x Mysterious page 5 | 1x Strange hallowed tome | 0 | | Frozen key | 1x Frozen key piece (armadyl), 1x Frozen key piece (bandos), 1x Frozen key piece (zamorak), 1x Frozen key piece (saradomin) | 1x Frozen key | 0 | | Ecumenical key | 50x Ecumenical key shard | 1x Ecumenical key | 0 | @@ -96,6 +97,8 @@ | Bone shortbow | 1x Yew shortbow, 1x Scurrius' spine | 1x Bone shortbow | 0 | | Bone staff | 1,000x Chaos rune, 1x Battlestaff, 1x Scurrius' spine | 1x Bone staff | 0 | | Venator bow (uncharged) | 5x Venator shard | 1x Venator bow (uncharged) | 0 | +| Blessed dizana's quiver | 150,000x Sunfire splinters, 1x Dizana's quiver (uncharged) | 1x Blessed dizana's quiver | 0 | +| Dizana's max cape | 1x Max cape, 1x Max hood, 1x Blessed dizana's quiver | 1x Dizana's max cape, 1x Dizana's max hood | 0 | | Revert tanzanite fang | 1x Tanzanite fang | 20,000x Zulrah's scales | 0 | | Revert toxic blowpipe (empty) | 1x Toxic blowpipe (empty) | 20,000x Zulrah's scales | 0 | | Revert magic fang | 1x Magic fang | 20,000x Zulrah's scales | 0 | @@ -161,6 +164,7 @@ | Revert great blue heron | 1x Great blue heron | 1x Heron | 0 | | Revert greatish guardian | 1x Greatish guardian | 1x Rift guardian, 1x Guardian's eye | 0 | | Revert xeric's talisman (inert) | 1x Xeric's talisman (inert) | 100x Lizardman fang | 0 | +| Revert Dizana's quiver (uncharged) | 1x Dizana's quiver (uncharged) | 4,000x Sunfire splinters | 0 | | Crystal pickaxe | 120x Crystal shard, 1x Dragon pickaxe, 1x Crystal tool seed | 1x Crystal pickaxe | 0 | | Crystal harpoon | 120x Crystal shard, 1x Dragon harpoon, 1x Crystal tool seed | 1x Crystal harpoon | 0 | | Crystal axe | 120x Crystal shard, 1x Dragon axe, 1x Crystal tool seed | 1x Crystal axe | 0 | @@ -825,6 +829,7 @@ | Divine water | Unknown/Dynamic | 1x Divine water | 0 | | Divine water (Bones) | Unknown/Dynamic | 1x Divine water | 0 | | Divine water (Big bones) | Unknown/Dynamic | 1x Divine water | 0 | +| Divine water (Jogre bones) | Unknown/Dynamic | 1x Divine water | 0 | | Divine water (Babydragon bones) | Unknown/Dynamic | 1x Divine water | 0 | | Divine water (Dragon bones) | Unknown/Dynamic | 1x Divine water | 0 | | Divine water (Wyrm bones) | Unknown/Dynamic | 1x Divine water | 0 | @@ -1173,7 +1178,7 @@ | Rune crossbow (or) | 1x Rune crossbow, 1x Shattered relics variety ornament kit | 1x Rune crossbow (or) | 0 | | Revert Rune crossbow (or) | 1x Rune crossbow (or) | 1x Rune crossbow, 1x Shattered relics variety ornament kit | 0 | | Elite void robe (or) | 1x Elite void robe, 1x Shattered relics void ornament kit | 1x Elite void robe (or) | 0 | -| Revert Elite void robe (or)) | 1x Elite void robe (or) | 1x Elite void robe, 1x Shattered relics void ornament kit | 0 | +| Revert Elite void robe (or) | 1x Elite void robe (or) | 1x Elite void robe, 1x Shattered relics void ornament kit | 0 | | Elite void top (or) | 1x Elite void top, 1x Shattered relics void ornament kit | 1x Elite void top (or) | 0 | | Revert Elite void top (or)) | 1x Elite void top (or) | 1x Elite void top, 1x Shattered relics void ornament kit | 0 | | Void knight gloves (or) | 1x Void knight gloves, 1x Shattered relics void ornament kit | 1x Void knight gloves (or) | 0 | @@ -1245,12 +1250,12 @@ | Revert Robe top of the eye (blue) | 1x Robe top of the eye (blue) | 1x Robe top of the eye | 0 | | Robe bottoms of the eye (blue) | 1x Abyssal blue dye, 1x Robe bottoms of the eye | 1x Robe bottoms of the eye (blue) | 0 | | Revert Robe bottoms of the eye (blue) | 1x Robe bottoms of the eye (blue) | 1x Robe bottoms of the eye | 0 | -| Abyssal green dye (using Abyssal blue dye) | 1x Abyssal green dye | 1x Abyssal blue dye | 0 | -| Abyssal green dye (using Abyssal red dye) | 1x Abyssal green dye | 1x Abyssal red dye | 0 | -| Abyssal blue dye (using Abyssal green dye) | 1x Abyssal blue dye | 1x Abyssal green dye | 0 | -| Abyssal blue dye (using Abyssal red dye) | 1x Abyssal blue dye | 1x Abyssal red dye | 0 | -| Abyssal red dye (using Abyssal green dye) | 1x Abyssal red dye | 1x Abyssal green dye | 0 | -| Abyssal red dye (using Abyssal blue dye) | 1x Abyssal red dye | 1x Abyssal blue dye | 0 | +| Abyssal green dye (using Abyssal blue dye) | 1x Abyssal blue dye | 1x Abyssal green dye | 0 | +| Abyssal green dye (using Abyssal red dye) | 1x Abyssal red dye | 1x Abyssal green dye | 0 | +| Abyssal blue dye (using Abyssal green dye) | 1x Abyssal green dye | 1x Abyssal blue dye | 0 | +| Abyssal blue dye (using Abyssal red dye) | 1x Abyssal red dye | 1x Abyssal blue dye | 0 | +| Abyssal red dye (using Abyssal green dye) | 1x Abyssal green dye | 1x Abyssal red dye | 0 | +| Abyssal red dye (using Abyssal blue dye) | 1x Abyssal blue dye | 1x Abyssal red dye | 0 | | Runite igne claws | 30x Runite bar, 10x Leather, 1x Igne gear frame | 1x Runite igne claws | 0 | | Dragon igne claws | 10x Leather, 1x Dragon claws, 1x Runite igne claws | 1x Dragon igne claws | 0 | | Barrows igne claws | 10x Leather, 1x Dragon igne claws | 1x Barrows igne claws | 0 | diff --git a/src/lib/data/createables.ts b/src/lib/data/createables.ts index 34080000ce1..f9fd0ce0891 100644 --- a/src/lib/data/createables.ts +++ b/src/lib/data/createables.ts @@ -3,10 +3,10 @@ import { Bank } from 'oldschooljs'; import { BitField, discontinuedItems } from '../constants'; import { allDyedItems } from '../dyedItems'; -import { MaterialBank } from '../invention/MaterialBank'; +import type { MaterialBank } from '../invention/MaterialBank'; import { blisterwoodRequirements, ivandisRequirements } from '../minions/data/templeTrekking'; -import { SlayerTaskUnlocksEnum } from '../slayer/slayerUnlocks'; -import { ItemBank, Skills } from '../types'; +import type { SlayerTaskUnlocksEnum } from '../slayer/slayerUnlocks'; +import type { ItemBank, Skills } from '../types'; import getOSItem from '../util/getOSItem'; import itemID from '../util/itemID'; import { itemNameFromID } from '../util/smallUtils'; @@ -1372,6 +1372,12 @@ const Reverteables: Createable[] = [ [itemID('Lizardman fang')]: 100 }, noCl: true + }, + { + name: "Revert Dizana's quiver (uncharged)", + inputItems: new Bank().add("Dizana's quiver (uncharged)"), + outputItems: new Bank().add('Sunfire splinters', 4000), + noCl: true } ]; @@ -2230,6 +2236,12 @@ const Createables: Createable[] = [ }, customReq: salveECustomReq }, + { + name: 'Ring of wealth (i)', + inputItems: new Bank().add('Ring of wealth').add('Ring of wealth scroll'), + GPCost: 50_000, + outputItems: new Bank().add('Ring of wealth (i)') + }, { name: 'Strange hallowed tome', inputItems: new Bank({ @@ -2394,6 +2406,16 @@ const Createables: Createable[] = [ inputItems: new Bank().add('Venator shard', 5).freeze(), outputItems: new Bank().add('Venator bow (uncharged)').freeze() }, + { + name: "Blessed dizana's quiver", + inputItems: new Bank().add('Sunfire splinters', 150_000).add("Dizana's quiver (uncharged)").freeze(), + outputItems: new Bank().add("Blessed dizana's quiver").freeze() + }, + { + name: "Dizana's max cape", + inputItems: new Bank().add("Blessed dizana's quiver").add('Max cape').add('Max hood').freeze(), + outputItems: new Bank().add("Dizana's max cape").add("Dizana's max hood").freeze() + }, ...Reverteables, ...crystalTools, ...ornamentKits, @@ -2433,7 +2455,6 @@ const Createables: Createable[] = [ export default Createables; export const creatablesCL = uniqueArr( Createables.filter(i => i.noCl !== true) - .map(i => (isFunction(i.outputItems) ? [] : new Bank(i.outputItems).items().map(i => i[0].id))) - .flat() + .flatMap(i => (isFunction(i.outputItems) ? [] : new Bank(i.outputItems).items().map(i => i[0].id))) .filter(i => !discontinuedItems.includes(i) && !allDyedItems.includes(i)) ); diff --git a/src/lib/data/filterables.ts b/src/lib/data/filterables.ts index 3ebf6043da6..56bfadc1598 100644 --- a/src/lib/data/filterables.ts +++ b/src/lib/data/filterables.ts @@ -7,7 +7,7 @@ import { MediumClueTable } from 'oldschooljs/dist/simulation/clues/Medium'; import { tmbTable, umbTable } from '../bsoOpenables'; import { customItems } from '../customItems/util'; -import { DisassembleFlag, disassembleFlagMaterials, materialTypes } from '../invention'; +import { type DisassembleFlag, disassembleFlagMaterials, materialTypes } from '../invention'; import { DisassemblySourceGroups } from '../invention/groups'; import Potions from '../minions/data/potions'; import { monkeyEatables } from '../monkeyRumble'; @@ -241,7 +241,7 @@ const gems = resolveItems([ 'Zenyte shard' ]); -const craftingItems = Craftables.flatMap(item => Object.keys(item.inputItems.bank).map(key => parseInt(key))); +const craftingItems = Craftables.flatMap(item => Object.keys(item.inputItems.bank).map(key => Number.parseInt(key))); const craftingItemsSet = [...new Set(craftingItems)]; @@ -365,7 +365,7 @@ export const seedsFilter = resolveItems([ const allPotions = Potions.flatMap(potion => potion.items); const potions = [...new Set(allPotions)]; -const grimyHerbs = Grimy.flatMap(grimy => Object.keys(grimy.inputItems.bank).map(key => parseInt(key))); +const grimyHerbs = Grimy.flatMap(grimy => Object.keys(grimy.inputItems.bank).map(key => Number.parseInt(key))); const cleanHerbs = Grimy.flatMap(clean => clean.item.id); cleanHerbs.push(itemID('Athelas')); @@ -375,7 +375,7 @@ const unfPots = unfinishedPotions.flatMap(unf => unf.item.id); const unfPotions = resolveItems(['Vial of water', ...new Set(unfPots)]); const allSecondaries = PotionsMixable.flatMap(item => - Object.keys(item.inputItems.bank).map(key => parseInt(key)) + Object.keys(item.inputItems.bank).map(key => Number.parseInt(key)) ).filter(item => !potions.includes(item) && !unfPotions.includes(item) && !herbs.includes(item)); export const secondaries = [...new Set(allSecondaries)]; @@ -413,7 +413,7 @@ const bones = resolveItems([ 'Frost dragon bones' ]); -const fletchingItems = Fletchables.flatMap(item => Object.keys(item.inputItems.bank).map(key => parseInt(key))); +const fletchingItems = Fletchables.flatMap(item => Object.keys(item.inputItems.bank).map(key => Number.parseInt(key))); const fletchingItemsSet = [...new Set(fletchingItems)]; @@ -962,7 +962,7 @@ export const baseFilters: Filterable[] = [ { name: 'Pets', aliases: ['pets', 'pmb'], - items: () => allPetIDs.flat(Infinity) as number[] + items: () => allPetIDs.flat(Number.POSITIVE_INFINITY) as number[] }, { name: 'Holiday', @@ -1051,7 +1051,7 @@ for (const type of materialTypes) { if (disassembleFlagMaterials.includes(type as DisassembleFlag)) { items = DisassemblySourceGroups.flatMap(group => group.items - .filter(item => item.flags && item.flags.has(type as DisassembleFlag)) + .filter(item => item.flags?.has(type as DisassembleFlag)) .flatMap(item => (Array.isArray(item.item) ? item.item.map(i => i.id) : [item.item.id])) ); } else { diff --git a/src/lib/data/itemAliases.ts b/src/lib/data/itemAliases.ts index 90330b463b2..0c46bdcfb91 100644 --- a/src/lib/data/itemAliases.ts +++ b/src/lib/data/itemAliases.ts @@ -1,8 +1,10 @@ -import '../customItems/customItems'; - -import { Items } from 'oldschooljs'; +import { deepMerge, modifyItem } from '@oldschoolgg/toolkit'; +import { omit } from 'lodash'; +import { EItem, Items } from 'oldschooljs'; +import { allTeamCapes } from 'oldschooljs/dist/data/itemConstants'; import { itemNameMap } from 'oldschooljs/dist/structures/Items'; import { cleanString } from 'oldschooljs/dist/util/cleanString'; +import { getItemOrThrow, resolveItems } from 'oldschooljs/dist/util/util'; import { customItems } from '../customItems/util'; @@ -193,12 +195,15 @@ setItemAlias(2993, 'Chompy bird hat (dragon archer)'); setItemAlias(2994, 'Chompy bird hat (expert ogre dragon archer)'); setItemAlias(2995, 'Chompy bird hat (expert dragon archer)'); -// Item aliases +// Achievement diary lamps setItemAlias(11_137, 'Antique lamp 1'); setItemAlias(11_139, 'Antique lamp 2'); setItemAlias(11_141, 'Antique lamp 3'); setItemAlias(11_185, 'Antique lamp 4'); +// Defender of varrock quest lamp +setItemAlias(28_820, 'Antique lamp (defender of varrock)'); + // Dragonfire shields setItemAlias(11_284, 'Uncharged dragonfire shield'); setItemAlias(11_283, 'Dragonfire shield'); @@ -361,3 +366,124 @@ setItemAlias(25_922, 'Antique lamp (hard ca)'); setItemAlias(25_923, 'Antique lamp (elite ca)'); setItemAlias(25_924, 'Antique lamp (master ca)'); setItemAlias(25_925, 'Antique lamp (grandmaster ca)'); + +/** + * Trophies + */ + +// BSO (Twisted) trophies +setItemAlias(24_372, 'BSO dragon trophy'); +setItemAlias(24_374, 'BSO rune trophy'); +setItemAlias(24_376, 'BSO adamant trophy'); +setItemAlias(24_378, 'BSO mithril trophy'); +setItemAlias(24_380, 'BSO steel trophy'); +setItemAlias(24_382, 'BSO iron trophy'); +setItemAlias(24_384, 'BSO bronze trophy'); + +// Comp. trophies +setItemAlias(25_042, 'Comp. dragon trophy'); +setItemAlias(25_044, 'Comp. rune trophy'); +setItemAlias(25_046, 'Comp. adamant trophy'); +setItemAlias(25_048, 'Comp. mithril trophy'); +setItemAlias(25_050, 'Comp. steel trophy'); +setItemAlias(25_052, 'Comp. iron trophy'); +setItemAlias(25_054, 'Comp. bronze trophy'); + +// Placeholder trophies +setItemAlias(26_515, 'Placeholder dragon trophy'); +setItemAlias(26_513, 'Placeholder rune trophy'); +setItemAlias(26_511, 'Placeholder adamant trophy'); +setItemAlias(26_509, 'Placeholder mithril trophy'); +setItemAlias(26_507, 'Placeholder steel trophy'); +setItemAlias(26_505, 'Placeholder iron trophy'); +setItemAlias(26_503, 'Placeholder bronze trophy'); + +export const allTrophyItems = resolveItems([ + 'BSO dragon trophy', + 'BSO rune trophy', + 'BSO adamant trophy', + 'BSO mithril trophy', + 'BSO steel trophy', + 'BSO iron trophy', + 'BSO bronze trophy', + 'Comp. dragon trophy', + 'Comp. rune trophy', + 'Comp. adamant trophy', + 'Comp. mithril trophy', + 'Comp. steel trophy', + 'Comp. iron trophy', + 'Comp. bronze trophy', + 'Placeholder dragon trophy', + 'Placeholder rune trophy', + 'Placeholder adamant trophy', + 'Placeholder mithril trophy', + 'Placeholder steel trophy', + 'Placeholder iron trophy', + 'Placeholder bronze trophy' +]); + +for (const item of allTrophyItems) { + modifyItem(item, { + tradeable: false, + tradeable_on_ge: false, + customItemData: { + cantBeSacrificed: true, + isSuperUntradeable: true, + cantDropFromMysteryBoxes: true + } + }); +} + +/** + * Item modifications + */ + +export interface CustomItemData { + cantBeSacrificed?: true; + isSuperUntradeable?: boolean; + cantDropFromMysteryBoxes?: boolean; + cantBeDropped?: true; + isDiscontinued?: true; + superTradeableButTradeableOnGE?: true; + isSecret?: true; +} + +declare module 'oldschooljs/dist/meta/types' { + interface Item { + customItemData?: CustomItemData; + } +} + +for (const item of allTeamCapes) { + modifyItem(item.id, { + price: 100 + }); + if (getItemOrThrow(item.id).price !== 100) { + throw new Error(`Failed to modify price of item ${item.id}`); + } +} + +export const itemDataSwitches = [ + { + from: 25488, + to: EItem.BELLATOR_RING + }, + { + from: 25486, + to: EItem.MAGUS_RING + }, + { + from: 25487, + to: EItem.VENATOR_RING + }, + { + from: 25485, + to: EItem.ULTOR_RING + } +]; + +for (const items of itemDataSwitches) { + const from = getItemOrThrow(items.from); + const to = getItemOrThrow(items.to); + modifyItem(to.id, deepMerge(omit(to, 'id'), omit(from, 'id'))); +} diff --git a/src/lib/data/kibble.ts b/src/lib/data/kibble.ts index 962ebaafa8d..d5fc3836379 100644 --- a/src/lib/data/kibble.ts +++ b/src/lib/data/kibble.ts @@ -1,4 +1,4 @@ -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; import getOSItem from '../util/getOSItem'; diff --git a/src/lib/data/misc.ts b/src/lib/data/misc.ts index 83bc4989480..65579392fc8 100644 --- a/src/lib/data/misc.ts +++ b/src/lib/data/misc.ts @@ -1,4 +1,4 @@ -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; import getOSItem from '../util/getOSItem'; diff --git a/src/lib/data/offerData.ts b/src/lib/data/offerData.ts index 8240d9bb9b7..b9a3cac1064 100644 --- a/src/lib/data/offerData.ts +++ b/src/lib/data/offerData.ts @@ -1,9 +1,9 @@ import LootTable from 'oldschooljs/dist/structures/LootTable'; +import { resolveItems } from 'oldschooljs/dist/util/util'; import itemID from '../util/itemID'; -import resolveItems from '../util/resolveItems'; -export interface Offerable { +interface Offerable { name: string; itemID: number; offerWhere: string; @@ -13,7 +13,7 @@ export interface Offerable { uniques?: number[]; } -export const UnsiredLootTable = new LootTable() +const UnsiredLootTable = new LootTable() .add('Abyssal head', 1, 10) .add('Abyssal orphan', 1, 5) .add('Abyssal dagger', 1, 26) @@ -21,7 +21,7 @@ export const UnsiredLootTable = new LootTable() .add('Bludgeon claw', 1, 62) .add('Jar of miasma', 1, 13); -export const ChewedBonesLootTable = new LootTable() +const ChewedBonesLootTable = new LootTable() .oneIn(256, 'Dragon full helm') .add('Adamant knife', 20, 3) .add('Adamant dart(p)', 20, 3) diff --git a/src/lib/data/osb.commands.json b/src/lib/data/osb.commands.json new file mode 100644 index 00000000000..8d06323b18e --- /dev/null +++ b/src/lib/data/osb.commands.json @@ -0,0 +1,544 @@ +[ + { + "name": "activities", + "desc": "Miscellaneous activities you can do.", + "subOptions": [ + "plank_make", + "chompy_hunt", + "champions_challenge", + "warriors_guild", + "camdozaal", + "collect", + "quest", + "decant", + "charge", + "fight_caves", + "inferno", + "birdhouses", + "aerial_fishing", + "enchant", + "bury", + "scatter", + "puro_puro", + "alch", + "cast", + "underwater", + "other" + ] + }, + { + "name": "ask", + "desc": "Ask a yes/no question to the bot and receive an answer.", + "subOptions": [] + }, + { + "name": "bank", + "desc": "See your minions bank.", + "subOptions": [] + }, + { + "name": "bingo", + "desc": "Bingo!", + "subOptions": ["make_team", "leave_team", "view", "leaderboard", "create_bingo", "manage_bingo", "items"] + }, + { + "name": "bossrecords", + "desc": "Shows your OSRS boss records.", + "subOptions": [] + }, + { + "name": "botleagues", + "desc": "Compete in the OSB/BSO Leagues.", + "subOptions": ["help", "claim_trophy", "leaderboard", "buy_reward"] + }, + { + "name": "bs", + "desc": "Search your minions bank.", + "subOptions": [] + }, + { + "name": "build", + "desc": "Sends your minion to train Construction by building things.", + "examples": ["/build name:Crude chair"], + "subOptions": [] + }, + { + "name": "buy", + "desc": "Allows you to purchase items.", + "subOptions": [] + }, + { + "name": "ca", + "desc": "Combat Achievements", + "subOptions": ["view", "claim"] + }, + { + "name": "casket", + "desc": "Simulate opening lots of clues caskets.", + "subOptions": [] + }, + { + "name": "choose", + "desc": "Have the bot make a choice from a list of things.", + "examples": ["/choose list:First option, second option, third option"], + "subOptions": [] + }, + { + "name": "chop", + "desc": "Chop logs using the Woodcutting skill.", + "examples": ["/chop name:Logs"], + "subOptions": [] + }, + { + "name": "cl", + "desc": "See your Collection Log.", + "examples": ["/cl name:Boss"], + "subOptions": [] + }, + { + "name": "claim", + "desc": "Claim prizes, rewards and other things.", + "subOptions": [] + }, + { + "name": "clue", + "desc": "Send your minion to complete clue scrolls.", + "examples": ["/clue tier:easy"], + "subOptions": [] + }, + { + "name": "clues", + "desc": "See your OSRS clue scores.", + "examples": ["/clues rsn:Magnaboy"], + "subOptions": [] + }, + { + "name": "config", + "desc": "Commands configuring settings and options.", + "subOptions": ["server", "user"] + }, + { + "name": "cook", + "desc": "Cook things using the cooking skill.", + "examples": ["/cook name:Shrimp"], + "subOptions": [] + }, + { + "name": "craft", + "desc": "Craft items with the Crafting skill.", + "examples": ["/craft name:Onyx necklace"], + "subOptions": [] + }, + { + "name": "create", + "desc": "Allows you to create items, like godswords or spirit shields - and pack barrows armor sets.", + "subOptions": [] + }, + { + "name": "data", + "desc": "View various pieces of data.", + "examples": ["/data name:Personal Activity Types"], + "subOptions": [] + }, + { + "name": "drop", + "desc": "Drop items from your bank.", + "examples": ["/drop items:10 trout, 5 coal"], + "subOptions": [] + }, + { + "name": "drycalc", + "desc": "Calculate your drystreak chance.", + "subOptions": [] + }, + { + "name": "fake", + "desc": "Generate fake images of getting loot.", + "subOptions": [] + }, + { + "name": "fakepm", + "desc": "Generate fake images of PMs.", + "subOptions": [] + }, + { + "name": "farming", + "desc": "Allows you to do Farming related things.", + "subOptions": [ + "check_patches", + "plant", + "auto_farm", + "auto_farm_filter", + "default_compost", + "always_pay", + "harvest", + "tithe_farm", + "compost_bin", + "contract" + ] + }, + { + "name": "finish", + "desc": "Simulate finishing a CL.", + "subOptions": [] + }, + { + "name": "fish", + "desc": "Send your minion to fish fish.", + "examples": ["/fish name:Shrimp"], + "subOptions": [] + }, + { + "name": "fletch", + "desc": "Fletch items with the Fletching skill.", + "examples": ["/craft name:Onyx necklace"], + "subOptions": [] + }, + { + "name": "gamble", + "desc": "Partake in various gambling activities.", + "subOptions": ["item", "dice", "duel", "lucky_pick", "slots", "hot_cold", "give_random_item"] + }, + { + "name": "ge", + "desc": "Exchange grandly with other players on the bot!", + "subOptions": ["buy", "sell", "my_listings", "cancel", "stats", "price", "view"] + }, + { + "name": "gear", + "desc": "Manage, equip, unequip your gear.", + "subOptions": ["equip", "unequip", "stats", "pet", "view", "swap"] + }, + { + "name": "gearpresets", + "desc": "Manage, equip, unequip your gear presets.", + "subOptions": ["view", "equip", "create", "edit", "delete"] + }, + { + "name": "gift", + "desc": "Create gifts for other users, or open one you received.", + "subOptions": ["open", "list", "create", "send"] + }, + { + "name": "giveaway", + "desc": "Giveaway items from your ban to other players.", + "examples": ["/giveaway items:10 trout, 5 coal time:1h"], + "subOptions": ["start", "list"] + }, + { + "name": "gp", + "desc": "See your current GP balance.", + "subOptions": [] + }, + { + "name": "help", + "desc": "Get information and help with the bot.", + "subOptions": [] + }, + { + "name": "hunt", + "desc": "Hunt creatures with the Hunter skill.", + "examples": ["/hunt name:Ferret"], + "subOptions": [] + }, + { + "name": "invite", + "desc": "Shows the invite link for the bot.", + "subOptions": [] + }, + { + "name": "k", + "desc": "Send your minion to kill things.", + "examples": ["/k name:zulrah"], + "subOptions": [] + }, + { + "name": "kc", + "desc": "See your OSRS kc for a monster/boss.", + "examples": ["/kc name:General Graardor"], + "subOptions": [] + }, + { + "name": "kill", + "desc": "Simulate killing monsters.", + "subOptions": [] + }, + { + "name": "laps", + "desc": "Do laps on Agility courses to train Agility.", + "examples": ["/laps name:Ardougne rooftop course"], + "subOptions": [] + }, + { + "name": "lb", + "desc": "Simulate killing monsters.", + "subOptions": [ + "kc", + "farming_contracts", + "inferno", + "sacrifice", + "minigames", + "hunter_catches", + "agility_laps", + "gp", + "skills", + "opens", + "cl", + "clues", + "movers", + "global", + "combat_achievements", + "mastery" + ] + }, + { + "name": "light", + "desc": "Light logs to train Firemaking.", + "examples": ["/light name:Logs"], + "subOptions": [] + }, + { + "name": "loot", + "desc": "View your loot tracker data.", + "examples": ["/loot view name:Nex"], + "subOptions": ["view", "reset"] + }, + { + "name": "lvl", + "desc": "See a level in your OSRS stats.", + "examples": ["/lvl rsn:Magnaboy skill:Mining"], + "subOptions": [] + }, + { + "name": "m", + "desc": "See your current minion status and helpful buttons.", + "subOptions": [] + }, + { + "name": "mass", + "desc": "Arrange to mass bosses, killing them as a group.", + "examples": ["/mass name:General graardor"], + "subOptions": [] + }, + { + "name": "mine", + "desc": "Send your minion to mine things.", + "examples": ["/mine name:Runite ore"], + "subOptions": [] + }, + { + "name": "minigames", + "desc": "Send your minion to do various minigames.", + "subOptions": [ + "barb_assault", + "castle_wars", + "lms", + "pest_control", + "fishing_trawler", + "mage_arena", + "mage_arena_2", + "gnome_restaurant", + "temple_trek", + "sepulchre", + "gauntlet", + "mage_training_arena", + "mahogany_homes", + "tears_of_guthix", + "pyramid_plunder", + "rogues_den", + "soul_wars", + "volcanic_mine", + "agility_arena", + "trouble_brewing", + "giants_foundry", + "gotr", + "nmz", + "shades_of_morton" + ] + }, + { + "name": "minion", + "desc": "Manage and control your minion.", + "subOptions": [ + "buy", + "status", + "cracker", + "stats", + "achievementdiary", + "bankbg", + "lamp", + "cancel", + "set_icon", + "set_name", + "level", + "kc", + "ironman", + "charge", + "daily", + "train", + "pat", + "blowpipe", + "info", + "peak", + "mastery" + ] + }, + { + "name": "mix", + "desc": "Mix potions to train Herblore.", + "examples": ["/mix name:Prayer potion"], + "subOptions": [] + }, + { + "name": "offer", + "desc": "Offer bones or bird eggs.", + "examples": ["/offer name:Dragon bones"], + "subOptions": [] + }, + { + "name": "open", + "desc": "Open an item (caskets, keys, boxes, etc).", + "subOptions": [] + }, + { + "name": "patreon", + "desc": "Shows the patreon link for the bot, where you can donate.", + "subOptions": [] + }, + { + "name": "pay", + "desc": "Send GP to another user.", + "subOptions": [] + }, + { + "name": "poh", + "desc": "Allows you to access and build in your POH.", + "examples": ["/poh build:Demonic throne"], + "subOptions": ["view", "wallkit", "build", "destroy", "mount_item", "items"] + }, + { + "name": "poll", + "desc": "Create a reaction poll.", + "subOptions": [] + }, + { + "name": "price", + "desc": "Looks up the price of an item.", + "subOptions": [] + }, + { + "name": "raid", + "desc": "Send your minion to do raids - CoX or ToB.", + "subOptions": ["cox", "tob", "toa"] + }, + { + "name": "redeem", + "desc": "Redeem a code you received.", + "subOptions": [] + }, + { + "name": "roll", + "desc": "Roll a random number from 1, up to a limit.", + "subOptions": [] + }, + { + "name": "rp", + "desc": "Admin tools second set", + "subOptions": ["action", "player", "user_event"] + }, + { + "name": "runecraft", + "desc": "Sends your minion to craft runes with essence, or craft tiaras.", + "examples": ["/runecraft"], + "flags": ["minion", "skilling"], + "subOptions": [] + }, + { + "name": "sacrifice", + "desc": "Sacrifice items from your bank to the bot.", + "examples": ["/sacrifice items:10k trout, 5 coal"], + "flags": ["minion"], + "subOptions": [] + }, + { + "name": "sell", + "desc": "Sell items from your bank to the bot for GP.", + "examples": ["/sell items:10k trout, 5 coal"], + "flags": ["minion"], + "subOptions": [] + }, + { + "name": "simulate", + "desc": "Simulate various OSRS related things.", + "examples": ["/simulate cox quantity:1"], + "subOptions": ["cox", "petroll", "colosseum"] + }, + { + "name": "slayer", + "desc": "Slayer skill commands", + "subOptions": ["autoslay", "new_task", "manage", "rewards", "status"] + }, + { + "name": "smelt", + "desc": "Smelt ores/items.", + "examples": ["/smelt runite bar", "/smelt runite bar [quantity: 1]"], + "subOptions": [] + }, + { + "name": "smith", + "desc": "Smith things using the Smithing skill.", + "examples": ["/smith name:Bronze platebody"], + "subOptions": [] + }, + { + "name": "stats", + "desc": "Check the stats of a OSRS account.", + "subOptions": [] + }, + { + "name": "steal", + "desc": "Sends your minion to steal to train Thieving.", + "examples": ["/steal name:Man"], + "subOptions": [] + }, + { + "name": "tokkulshop", + "desc": "Buy or sell items from the Tzhaar shops.", + "examples": ["/tokkulshop buy Obsidian platebody", "/tokkulshop sell Chaos rune 5000"], + "flags": ["minion"], + "subOptions": ["buy", "sell"] + }, + { + "name": "tools", + "desc": "Various tools and miscellaneous commands.", + "subOptions": ["patron", "user", "stash_units"] + }, + { + "name": "trade", + "desc": "Allows you to trade items with other players.", + "subOptions": [] + }, + { + "name": "trivia", + "desc": "Try to answer a random trivia question!", + "examples": ["/trivia", "/trivia duel:@Magnaboy"], + "subOptions": [] + }, + { + "name": "use", + "desc": "Use items/things.", + "examples": ["/use name:Mithril seeds"], + "subOptions": [] + }, + { + "name": "wiki", + "desc": "Search the official OSRS wiki.", + "subOptions": [] + }, + { + "name": "xp", + "desc": "See your OSRS xp.", + "examples": ["/xp rsn:Magnaboy"], + "subOptions": [] + } +] diff --git a/src/lib/data/similarItems.ts b/src/lib/data/similarItems.ts index 725f82778f0..dad8b723334 100644 --- a/src/lib/data/similarItems.ts +++ b/src/lib/data/similarItems.ts @@ -1,8 +1,8 @@ +import { resolveItems } from 'oldschooljs/dist/util/util'; import { dyedItems } from '../dyedItems'; import skillcapes from '../skilling/skillcapes'; import getOSItem from '../util/getOSItem'; import itemID from '../util/itemID'; -import resolveItems from '../util/resolveItems'; import { gracefulCapes, gracefulFeet, @@ -200,6 +200,17 @@ const source: [string, (string | number)[]][] = [ ['Infernal cape', ['Infernal max cape', 'Infernal max cape (l)', 'TzKal cape']], ['Ardougne cloak 4', ['Ardougne max cape']], ["Ava's accumulator", ['Accumulator max cape', 'Tidal collector']], + [ + "Ava's assembler", + [ + 'Assembler max cape', + 'Assembler max cape (l)', + 'Masori assembler', + 'Masori assembler max cape', + "Blessed dizana's quiver", + "Dizana's max cape" + ] + ], ['Mythical cape', ['Mythical max cape']], [ 'Imbued guthix cape', @@ -394,6 +405,7 @@ const source: [string, (string | number)[]][] = [ ['Armadyl helmet', ['Masori mask (f)', 'Masori mask']], ['Armadyl chestplate', ['Armadyl chestplate', 'Masori body (f)', 'Masori body']], ['Armadyl chainskirt', ['Masori chaps (f)', 'Masori chaps']], + ['Ring of stone', ['Ring of coins', 'Crate ring', 'Ring of nature', 'Snowman ring', 'Ring of 3rd age']], ['Imbued heart', ['Saturated heart']], ["Craw's bow", ['Webweaver bow']], @@ -489,7 +501,7 @@ for (const [baseItem, similarItems] of source) { if (!inverseSimilarItems.get(item)) { inverseSimilarItems.set(item, new Set()); } - inverseSimilarItems.get(item)!.add(itemID(baseItem)); + inverseSimilarItems.get(item)?.add(itemID(baseItem)); } } diff --git a/src/lib/data/slayerMaskHelms.ts b/src/lib/data/slayerMaskHelms.ts index 1fc463419ef..c5401e97f6a 100644 --- a/src/lib/data/slayerMaskHelms.ts +++ b/src/lib/data/slayerMaskHelms.ts @@ -1,6 +1,6 @@ import { Monsters } from 'oldschooljs'; -import { Item } from 'oldschooljs/dist/meta/types'; -import Monster from 'oldschooljs/dist/structures/Monster'; +import type { Item } from 'oldschooljs/dist/meta/types'; +import type Monster from 'oldschooljs/dist/structures/Monster'; import { BSOMonsters } from '../minions/data/killableMonsters/custom/customMonsters'; import { slayerMasters } from '../slayer/slayerMasters'; @@ -165,4 +165,4 @@ for (const a of slayerMaskHelms) { } } -export const slayerMasksHelmsCL = slayerMaskHelms.map(i => [i.helm.id, i.mask.id]).flat(); +export const slayerMasksHelmsCL = slayerMaskHelms.flatMap(i => [i.helm.id, i.mask.id]); diff --git a/src/lib/data/tameCreatables.ts b/src/lib/data/tameCreatables.ts index 62e16251470..7e83ba4d8ae 100644 --- a/src/lib/data/tameCreatables.ts +++ b/src/lib/data/tameCreatables.ts @@ -3,7 +3,7 @@ import { Bank } from 'oldschooljs'; import { MaterialBank } from '../invention/MaterialBank'; import getOSItem from '../util/getOSItem'; import resolveItems from '../util/resolveItems'; -import { Createable } from './createables'; +import type { Createable } from './createables'; const eagleTameCreatables: Createable[] = [ { diff --git a/src/lib/data/tob.ts b/src/lib/data/tob.ts index f0e89c09d74..bcbbc43be5d 100644 --- a/src/lib/data/tob.ts +++ b/src/lib/data/tob.ts @@ -1,17 +1,17 @@ -import { calcPercentOfNum, calcWhatPercent, randFloat, randInt, reduceNumByPercent, round, Time } from 'e'; +import { Time, calcPercentOfNum, calcWhatPercent, randFloat, randInt, reduceNumByPercent, round } from 'e'; import { Bank } from 'oldschooljs'; import { randomVariation } from 'oldschooljs/dist/util'; +import { resolveItems } from 'oldschooljs/dist/util/util'; import type { GearStats } from '../gear/types'; import { inventionBoosts } from '../invention/inventions'; import { blowpipeDarts } from '../minions/functions/blowpipeCommand'; -import { constructGearSetup, Gear } from '../structures/Gear'; +import { Gear, constructGearSetup } from '../structures/Gear'; import getOSItem from '../util/getOSItem'; import { logError } from '../util/logError'; -import resolveItems from '../util/resolveItems'; import { gorajanArcherOutfit, gorajanOccultOutfit, gorajanWarriorOutfit, pernixOutfit } from './CollectionsExport'; -export interface TOBRoom { +interface TOBRoom { name: string; difficultyRating: number; timeWeighting: number; @@ -72,10 +72,10 @@ export function calculateTOBDeaths( total: number; } ): TOBDeaths { - let deaths: number[] = []; - let wipeDeaths: number[] = []; - let realDeathChances: { name: string; deathChance: number }[] = []; - let wipeDeathChances: { name: string; deathChance: number }[] = []; + const deaths: number[] = []; + const wipeDeaths: number[] = []; + const realDeathChances: { name: string; deathChance: number }[] = []; + const wipeDeathChances: { name: string; deathChance: number }[] = []; const actualDeathReductionFactor = isHardMode ? 3 : 4; @@ -168,14 +168,8 @@ function calcSetupPercent( } // For melee compare the highest melee attack stat of max setup with the highest melee attack stat of the user if (melee) { - let maxMeleeStat = Math.max( - maxStats['attack_stab'], - Math.max(maxStats['attack_slash'], maxStats['attack_crush']) - ); - let userMeleeStat = Math.max( - userStats['attack_stab'], - Math.max(userStats['attack_slash'], userStats['attack_crush']) - ); + const maxMeleeStat = Math.max(maxStats.attack_stab, Math.max(maxStats.attack_slash, maxStats.attack_crush)); + const userMeleeStat = Math.max(userStats.attack_stab, Math.max(userStats.attack_slash, userStats.attack_crush)); totalPercent += Math.min(100, calcWhatPercent(userMeleeStat, maxMeleeStat)); numKeys++; } @@ -313,9 +307,9 @@ interface TobTeam { export function calcTOBBaseDuration({ team, hardMode }: { team: TobTeam[]; hardMode: boolean }) { const teamSize = team.length; - let individualReductions = []; + const individualReductions = []; - let reductions: Record = {}; + const reductions: Record = {}; for (const u of team) { let userPercentChange = 0; @@ -414,7 +408,7 @@ export function calcTOBBaseDuration({ team, hardMode }: { team: TobTeam[]; hardM userPercentChange += 2; } - let reduction = round(userPercentChange / teamSize, 1); + const reduction = round(userPercentChange / teamSize, 1); individualReductions.push(userPercentChange); reductions[u.user.id] = reduction; @@ -472,7 +466,7 @@ export function createTOBRaid({ wipedRoom: TOBRoom | null; deathDuration: number | null; } { - let parsedTeam: ParsedTeamMember[] = []; + const parsedTeam: ParsedTeamMember[] = []; for (const u of team) { const gearPercents = calculateTOBUserGearPercents(u.user); @@ -487,7 +481,7 @@ export function createTOBRaid({ }); } - let duration = Math.floor(randomVariation(baseDuration, 5)); + const duration = Math.floor(randomVariation(baseDuration, 5)); let chinCannonUser: MUser | null = null; @@ -501,7 +495,7 @@ export function createTOBRaid({ let wipedRoom: TOBRoom | null = null; let deathDuration: number | null = 0; for (let i = 0; i < TOBRooms.length; i++) { - let room = TOBRooms[i]; + const room = TOBRooms[i]; if (parsedTeam.every(member => member.wipeDeaths.includes(i))) { wipedRoom = room; diff --git a/src/lib/data/trophies.ts b/src/lib/data/trophies.ts deleted file mode 100644 index 92515e25ea6..00000000000 --- a/src/lib/data/trophies.ts +++ /dev/null @@ -1,69 +0,0 @@ -import '../customItems/customItems'; - -import { modifyItem } from '@oldschoolgg/toolkit'; - -import resolveItems from '../util/resolveItems'; -import { setItemAlias } from './itemAliases'; - -// BSO (Twisted) trophies -setItemAlias(24_372, 'BSO dragon trophy'); -setItemAlias(24_374, 'BSO rune trophy'); -setItemAlias(24_376, 'BSO adamant trophy'); -setItemAlias(24_378, 'BSO mithril trophy'); -setItemAlias(24_380, 'BSO steel trophy'); -setItemAlias(24_382, 'BSO iron trophy'); -setItemAlias(24_384, 'BSO bronze trophy'); - -// Comp. trophies -setItemAlias(25_042, 'Comp. dragon trophy'); -setItemAlias(25_044, 'Comp. rune trophy'); -setItemAlias(25_046, 'Comp. adamant trophy'); -setItemAlias(25_048, 'Comp. mithril trophy'); -setItemAlias(25_050, 'Comp. steel trophy'); -setItemAlias(25_052, 'Comp. iron trophy'); -setItemAlias(25_054, 'Comp. bronze trophy'); - -// Placeholder trophies -setItemAlias(26_515, 'Placeholder dragon trophy'); -setItemAlias(26_513, 'Placeholder rune trophy'); -setItemAlias(26_511, 'Placeholder adamant trophy'); -setItemAlias(26_509, 'Placeholder mithril trophy'); -setItemAlias(26_507, 'Placeholder steel trophy'); -setItemAlias(26_505, 'Placeholder iron trophy'); -setItemAlias(26_503, 'Placeholder bronze trophy'); - -export const allTrophyItems = resolveItems([ - 'BSO dragon trophy', - 'BSO rune trophy', - 'BSO adamant trophy', - 'BSO mithril trophy', - 'BSO steel trophy', - 'BSO iron trophy', - 'BSO bronze trophy', - 'Comp. dragon trophy', - 'Comp. rune trophy', - 'Comp. adamant trophy', - 'Comp. mithril trophy', - 'Comp. steel trophy', - 'Comp. iron trophy', - 'Comp. bronze trophy', - 'Placeholder dragon trophy', - 'Placeholder rune trophy', - 'Placeholder adamant trophy', - 'Placeholder mithril trophy', - 'Placeholder steel trophy', - 'Placeholder iron trophy', - 'Placeholder bronze trophy' -]); - -for (const item of allTrophyItems) { - modifyItem(item, { - tradeable: false, - tradeable_on_ge: false, - customItemData: { - cantBeSacrificed: true, - isSuperUntradeable: true, - cantDropFromMysteryBoxes: true - } - }); -} diff --git a/src/lib/data/wordBlacklist.txt b/src/lib/data/wordBlacklist.txt deleted file mode 100644 index d791da9bb7f..00000000000 --- a/src/lib/data/wordBlacklist.txt +++ /dev/null @@ -1 +0,0 @@ -YXNzDQpzaGl0DQpiaXRjaA0KYm9vYnMNCnRpdHMNCmJhbGxzYWNrDQpiYncNCmJkc20NCmJhc3RhcmQNCmJpbWJvDQpjb2NrDQpkaWNrDQpjbGl0DQpibG93am9iDQpib2xsb2NrDQpib25kYWdlDQpib25lcg0KYm9vYg0KYnVra2FrZQ0KZHlrZQ0KYnVsbHNoaXQNCmJ1bQ0KYnV0dGhvbGUNCmNhbXNsdXQNCmNhbXdob3JlDQpjaGluaw0KY2hvYWQNCmdhbmdiYW5nDQpjdW0NCmN1bnQNCmRlZXB0aHJvYXQNCmRpbGRvDQpjb2NrDQpmdWNrDQpwZW5pcw0KdmFnaW5hDQp2dWx2YQ0Kc2x1dA0KbWFzdHVyYmF0ZQ0Kc2hpdA0KbmlnZ2VyDQpjcmFja2VyDQpqZXcNCmlzcmFlbA0KcGFsZXN0aW5lDQp0cnVtcA0KYmlkZW4NCnJlcHVibGljYW4NCmRlbW9jcmF0DQpuYXppDQphbnRpZmE= \ No newline at end of file diff --git a/src/lib/degradeableItems.ts b/src/lib/degradeableItems.ts index a5e69d5eca0..78786b4437f 100644 --- a/src/lib/degradeableItems.ts +++ b/src/lib/degradeableItems.ts @@ -1,11 +1,11 @@ -import { percentChance, Time } from 'e'; +import { Time, percentChance } from 'e'; import { Bank } from 'oldschooljs'; -import { Item } from 'oldschooljs/dist/meta/types'; -import Monster from 'oldschooljs/dist/structures/Monster'; +import type { Item } from 'oldschooljs/dist/meta/types'; +import type Monster from 'oldschooljs/dist/structures/Monster'; -import { GearSetupType } from './gear/types'; -import { KillableMonster } from './minions/types'; -import { ChargeBank } from './structures/Banks'; +import type { GearSetupType } from './gear/types'; +import type { KillableMonster } from './minions/types'; +import type { ChargeBank } from './structures/Banks'; import { assert } from './util'; import getOSItem from './util/getOSItem'; import itemID from './util/itemID'; @@ -62,6 +62,13 @@ interface DegradeableItemPVMBoost { boost: number; } +interface RefundResult { + item: Item; + refundedCharges: number; + totalCharges: number; + userMessage: string; +} + export const degradeableItems: DegradeableItem[] = [ { item: getOSItem('Abyssal tentacle'), @@ -420,10 +427,8 @@ export async function degradeItem({ const chargesAfter = user.user[degItem.settingsKey]; assert(typeof chargesAfter === 'number' && chargesAfter > 0); return { - userMessage: `Your ${ - item.name - } degraded by ${chargesToDegrade} charges, and now has ${chargesAfter} remaining.${ - pennyReduction > 0 ? ` Your Ghommal's lucky penny saved ${pennyReduction} charges` : '' + userMessage: `Your ${item.name} degraded by ${chargesToDegrade} charges, and now has ${chargesAfter} remaining${ + pennyReduction > 0 ? `. Your Ghommal's lucky penny saved ${pennyReduction} charges` : '' }` }; } @@ -457,3 +462,38 @@ export async function degradeChargeBank(user: MUser, chargeBank: ChargeBank) { return results; } + +export async function refundChargeBank(user: MUser, chargeBank: ChargeBank): Promise { + const results: RefundResult[] = []; + + for (const [key, chargesToRefund] of chargeBank.entries()) { + const degItem = degradeableItems.find(i => i.settingsKey === key); + if (!degItem) { + throw new Error(`Invalid degradeable item settings key: ${key}`); + } + + const currentCharges = user.user[degItem.settingsKey]; + const newCharges = currentCharges + chargesToRefund; + + // Prepare result message + const userMessage = `Refunded ${chargesToRefund} charges for ${degItem.item.name}.`; + + // Create result object + const result: RefundResult = { + item: degItem.item, + refundedCharges: chargesToRefund, + totalCharges: newCharges, + userMessage + }; + + // Push result to results array + results.push(result); + + // Update user + await user.update({ + [degItem.settingsKey]: newCharges + }); + } + + return results; +} diff --git a/src/lib/depthsOfAtlantis.ts b/src/lib/depthsOfAtlantis.ts index 4517f6f81d0..32c769ea50a 100644 --- a/src/lib/depthsOfAtlantis.ts +++ b/src/lib/depthsOfAtlantis.ts @@ -1,37 +1,26 @@ -import { channelIsSendable, formatOrdinal, mentionCommand } from '@oldschoolgg/toolkit'; +import { formatOrdinal, mentionCommand } from '@oldschoolgg/toolkit'; import { bold } from 'discord.js'; import { + Time, calcPercentOfNum, clamp, increaseNumByPercent, percentChance, randArrItem, randInt, - reduceNumByPercent, - Time + reduceNumByPercent } from 'e'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; import { Bank } from 'oldschooljs'; -import { production } from '../config'; -import { mahojiParseNumber, userStatsBankUpdate } from '../mahoji/mahojiSettings'; -import { Emoji } from './constants'; import { calcSetupPercent } from './data/cox'; import { getSimilarItems } from './data/similarItems'; -import { degradeItem } from './degradeableItems'; -import { UserFullGearSetup } from './gear'; -import { trackLoot } from './lootTrack'; -import { setupParty } from './party'; +import type { UserFullGearSetup } from './gear'; import { getMinigameScore } from './settings/minigames'; import { Gear } from './structures/Gear'; -import { ItemBank, MakePartyOptions, Skills } from './types'; -import { DOAOptions, DOAStoredRaid } from './types/minions'; -import { bankToStrShortNames, formatDuration, formatSkillRequirements, itemID, itemNameFromID } from './util'; -import addSubTaskToActivityTask from './util/addSubTaskToActivityTask'; -import { calcMaxTripLength } from './util/calcMaxTripLength'; -import getOSItem from './util/getOSItem'; +import type { ItemBank, Skills } from './types'; +import type { DOAStoredRaid } from './types/minions'; +import { formatDuration, formatSkillRequirements, itemID, itemNameFromID } from './util'; import resolveItems from './util/resolveItems'; -import { updateBankSetting } from './util/updateBankSetting'; const { floor } = Math; @@ -277,7 +266,7 @@ export const DOARooms: AtlantisRoom[] = [ id: 1, name: 'Shadowcaster the Octopus', addedDeathChance: (_user: MUser) => { - let addition = 0; + const addition = 0; return addition; }, baseTime: Time.Minute * 16, @@ -403,7 +392,7 @@ export const DOARooms: AtlantisRoom[] = [ id: 4, name: "Thalassar the Ocean's Warden", addedDeathChance: (_user: MUser) => { - let addition = 0; + const addition = 0; return addition; }, baseTime: Time.Minute * 13, @@ -462,7 +451,7 @@ export async function calcDOAInput({ let brewsNeeded = Math.max(4, 9 - Math.max(1, Math.ceil((kc + 1) / 12))); brewsNeeded += 10; - let restoresNeeded = Math.max(2, Math.floor(brewsNeeded / 3)); + const restoresNeeded = Math.max(2, Math.floor(brewsNeeded / 3)); cost.add('Saradomin brew(4)', brewsNeeded * quantity); cost.add('Super restore(4)', restoresNeeded * quantity); @@ -645,8 +634,8 @@ export function createDOATeam({ for (const { user, roomKCs } of team) { messages.push(` Checking boosts for ${user.usernameOrMention}`); for (const boost of room.speedBoosts) { - let percent = boost.percent / team.length; - let actualDecrease = calcPercentOfNum(percent, roomTime); + const percent = boost.percent / team.length; + const actualDecrease = calcPercentOfNum(percent, roomTime); if (boost.has(user)) { roomTime -= actualDecrease; messages.push( @@ -680,7 +669,7 @@ export function createDOATeam({ if (addedDeathChance !== 0) { messages.push(` Had an extra ${addedDeathChance}% added because of one of the rooms mechanics`); } - let deathChanceForThisUserRoom = clamp(baseDeathChance + addedDeathChance, 2, 99); + const deathChanceForThisUserRoom = clamp(baseDeathChance + addedDeathChance, 2, 99); messages.push( ` Final death chance for ${user.usernameOrMention} in ${room.name} room: ${deathChanceForThisUserRoom}%` ); @@ -745,190 +734,6 @@ export async function checkDOATeam(users: MUser[], challengeMode: boolean, quant return checkedUsers; } -export async function doaStartCommand( - user: MUser, - challengeMode: boolean, - solo: boolean, - channelID: string, - teamSize: number | undefined, - quantityInput: number | undefined -): CommandResponse { - if (user.minionIsBusy) { - return `${user.usernameOrMention} minion is busy`; - } - - const initialCheck = await checkDOAUser({ - user, - challengeMode, - duration: Time.Hour, - quantity: 1 - }); - if (typeof initialCheck === 'string') { - return initialCheck; - } - - if (user.minionIsBusy) { - return "Your minion is busy, so you can't start a raid."; - } - - let maxSize = mahojiParseNumber({ input: teamSize, min: 2, max: 8 }) ?? 8; - - const partyOptions: MakePartyOptions = { - leader: user, - minSize: 1, - maxSize, - ironmanAllowed: true, - message: `${user.usernameOrMention} is hosting a Depths of Atlantis mass! Use the buttons below to join/leave.`, - customDenier: async checkUser => { - const checkResult = await checkDOAUser({ - user: checkUser, - challengeMode, - duration: Time.Hour, - quantity: 1 - }); - if (typeof checkResult === 'string') { - return [true, checkResult]; - } - - return [false]; - } - }; - - const channel = globalClient.channels.cache.get(channelID); - if (!channelIsSendable(channel)) return 'No channel found.'; - - let usersWhoConfirmed = []; - try { - usersWhoConfirmed = solo ? [user] : await setupParty(channel, user, partyOptions); - } catch (err: any) { - return { - content: typeof err === 'string' ? err : 'Your mass failed to start.', - ephemeral: true - }; - } - const users = usersWhoConfirmed.filter(u => !u.minionIsBusy).slice(0, maxSize); - - const teamCheck = await checkDOATeam(users, challengeMode, 1); - if (typeof teamCheck === 'string') { - return { - content: `Your mass failed to start because of this reason: ${teamCheck} ${users}`, - allowedMentions: { - users: users.map(i => i.id) - } - }; - } - - const baseDuration = createDOATeam({ - team: teamCheck, - quantity: 1, - challengeMode - }).fakeDuration; - const maxTripLength = Math.max(...users.map(i => calcMaxTripLength(i, 'DepthsOfAtlantis'))); - const maxQuantity = clamp(Math.floor(maxTripLength / baseDuration), 1, 5); - const quantity = clamp(quantityInput ?? maxQuantity, 1, maxQuantity); - - const createdDOATeam = createDOATeam({ - team: teamCheck, - quantity, - challengeMode - }); - - let debugStr = ''; - - const totalCost = new Bank(); - - const costResult = await Promise.all( - users.map(async u => { - const { cost, sangCharges, voidStaffCharges, tumShadowCharges } = await calcDOAInput({ - user: u, - duration: createdDOATeam.fakeDuration, - quantity, - challengeMode - }); - const { realCost } = await u.specialRemoveItems(cost.clone()); - - await degradeItem({ - item: getOSItem('Sanguinesti staff'), - chargesToDegrade: sangCharges, - user: u - }); - - if (voidStaffCharges) { - await degradeItem({ - item: getOSItem('Void staff'), - chargesToDegrade: voidStaffCharges, - user: u - }); - } else if (tumShadowCharges) { - await degradeItem({ - item: getOSItem("Tumeken's shadow"), - chargesToDegrade: tumShadowCharges, - user: u - }); - } else { - throw new Error('No staff equipped'); - } - await userStatsBankUpdate(u.id, 'doa_cost', realCost); - const effectiveCost = realCost.clone(); - totalCost.add(effectiveCost); - - const { total } = calculateUserGearPercents(u.gear); - - const gearMarker = users.length > 5 ? 'Gear: ' : Emoji.Gear; - const boostsMarker = users.length > 5 ? 'Boosts: ' : Emoji.CombatSword; - debugStr += `**- ${u.usernameOrMention}** ${gearMarker}${total.toFixed( - 1 - )}% ${boostsMarker} used ${bankToStrShortNames(realCost)}\n\n`; - return { - userID: u.id, - effectiveCost - }; - }) - ); - - await updateBankSetting('doa_cost', totalCost); - await trackLoot({ - totalCost, - id: 'depths_of_atlantis', - type: 'Minigame', - changeType: 'cost', - users: costResult.map(i => ({ - id: i.userID, - cost: i.effectiveCost, - duration: createdDOATeam.realDuration - })) - }); - await addSubTaskToActivityTask({ - userID: user.id, - channelID: channelID.toString(), - duration: createdDOATeam.realDuration, - type: 'DepthsOfAtlantis', - leader: user.id, - users: users.map(i => i.id), - fakeDuration: createdDOATeam.fakeDuration, - quantity, - cm: challengeMode, - raids: createdDOATeam.raids - }); - - let str = `${partyOptions.leader.usernameOrMention}'s party (${users - .map(u => u.usernameOrMention) - .join(', ')}) is now off to do ${quantity === 1 ? 'a' : `${quantity}x`} ${ - challengeMode ? 'Challenge Mode' : '' - } Depths of Atlantis raid - the total trip will take ${formatDuration(createdDOATeam.fakeDuration)}.`; - - str += ` \n\n${debugStr}`; - - if (createdDOATeam.results[0].messages.length > 0 && !production) { - return { - content: str, - files: [{ attachment: Buffer.from(createdDOATeam.results[0].messages.join('\n')), name: 'doa.txt' }] - }; - } - - return str; -} - export async function doaCheckCommand(user: MUser) { const result = await checkDOAUser({ user, challengeMode: false, duration: Time.Hour, quantity: 1 }); if (typeof result === 'string') { @@ -961,7 +766,7 @@ export async function doaHelpCommand(user: MUser) { const minigameStats = await user.fetchMinigames(); - let str = `**Depths of Atlantis** + const str = `**Depths of Atlantis** **Attempts:** ${DOARooms.map(i => `- ${i.name}: ${(stats.doa_room_attempts_bank as ItemBank)[i.id] ?? 0} Attempts`).join('\n')} @@ -972,16 +777,16 @@ ${DOARooms.map(i => `- ${i.name}: ${(stats.doa_room_attempts_bank as ItemBank)[i totalUniques > 0 ? `(one unique every ${Math.floor( (minigameStats.depths_of_atlantis + minigameStats.depths_of_atlantis_cm) / totalUniques - )} raids, one unique every ${formatDuration( + )} raids, one unique every ${formatDuration( (stats.doa_total_minutes_raided * Time.Minute) / totalUniques - )})` + )})` : '' } **Requirements** ${requirements .map(i => { - let res = i.doesMeet({ user, gearStats, quantity: 1, allItemsOwned: user.allItemsOwned }); + const res = i.doesMeet({ user, gearStats, quantity: 1, allItemsOwned: user.allItemsOwned }); if (typeof res === 'string') { return `- ❌ ${bold(i.name)} ${res}`; } diff --git a/src/lib/diaries.ts b/src/lib/diaries.ts index 36a0fd72d6f..83f5d5ad95f 100644 --- a/src/lib/diaries.ts +++ b/src/lib/diaries.ts @@ -1,51 +1,41 @@ import { objectEntries } from 'e'; import { Monsters } from 'oldschooljs'; -import { Item } from 'oldschooljs/dist/meta/types'; -import { MAX_QP } from '../mahoji/lib/abstracted_commands/questCommand'; -import type { MinigameName, MinigameScore } from './settings/minigames'; +import type { Minigame } from '@prisma/client'; +import { MAX_QP } from './minions/data/quests'; +import type { DiaryTier, DiaryTierName } from './minions/types'; +import { DiaryID } from './minions/types'; +import { Minigames } from './settings/minigames'; import Skillcapes from './skilling/skillcapes'; import { courses } from './skilling/skills/agility'; import { MUserStats } from './structures/MUserStats'; -import { Skills } from './types'; -import { formatSkillRequirements, hasSkillReqs, itemNameFromID } from './util'; +import type { Skills } from './types'; import getOSItem from './util/getOSItem'; import resolveItems from './util/resolveItems'; +import { formatSkillRequirements, hasSkillReqs, itemNameFromID } from './util/smallUtils'; -export const diaryTiers = ['easy', 'medium', 'hard', 'elite'] as const; -export type DiaryTierName = (typeof diaryTiers)[number]; -export interface DiaryTier { - name: 'Easy' | 'Medium' | 'Hard' | 'Elite'; - items: Item[]; - skillReqs: Skills; - ownedItems?: number[]; - collectionLogReqs?: number[]; - minigameReqs?: Partial>; - lapsReqs?: Record; - qp?: number; - monsterScores?: Record; - customReq?: (user: MUser, summary: Boolean, stats: MUserStats) => [true] | [false, string]; -} -export interface Diary { +export type Diary = { name: string; + id: DiaryID; alias?: string[]; easy: DiaryTier; medium: DiaryTier; hard: DiaryTier; elite: DiaryTier; -} +}; export function userhasDiaryTierSync( user: MUser, - tier: DiaryTier, - data: { stats: MUserStats; minigameScores: MinigameScore[] } -): [true] | [false, string] { + _tier: DiaryTier | [DiaryID, DiaryTierName], + data: { stats: MUserStats; minigameScores: Minigame } +): { hasDiary: boolean; reasons: string; diaryGroup: Diary; tier: DiaryTier } { + const tier = Array.isArray(_tier) ? diaries.find(d => d.id === _tier[0])![_tier[1]] : _tier; const [hasReqs] = hasSkillReqs(user, tier.skillReqs); const skills = user.skillsAsLevels; let canDo = true; const reasons: string[] = []; if (!hasReqs) { - let failSkills: Skills = {}; + const failSkills: Skills = {}; for (const skill of objectEntries(tier.skillReqs)) { if (skills[skill[0]] < skill[1]!) failSkills[skill[0]] = skill[1]!; canDo = false; @@ -81,13 +71,13 @@ export function userhasDiaryTierSync( } if (tier.minigameReqs) { - const entries = Object.entries(tier.minigameReqs); + const entries = objectEntries(tier.minigameReqs); for (const [key, neededScore] of entries) { - const thisScore = data.minigameScores.find(m => m.minigame.column === key)!; - if (thisScore.score < neededScore!) { + const thisScore = data.minigameScores[key]!; + if (thisScore < neededScore!) { canDo = false; reasons.push( - `You don't have **${neededScore}** KC in **${thisScore.minigame.name}**, you have **${thisScore.score}**` + `You don't have **${neededScore}** KC in **${Minigames.find(m => m.column === key)!.name}**, you have **${thisScore}**` ); } } @@ -126,19 +116,28 @@ export function userhasDiaryTierSync( } } - if (canDo) return [true]; - return [canDo, reasons.join('\n- ')]; + return { + hasDiary: canDo, + reasons: reasons.join('\n- '), + tier, + diaryGroup: diaries.find(d => [d.easy, d.medium, d.hard, d.elite].includes(tier))! + }; } -export async function userhasDiaryTier(user: MUser, tier: DiaryTier): Promise<[true] | [false, string]> { - return userhasDiaryTierSync(user, tier, { +export async function userhasDiaryTier( + user: MUser, + tier: [DiaryID, DiaryTierName] | DiaryTier +): Promise<[boolean, string, Diary]> { + const result = userhasDiaryTierSync(user, tier, { stats: await MUserStats.fromID(user.id), - minigameScores: await user.fetchMinigameScores() + minigameScores: await user.fetchMinigames() }); + return [result.hasDiary, result.reasons, result.diaryGroup]; } export const WesternProv: Diary = { name: 'Western Provinces', + id: DiaryID.WesternProvinces, alias: ['western', 'wp', 'west', 'west prov'], easy: { name: 'Easy', @@ -237,6 +236,7 @@ export const WesternProv: Diary = { }; export const ArdougneDiary: Diary = { name: 'Ardougne', + id: DiaryID.Ardougne, alias: ['ardy', 'ardougn'], easy: { name: 'Easy', @@ -328,6 +328,7 @@ export const ArdougneDiary: Diary = { export const DesertDiary: Diary = { name: 'Desert', + id: DiaryID.Desert, easy: { name: 'Easy', items: [getOSItem('Desert amulet 1')], @@ -411,6 +412,7 @@ export const DesertDiary: Diary = { export const FaladorDiary: Diary = { name: 'Falador', + id: DiaryID.Falador, alias: ['fally', 'fal'], easy: { name: 'Easy', @@ -466,13 +468,7 @@ export const FaladorDiary: Diary = { woodcutting: 71 }, qp: 32, - collectionLogReqs: resolveItems([ - 'Mind rune', - 'Prospector jacket', - 'Prospector helmet', - 'Prospector legs', - 'Prospector boots' - ]), + collectionLogReqs: resolveItems(['Mind rune', 'Prospector helmet']), monsterScores: { 'Skeletal Wyvern': 1, 'Blue Dragon': 1 @@ -512,6 +508,7 @@ export const FaladorDiary: Diary = { export const FremennikDiary: Diary = { name: 'Fremennik', + id: DiaryID.Fremennik, alias: ['fremmy', 'fremenik', 'fremmenik', 'frem'], easy: { name: 'Easy', @@ -592,6 +589,7 @@ export const FremennikDiary: Diary = { export const KandarinDiary: Diary = { name: 'Kandarin', + id: DiaryID.Kandarin, alias: ['kand'], easy: { name: 'Easy', @@ -681,6 +679,7 @@ export const KandarinDiary: Diary = { export const KaramjaDiary: Diary = { name: 'Karamja', + id: DiaryID.Karamja, alias: ['ramja', 'ram', 'karam', 'kar'], easy: { name: 'Easy', @@ -743,6 +742,7 @@ export const KaramjaDiary: Diary = { export const KourendKebosDiary: Diary = { name: 'Kourend & Kebos', + id: DiaryID.KourendKebos, alias: ['kebos', 'kouren', 'kourend', 'kk', 'kek'], easy: { name: 'Easy', @@ -818,6 +818,7 @@ export const KourendKebosDiary: Diary = { }; export const LumbridgeDraynorDiary: Diary = { name: 'Lumbridge & Draynor', + id: DiaryID.LumbridgeDraynor, alias: ['lumb', 'draynor', 'lumbridge', 'led'], easy: { name: 'Easy', @@ -890,6 +891,7 @@ export const LumbridgeDraynorDiary: Diary = { export const MorytaniaDiary: Diary = { name: 'Morytania', + id: DiaryID.Morytania, alias: ['mory', 'swamp'], easy: { name: 'Easy', @@ -969,6 +971,7 @@ export const MorytaniaDiary: Diary = { export const VarrockDiary: Diary = { name: 'Varrock', + id: DiaryID.Varrock, alias: ['var'], easy: { name: 'Easy', @@ -1036,6 +1039,7 @@ export const VarrockDiary: Diary = { export const WildernessDiary: Diary = { name: 'Wilderness', + id: DiaryID.Wilderness, alias: ['wild', 'wildy'], easy: { name: 'Easy', @@ -1130,3 +1134,10 @@ export const diariesObject = { WildernessDiary } as const; export const diaries = Object.values(diariesObject); + +export async function userhasDiaryIDTier(user: MUser, diaryID: DiaryID, tier: DiaryTierName) { + return userhasDiaryTierSync(user, [diaryID, tier], { + stats: await MUserStats.fromID(user.id), + minigameScores: await user.fetchMinigames() + }); +} diff --git a/src/lib/doubleLoot.ts b/src/lib/doubleLoot.ts index e745532410b..5b24472b96b 100644 --- a/src/lib/doubleLoot.ts +++ b/src/lib/doubleLoot.ts @@ -1,4 +1,4 @@ -import { TextChannel } from 'discord.js'; +import type { TextChannel } from 'discord.js'; import { Time } from 'e'; import { Channel } from './constants'; @@ -7,7 +7,7 @@ import { mahojiClientSettingsFetch, mahojiClientSettingsUpdate } from './util/cl export let DOUBLE_LOOT_FINISH_TIME_CACHE = 0; -export function isDoubleLootActive(duration: number = 0) { +export function isDoubleLootActive(duration = 0) { return Date.now() - duration < DOUBLE_LOOT_FINISH_TIME_CACHE; } @@ -35,7 +35,7 @@ export async function addToDoubleLootTimer(amount: number, reason: string) { } export async function addPatronLootTime(_tier: number, user: MUser | null) { - let map: Record = { + const map: Record = { 1: 3, 2: 6, 3: 15, @@ -44,8 +44,8 @@ export async function addPatronLootTime(_tier: number, user: MUser | null) { }; const tier = _tier - 1; if (!map[tier]) return; - let minutes = map[tier]; - let timeAdded = Math.floor(Time.Minute * minutes); + const minutes = map[tier]; + const timeAdded = Math.floor(Time.Minute * minutes); addToDoubleLootTimer(timeAdded, `${user ?? 'Someone'} became a Tier ${tier} sponsor`); } @@ -59,7 +59,7 @@ export async function syncDoubleLoot() { export async function syncPrescence() { await syncDoubleLoot(); - let str = isDoubleLootActive() + const str = isDoubleLootActive() ? `${formatDuration(DOUBLE_LOOT_FINISH_TIME_CACHE - Date.now(), true)} Double Loot!` : '/help'; if (globalClient.user!.presence.activities[0]?.name !== str) { diff --git a/src/lib/dyedItems.ts b/src/lib/dyedItems.ts index f95d25a8b94..04430038cec 100644 --- a/src/lib/dyedItems.ts +++ b/src/lib/dyedItems.ts @@ -1,4 +1,4 @@ -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; import getOSItem from './util/getOSItem'; import resolveItems from './util/resolveItems'; @@ -632,7 +632,4 @@ export const dyedItems: DyedItem[] = [ } ]; -export const allDyedItems = dyedItems - .map(i => i.dyedVersions) - .flat() - .map(i => i.item.id); +export const allDyedItems = dyedItems.flatMap(i => i.dyedVersions).map(i => i.item.id); diff --git a/src/lib/economyLogs.ts b/src/lib/economyLogs.ts index d42331c245c..b364fc0d217 100644 --- a/src/lib/economyLogs.ts +++ b/src/lib/economyLogs.ts @@ -3,7 +3,7 @@ import { sendToChannelID } from './util/webhook'; let economyLogBuffer: string[] = []; -export async function economyLog(message: string, flush: boolean = false) { +export async function economyLog(message: string, flush = false) { economyLogBuffer.push(message); if ((flush && economyLogBuffer.length !== 1) || economyLogBuffer.length === 10) { await sendToChannelID(Channel.EconomyLogs, { diff --git a/src/lib/events.ts b/src/lib/events.ts index 95683f4efab..7434c77e62a 100644 --- a/src/lib/events.ts +++ b/src/lib/events.ts @@ -1,22 +1,24 @@ -import { mentionCommand } from '@oldschoolgg/toolkit'; -import { UserError } from '@oldschoolgg/toolkit/dist/lib/UserError'; -import { BaseMessageOptions, bold, ButtonBuilder, ButtonStyle, EmbedBuilder, Message, time } from 'discord.js'; -import { isFunction, Time } from 'e'; +import { channelIsSendable, mentionCommand } from '@oldschoolgg/toolkit'; +import { UserError } from '@oldschoolgg/toolkit'; +import type { BaseMessageOptions, Message } from 'discord.js'; +import { ButtonBuilder, ButtonStyle, EmbedBuilder, bold, time } from 'discord.js'; +import { Time, isFunction } from 'e'; import { Items } from 'oldschooljs'; +import { Cooldowns } from '../mahoji/lib/Cooldowns'; -import { PATRON_DOUBLE_LOOT_COOLDOWN } from '../mahoji/commands/tools'; +import { command_name_enum } from '@prisma/client'; import { minionStatusCommand } from '../mahoji/lib/abstracted_commands/minionStatusCommand'; -import { Cooldowns } from '../mahoji/lib/Cooldowns'; +import { giveBoxResetTime, itemContractResetTime, spawnLampResetTime } from './MUser'; import { boxSpawnHandler } from './boxSpawns'; import { getGuthixianCacheInterval, userHasDoneCurrentGuthixianCache } from './bso/guthixianCache'; import { IronmanPMBTable, itemSearchMbTable } from './bsoOpenables'; -import { BitField, Emoji, globalConfig, secretItems } from './constants'; +import { BitField, Emoji, globalConfig } from './constants'; import { customItems } from './customItems/util'; import { DOUBLE_LOOT_FINISH_TIME_CACHE, isDoubleLootActive } from './doubleLoot'; -import { giveBoxResetTime, itemContractResetTime, spawnLampResetTime } from './MUser'; -import { prisma } from './settings/prisma'; -import { ItemBank } from './types'; -import { channelIsSendable, formatDuration, makeComponents, toKMB } from './util'; + +import { PATRON_DOUBLE_LOOT_COOLDOWN } from '../mahoji/commands/tools'; +import type { ItemBank } from './types'; +import { formatDuration, makeComponents, toKMB } from './util'; import { logError } from './util/logError'; import { makeBankImage } from './util/makeBankImage'; import { minionStatsEmbed } from './util/minionStatsEmbed'; @@ -87,7 +89,7 @@ interface MentionCommandOptions { content: string; } interface MentionCommand { - name: string; + name: command_name_enum; aliases: string[]; description: string; run: (options: MentionCommandOptions) => Promise; @@ -95,7 +97,7 @@ interface MentionCommand { const mentionCommands: MentionCommand[] = [ { - name: 'bs', + name: command_name_enum.bs, aliases: ['bs'], description: 'Searches your bank.', run: async ({ msg, user, components, content }: MentionCommandOptions) => { @@ -114,7 +116,7 @@ const mentionCommands: MentionCommand[] = [ } }, { - name: 'bal', + name: command_name_enum.bal, aliases: ['bal', 'gp'], description: 'Shows how much GP you have.', run: async ({ msg, user, components }: MentionCommandOptions) => { @@ -125,14 +127,14 @@ const mentionCommands: MentionCommand[] = [ } }, { - name: 'is', + name: command_name_enum.is, aliases: ['is'], description: 'Searches for items.', run: async ({ msg, components, user, content }: MentionCommandOptions) => { const items = Items.filter( i => [i.id.toString(), i.name.toLowerCase()].includes(content.toLowerCase()) && - !secretItems.includes(i.id) + !i.customItemData?.isSecret ).array(); if (items.length === 0) return msg.reply('No results for that item.'); @@ -175,7 +177,7 @@ const mentionCommands: MentionCommand[] = [ } }, { - name: 'bank', + name: command_name_enum.bank, aliases: ['b', 'bank'], description: 'Shows your bank.', run: async ({ msg, user, components }: MentionCommandOptions) => { @@ -197,7 +199,7 @@ const mentionCommands: MentionCommand[] = [ } }, { - name: 'cd', + name: command_name_enum.cd, aliases: ['cd'], description: 'Shows your cooldowns.', run: async ({ msg, user, components }: MentionCommandOptions) => { @@ -228,7 +230,7 @@ const mentionCommands: MentionCommand[] = [ } if (isDoubleLootActive()) { - let date = new Date(DOUBLE_LOOT_FINISH_TIME_CACHE); + const date = new Date(DOUBLE_LOOT_FINISH_TIME_CACHE); content += `\n\n2️⃣🇽 **Double Loot is Active until ${time(date)} (${time(date, 'R')})**`; } msg.reply({ @@ -238,7 +240,7 @@ const mentionCommands: MentionCommand[] = [ } }, { - name: 'sendtoabutton', + name: command_name_enum.sendtoabutton, aliases: ['sendtoabutton'], description: 'Shows your stats.', run: async ({ msg, user }: MentionCommandOptions) => { @@ -263,7 +265,7 @@ const mentionCommands: MentionCommand[] = [ } }, { - name: 's', + name: command_name_enum.stats, aliases: ['s', 'stats'], description: 'Shows your stats.', run: async ({ msg, user, components }: MentionCommandOptions) => { @@ -296,7 +298,6 @@ export async function onMessage(msg: Message) { guild_id: msg.guildId ? BigInt(msg.guildId) : undefined, command_name: command.name, args: msgContentWithoutCommand, - flags: undefined, inhibited: false, is_mention_command: true } diff --git a/src/lib/finishables.ts b/src/lib/finishables.ts index 9e1ebd3aa75..3e729cca579 100644 --- a/src/lib/finishables.ts +++ b/src/lib/finishables.ts @@ -10,7 +10,8 @@ import MediumClueTable from 'oldschooljs/dist/simulation/clues/Medium'; import { ChambersOfXeric, Nightmare } from 'oldschooljs/dist/simulation/misc'; import { EliteMimicTable, MasterMimicTable } from 'oldschooljs/dist/simulation/misc/Mimic'; -import { rollNaxxusLoot } from '../tasks/minions/bso/naxxusActivity'; +import { resolveItems } from 'oldschooljs/dist/util/util'; +import { rollNaxxusLoot } from './bso/naxxus/rollNaxxusLoot'; import { allCollectionLogsFlat } from './data/Collections'; import { chambersOfXericCL, @@ -27,12 +28,12 @@ import { nexCL, nexUniqueDrops, temporossCL, - theatreOfBloodCapes, - theatreOfBloodHardUniques, - theatreOfBloodNormalUniques, theGauntletCL, theNightmareCL, theNightmareNormalCL, + theatreOfBloodCapes, + theatreOfBloodHardUniques, + theatreOfBloodNormalUniques, wintertodtCL } from './data/CollectionsExport'; import pets from './data/pets'; @@ -47,17 +48,16 @@ import { gauntlet } from './simulation/gauntlet'; import { getTemporossLoot } from './simulation/tempoross'; import { TheatreOfBlood } from './simulation/tob'; import { WintertodtCrate } from './simulation/wintertodt'; -import { calculateTripConsumableCost } from './util'; +import { calculateTripConsumableCost } from './util/calculateTripConsumableCost'; import getOSItem from './util/getOSItem'; import itemID from './util/itemID'; -import resolveItems from './util/resolveItems'; interface KillArgs { accumulatedLoot: Bank; totalRuns: number; } -export interface Finishable { +interface Finishable { name: string; aliases?: string[]; cl: number[]; @@ -319,15 +319,15 @@ for (const mon of monsterPairedCLs) { cl: mon.cl, kill: ({ accumulatedLoot }) => { const cost = new Bank(); - if (killableMonster && killableMonster.healAmountNeeded) { + if (killableMonster?.healAmountNeeded) { cost.add('Swordfish', Math.ceil(killableMonster.healAmountNeeded / 14)); } if (killableMonster?.itemCost) { cost.add(calculateTripConsumableCost(killableMonster.itemCost, 1, killableMonster.timeToFinish)); } - let loot = 'kill' in mon.mon ? mon.mon.kill(1, {}) : mon.mon.table.roll(); - if (killableMonster && killableMonster.specialLoot) { + const loot = 'kill' in mon.mon ? mon.mon.kill(1, {}) : mon.mon.table.roll(); + if (killableMonster?.specialLoot) { killableMonster.specialLoot({ ownedItems: accumulatedLoot, loot, quantity: 1, cl: accumulatedLoot }); } return { loot, cost }; diff --git a/src/lib/fishingContest.ts b/src/lib/fishingContest.ts index 446907470ae..aa283348cab 100644 --- a/src/lib/fishingContest.ts +++ b/src/lib/fishingContest.ts @@ -1,7 +1,7 @@ -import { toTitleCase } from '@oldschoolgg/toolkit'; -import { FishingContestCatch } from '@prisma/client'; +import { averageArr, seedShuffle, toTitleCase } from '@oldschoolgg/toolkit'; +import type { FishingContestCatch } from '@prisma/client'; import { calcPercentOfNum, randArrItem } from 'e'; -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; import { ArdougneDiary, @@ -9,12 +9,12 @@ import { KourendKebosDiary, LumbridgeDraynorDiary, MorytaniaDiary, - userhasDiaryTier, - WildernessDiary + WildernessDiary, + userhasDiaryTier } from './diaries'; -import { prisma } from './settings/prisma'; + import { SkillsEnum } from './skilling/types'; -import { averageArr, gaussianRandom, ISODateString, murMurSort } from './util'; +import { ISODateString, gaussianRandom } from './util'; import getOSItem from './util/getOSItem'; const warmVerbs = ['freshwater', 'waterborn', 'silver']; @@ -247,8 +247,8 @@ export function getCurrentFishType(dateOverride?: Date): FishType { const day = (dateOverride ?? new Date()).toLocaleDateString(); return { - temperature: murMurSort(['cold', 'warm'], day)[0], - water: murMurSort(['ocean', 'lake', 'river'], day)[0] + temperature: seedShuffle(['cold', 'warm'] as const, day)[0], + water: seedShuffle(['ocean', 'lake', 'river'] as const, day)[0] }; } diff --git a/src/lib/geImage.ts b/src/lib/geImage.ts index 1e35e331ad1..fb0e5224bdd 100644 --- a/src/lib/geImage.ts +++ b/src/lib/geImage.ts @@ -1,24 +1,19 @@ -import { Canvas, Image, loadImage, SKRSContext2D } from '@napi-rs/canvas'; +import * as fs from 'node:fs/promises'; +import type { Image, SKRSContext2D } from '@napi-rs/canvas'; +import { Canvas, loadImage } from '@napi-rs/canvas'; import { formatItemStackQuantity, generateHexColorForCashStack, toTitleCase } from '@oldschoolgg/toolkit'; -import { GEListing, GETransaction } from '@prisma/client'; -import * as fs from 'fs/promises'; -import { floor } from 'lodash'; -import fetch from 'node-fetch'; -import * as path from 'path'; -import { GEListingWithTransactions } from './../mahoji/commands/ge'; +import type { GEListing, GETransaction } from '@prisma/client'; +import type { GEListingWithTransactions } from './../mahoji/commands/ge'; import { GrandExchange } from './grandExchange'; import { fillTextXTimesInCtx } from './util/canvasUtil'; import getOSItem from './util/getOSItem'; -import { logError } from './util/logError'; - -const CACHE_DIR = './icon_cache'; function drawTitle(ctx: SKRSContext2D, title: string, canvas: Canvas) { // Draw Page Title ctx.font = '16px RuneScape Bold 12'; const titleWidthPx = ctx.measureText(title); - let titleX = Math.floor(floor(canvas.width) * 0.95 - titleWidthPx.width); + const titleX = Math.floor(Math.floor(canvas.width) * 0.95 - titleWidthPx.width); ctx.fillStyle = '#000000'; fillTextXTimesInCtx(ctx, title, titleX + 1, 22); @@ -34,20 +29,9 @@ class GeImageTask { public geProgressShadow: Image | null = null; public geIconBuy: Image | null = null; public geIconSell: Image | null = null; - public itemIconsList: Set; - public itemIconImagesCache: Map; - - public constructor() { - // This tells us simply whether the file exists or not on disk. - this.itemIconsList = new Set(); - - // If this file does exist, it might be cached in this, or need to be read from fs. - this.itemIconImagesCache = new Map(); - } async init() { await this.prepare(); - await this.run(); } async prepare() { @@ -74,31 +58,7 @@ class GeImageTask { ); } - async run() { - await this.cacheFiles(); - } - - async cacheFiles() { - // Ensure that the icon_cache dir exists. - await fs.mkdir(CACHE_DIR).catch(() => null); - CACHE_DIR; - // Get a list of all files (images) in the dir. - const filesInDir = await fs.readdir(CACHE_DIR); - - // For each one, set a cache value that it exists. - for (const fileName of filesInDir) { - this.itemIconsList.add(parseInt(path.parse(fileName).name)); - } - } - - drawText( - ctx: SKRSContext2D, - text: string, - x: number, - y: number, - maxWidth: number | undefined = undefined, - lineHeight: number - ) { + drawText(ctx: SKRSContext2D, text: string, x: number, y: number, maxWidth: number | undefined, lineHeight: number) { // If max width is set, we have to line break the text const textLines = []; const measuredText = ctx.measureText(text); @@ -127,7 +87,7 @@ class GeImageTask { async getSlotImage( ctx: SKRSContext2D, slot: number, - locked: boolean = false, + locked: boolean, listing: GEListingWithTransactions | undefined ) { const slotImage = listing ? this.geSlotActive! : locked ? this.geSlotLocked! : this.geSlotOpen!; @@ -136,7 +96,7 @@ class GeImageTask { // Draw Bank Title ctx.textAlign = 'center'; ctx.font = '16px RuneScape Bold 12'; - let type = listing ? ` - ${toTitleCase(listing.type.toString())}` : ' - Empty'; + const type = listing ? ` - ${toTitleCase(listing.type.toString())}` : ' - Empty'; this.drawText( ctx, locked ? 'Locked' : `Slot ${slot}${type}`, @@ -148,7 +108,7 @@ class GeImageTask { if (listing) { // Get item - const itemImage = await this.getItemImage(listing.item_id); + const itemImage = await bankImageGenerator.getItemImage(listing.item_id); // Draw item ctx.textAlign = 'left'; @@ -158,10 +118,10 @@ class GeImageTask { ctx.translate(8, 34); ctx.drawImage( itemImage, - Math.floor((32 - itemImage!.width) / 2) + 2, - Math.floor((32 - itemImage!.height) / 2), - itemImage!.width, - itemImage!.height + Math.floor((32 - itemImage?.width) / 2) + 2, + Math.floor((32 - itemImage?.height) / 2), + itemImage?.width, + itemImage?.height ); if (listing.total_quantity > 1) { const formattedQuantity = formatItemStackQuantity(listing.total_quantity); @@ -221,40 +181,6 @@ class GeImageTask { } } - async getItemImage(itemID: number): Promise { - const cachedImage = this.itemIconImagesCache.get(itemID); - if (cachedImage) return cachedImage; - - const isOnDisk = this.itemIconsList.has(itemID); - if (!isOnDisk) { - await this.fetchAndCacheImage(itemID); - return this.getItemImage(itemID); - } - - const imageBuffer = await fs.readFile(path.join(CACHE_DIR, `${itemID}.png`)); - try { - const image = await loadImage(imageBuffer); - this.itemIconImagesCache.set(itemID, image); - return image; - } catch (err) { - logError(`Failed to load item icon with id: ${itemID}`); - return this.getItemImage(1); - } - } - - async fetchAndCacheImage(itemID: number) { - const imageBuffer = await fetch(`https://chisel.weirdgloop.org/static/img/osrs-sprite/${itemID}.png`).then( - result => result.buffer() - ); - - await fs.writeFile(path.join(CACHE_DIR, `${itemID}.png`), imageBuffer); - - const image = await loadImage(imageBuffer); - - this.itemIconsList.add(itemID); - this.itemIconImagesCache.set(itemID, image); - } - async createInterface(opts: { user: MUser; page: number; @@ -293,7 +219,7 @@ class GeImageTask { } ctx.save(); ctx.translate(x * (this.geSlotOpen!.width + 2), y); - await this.getSlotImage(ctx, i + 1, i >= slots ? true : false, listing); + await this.getSlotImage(ctx, i + 1, i >= slots, listing); ctx.restore(); x++; if (i > (page - 1) * chunkSize + 8) break; @@ -305,15 +231,9 @@ class GeImageTask { } declare global { - const geImageGenerator: GeImageTask; -} -declare global { - namespace NodeJS { - interface Global { - geImageGenerator: GeImageTask; - } - } + var geImageGenerator: GeImageTask; } + global.geImageGenerator = new GeImageTask(); geImageGenerator.init(); diff --git a/src/lib/gear/functions/generateGearImage.ts b/src/lib/gear/functions/generateGearImage.ts index f3902275e30..bec8624b400 100644 --- a/src/lib/gear/functions/generateGearImage.ts +++ b/src/lib/gear/functions/generateGearImage.ts @@ -1,26 +1,26 @@ -/* eslint-disable @typescript-eslint/restrict-plus-operands */ -import { Canvas, Image } from '@napi-rs/canvas'; +import * as fs from 'node:fs'; +import * as fsPromises from 'node:fs/promises'; +import { Canvas, type Image, loadImage } from '@napi-rs/canvas'; import { toTitleCase } from '@oldschoolgg/toolkit'; import { randInt } from 'e'; -import * as fs from 'fs'; -import * as fsPromises from 'fs/promises'; -import { EquipmentSlot, Item } from 'oldschooljs/dist/meta/types'; +import { EquipmentSlot, type Item } from 'oldschooljs/dist/meta/types'; +import { + type GearSetup, + type GearSetupType, + GearSetupTypes, + type GearStats, + maxDefenceStats, + maxOffenceStats +} from '..'; import { monkeyTiers } from '../../monkeyRumble'; import { Gear } from '../../structures/Gear'; -import { - calcAspectRatioFit, - canvasImageFromBuffer, - drawItemQuantityText, - drawTitleText, - fillTextXTimesInCtx -} from '../../util/canvasUtil'; +import { calcAspectRatioFit, drawItemQuantityText, drawTitleText, fillTextXTimesInCtx } from '../../util/canvasUtil'; import { applyCustomItemEffects } from '../../util/customItemEffects'; import getOSItem from '../../util/getOSItem'; import { allSlayerMaskHelmsAndMasks, slayerMaskLeaderboardCache } from '../../util/slayerMaskLeaderboard'; -import { GearSetup, GearSetupType, GearSetupTypes, GearStats, maxDefenceStats, maxOffenceStats } from '..'; -const banana = canvasImageFromBuffer(fs.readFileSync('./src/lib/resources/images/banana.png')); +const banana = loadImage(fs.readFileSync('./src/lib/resources/images/banana.png')); export const gearImages = [ { @@ -87,8 +87,8 @@ function drawText(canvas: Canvas, text: string, x: number, y: number, maxStat = i === 0 ? x - (ctx.textAlign === 'end' ? ctx.measureText(texts[i + 1]).width - 3 : 0) : ctx.textAlign === 'end' - ? x - : ctx.measureText(texts[i - 1]).width + x + 3, + ? x + : ctx.measureText(texts[i - 1]).width + x + 3, y ); } @@ -104,7 +104,7 @@ async function drawStats(canvas: Canvas, gearStats: GearStats, alternateImage: I if (alternateImage) { const numBananas = randInt(1, 30); for (let i = 0; i < numBananas; i++) { - let b = await banana; + const b = await banana; ctx.drawImage( b, randInt(1, canvas.width * 0.8), @@ -113,8 +113,6 @@ async function drawStats(canvas: Canvas, gearStats: GearStats, alternateImage: I b.height ); } - let { sprite } = bankImageGenerator.getBgAndSprite(1); - if (1 > 2) bankImageGenerator.drawBorder(ctx, sprite, false); return; } @@ -218,23 +216,23 @@ interface TransmogItem { const transmogItems: TransmogItem[] = [ { item: getOSItem('Gorilla rumble greegree'), - image: fsPromises.readFile('./src/lib/resources/images/mmmr/gorilla.png').then(canvasImageFromBuffer), + image: fsPromises.readFile('./src/lib/resources/images/mmmr/gorilla.png').then(loadImage), maxHeight: 170 }, ...monkeyTiers.map(m => m.greegrees.map(g => ({ item: g, image: m.image }))).flat(2), { item: getOSItem('Gastly ghost cape'), - image: fsPromises.readFile('./src/lib/resources/images/ghost.png').then(canvasImageFromBuffer), + image: fsPromises.readFile('./src/lib/resources/images/ghost.png').then(loadImage), maxHeight: 170 }, { item: getOSItem('Spooky cat ears'), - image: fsPromises.readFile('./src/lib/resources/images/cat.png').then(canvasImageFromBuffer), + image: fsPromises.readFile('./src/lib/resources/images/cat.png').then(loadImage), maxHeight: 74 }, { item: getOSItem('Pumpkinpole'), - image: fsPromises.readFile('./src/lib/resources/images/pumpkin.png').then(canvasImageFromBuffer), + image: fsPromises.readFile('./src/lib/resources/images/pumpkin.png').then(loadImage), maxHeight: 180 } ]; @@ -246,18 +244,17 @@ export async function generateGearImage( petID: number | null, titleOverride?: string ) { - debugLog('Generating gear image', { user_id: user.id }); const transmogItem = (gearType && transmogItems.find(t => user.gear[gearType].hasEquipped(t.item.name))) ?? null; const transMogImage = transmogItem === null ? null : await transmogItem.image; const bankBg = user.user.bankBackground ?? 1; - let { sprite, uniqueSprite, background: userBgImage } = bankImageGenerator.getBgAndSprite(bankBg); + const { sprite, uniqueSprite, background: userBgImage } = bankImageGenerator.getBgAndSprite(bankBg); const hexColor = user.user.bank_bg_hex; const gearStats = gearSetup instanceof Gear ? gearSetup.stats : new Gear(gearSetup).stats; - const gearTemplateImage = await canvasImageFromBuffer(user.gearTemplate.template); + const gearTemplateImage = await loadImage(user.gearTemplate.template); const canvas = new Canvas(gearTemplateImage.width, gearTemplateImage.height); const ctx = canvas.getContext('2d'); ctx.imageSmoothingEnabled = false; @@ -364,15 +361,14 @@ export async function generateGearImage( } export async function generateAllGearImage(user: MUser) { - let { + const { sprite: bgSprite, uniqueSprite: hasBgSprite, background: userBg } = bankImageGenerator.getBgAndSprite(user.user.bankBackground ?? 1, user); const hexColor = user.user.bank_bg_hex; - debugLog('Generating all-gear image', { user_id: user.id }); - const gearTemplateImage = await canvasImageFromBuffer(user.gearTemplate.templateCompact); + const gearTemplateImage = await loadImage(user.gearTemplate.templateCompact); const canvas = new Canvas((gearTemplateImage.width + 10) * 4 + 20, Number(gearTemplateImage.height) * 2 + 70); const ctx = canvas.getContext('2d'); ctx.imageSmoothingEnabled = false; diff --git a/src/lib/gear/functions/hasWildyHuntGearEquipped.ts b/src/lib/gear/functions/hasWildyHuntGearEquipped.ts index de718cc79eb..d3445c571e5 100644 --- a/src/lib/gear/functions/hasWildyHuntGearEquipped.ts +++ b/src/lib/gear/functions/hasWildyHuntGearEquipped.ts @@ -1,5 +1,5 @@ import getOSItem from '../../util/getOSItem'; -import { GearSetup } from '../types'; +import type { GearSetup } from '../types'; export function hasWildyHuntGearEquipped(setup: GearSetup): [boolean, string, number] { const userBodyID = setup.body?.item; diff --git a/src/lib/gear/functions/inverseOfStat.ts b/src/lib/gear/functions/inverseOfStat.ts index 21704afffcc..79b2dd7dd6d 100644 --- a/src/lib/gear/functions/inverseOfStat.ts +++ b/src/lib/gear/functions/inverseOfStat.ts @@ -1,12 +1,5 @@ -import { DefenceGearStat, GearStat, OffenceGearStat } from '../types'; - -const defenceMap: { [key in DefenceGearStat]: OffenceGearStat } = { - [GearStat.DefenceSlash]: GearStat.AttackSlash, - [GearStat.DefenceStab]: GearStat.AttackStab, - [GearStat.DefenceCrush]: GearStat.AttackCrush, - [GearStat.DefenceMagic]: GearStat.AttackMagic, - [GearStat.DefenceRanged]: GearStat.AttackRanged -}; +import type { DefenceGearStat, OffenceGearStat } from '../types'; +import { GearStat } from '../types'; const offenceMap: { [key in OffenceGearStat]: DefenceGearStat } = { [GearStat.AttackSlash]: GearStat.DefenceSlash, @@ -16,10 +9,6 @@ const offenceMap: { [key in OffenceGearStat]: DefenceGearStat } = { [GearStat.AttackRanged]: GearStat.DefenceRanged }; -export function inverseOfDefenceStat(stat: DefenceGearStat) { - return defenceMap[stat]; -} - export function inverseOfOffenceStat(stat: OffenceGearStat) { return offenceMap[stat]; } diff --git a/src/lib/gear/index.ts b/src/lib/gear/index.ts index 73a5c2bdc2c..606083b4d11 100644 --- a/src/lib/gear/index.ts +++ b/src/lib/gear/index.ts @@ -1,9 +1,8 @@ -import { GearPreset } from '@prisma/client'; +import type { GearPreset } from '@prisma/client'; import { EquipmentSlot } from 'oldschooljs/dist/meta/types'; import itemID from '../util/itemID'; -import { DefenceGearStat, GearSetup, GearStat, OffenceGearStat, OtherGearStat } from './types'; -import { constructGearSetup, PartialGearSetup } from './util'; +import { type DefenceGearStat, type GearSetup, GearStat, type OffenceGearStat, type OtherGearStat } from './types'; export * from './types'; export * from './util'; @@ -46,14 +45,7 @@ export const defaultGear: GearSetup = { [EquipmentSlot.Weapon]: null }; Object.freeze(defaultGear); -export function filterGearSetup(gear: undefined | null | GearSetup | PartialGearSetup): GearSetup | undefined { - const filteredGear = !gear - ? undefined - : typeof gear.ammo === 'undefined' || typeof gear.ammo === 'string' - ? constructGearSetup(gear as PartialGearSetup) - : (gear as GearSetup); - return filteredGear; -} + export const globalPresets: GearPreset[] = [ { name: 'graceful', diff --git a/src/lib/gear/types.ts b/src/lib/gear/types.ts index 07a0d69a3bc..31f0fac9895 100644 --- a/src/lib/gear/types.ts +++ b/src/lib/gear/types.ts @@ -1,6 +1,6 @@ -import { EquipmentSlot } from 'oldschooljs/dist/meta/types'; +import type { EquipmentSlot } from 'oldschooljs/dist/meta/types'; -import { Gear } from '../structures/Gear'; +import type { Gear } from '../structures/Gear'; export type UserFullGearSetup = { [key in GearSetupType]: Gear; diff --git a/src/lib/gear/util.ts b/src/lib/gear/util.ts index 7eb48cfe434..e466900650e 100644 --- a/src/lib/gear/util.ts +++ b/src/lib/gear/util.ts @@ -1,11 +1,11 @@ import { toTitleCase } from '@oldschoolgg/toolkit'; -import { GearPreset } from '@prisma/client'; -import { EquipmentSlot, Item } from 'oldschooljs/dist/meta/types'; +import type { GearPreset } from '@prisma/client'; +import type { EquipmentSlot, Item } from 'oldschooljs/dist/meta/types'; +import type { GearSetup } from '.'; import { Gear } from '../structures/Gear'; import { itemID } from '../util'; import getOSItem from '../util/getOSItem'; -import { GearSetup } from '.'; export function itemInSlot(setup: GearSetup, slot: EquipmentSlot): [null, null] | [Item, number] { const equipped = setup[slot]; diff --git a/src/lib/giantsFoundry.ts b/src/lib/giantsFoundry.ts index 941a3fa9767..e7fc77f00bc 100644 --- a/src/lib/giantsFoundry.ts +++ b/src/lib/giantsFoundry.ts @@ -1,12 +1,10 @@ import { randInt } from 'e'; -import { assert } from './util'; - // Difference between this and ItemBank, is ItemBank is expected to have only integers as strings export interface GiantsFoundryBank { [key: string]: number; } -export const tipMoulds: string[] = [ +const tipMoulds: string[] = [ 'Saw Tip', 'Gladius Point', "Serpent's Fang", @@ -20,7 +18,7 @@ export const tipMoulds: string[] = [ 'The Point!' ]; -export const bladeMoulds: string[] = [ +const bladeMoulds: string[] = [ 'Gladius Edge', 'Stiletto Blade', 'Medusa Blade', @@ -34,7 +32,7 @@ export const bladeMoulds: string[] = [ 'Choppa!' ]; -export const forteMoulds: string[] = [ +const forteMoulds: string[] = [ 'Serrated Forte', 'Serpent Ricasso', 'Medusa Ricasso', @@ -50,14 +48,6 @@ export const forteMoulds: string[] = [ export const TOTAL_GIANT_WEAPONS = tipMoulds.length * bladeMoulds.length * forteMoulds.length; -// weaponID stored as 10-4-3 => 10, 4, 3 -export function decodeGiantWeapons(weaponID: string) { - const weaponIDs = weaponID.split('-'); - assert(weaponIDs.length === 3); - const [tipMould, bladeMould, forteMould] = weaponIDs; - return [parseInt(tipMould), parseInt(bladeMould), parseInt(forteMould)]; -} - // weaponID encoded as 10-4-3 export function encodeGiantWeapons([tip, blade, forte]: [number, number, number]) { return `${tip.toString()}-${blade.toString()}-${forte.toString()}`; diff --git a/src/lib/globals.ts b/src/lib/globals.ts new file mode 100644 index 00000000000..f75e85a675d --- /dev/null +++ b/src/lib/globals.ts @@ -0,0 +1,58 @@ +import { isMainThread } from 'node:worker_threads'; +import { TSRedis } from '@oldschoolgg/toolkit/TSRedis'; +import { PrismaClient } from '@prisma/client'; +import { PrismaClient as RobochimpPrismaClient } from '@prisma/robochimp'; + +import { production } from '../config'; +import { globalConfig } from './constants'; +import { handleDeletedPatron, handleEditPatron } from './patreonUtils'; + +declare global { + var prisma: PrismaClient; + var redis: TSRedis; + var roboChimpClient: RobochimpPrismaClient; +} + +function makePrismaClient(): PrismaClient { + if (!production && !process.env.TEST) console.log('Making prisma client...'); + if (!isMainThread && !process.env.TEST) { + throw new Error('Prisma client should only be created on the main thread.'); + } + + return new PrismaClient({ + log: ['info', 'warn', 'error'] + }); +} +global.prisma = global.prisma || makePrismaClient(); + +function makeRobochimpPrismaClient(): RobochimpPrismaClient { + if (!production && !process.env.TEST) console.log('Making robochimp client...'); + if (!isMainThread && !process.env.TEST) { + throw new Error('Robochimp client should only be created on the main thread.'); + } + + return new RobochimpPrismaClient({ + log: ['info', 'warn', 'error'] + }); +} +global.roboChimpClient = global.roboChimpClient || makeRobochimpPrismaClient(); + +function makeRedisClient(): TSRedis { + if (!production && !process.env.TEST) console.log('Making Redis client...'); + if (!isMainThread && !process.env.TEST) { + throw new Error('Redis client should only be created on the main thread.'); + } + return new TSRedis({ mocked: !globalConfig.redisPort, port: globalConfig.redisPort }); +} +global.redis = global.redis || makeRedisClient(); + +global.redis.subscribe(message => { + debugLog(`Received message from Redis: ${JSON.stringify(message)}`); + if (message.type === 'patron_tier_change') { + if (message.new_tier === 0) { + return handleDeletedPatron(message.discord_ids); + } else { + return handleEditPatron(message.discord_ids); + } + } +}); diff --git a/src/lib/grandExchange.ts b/src/lib/grandExchange.ts index 038e8e975bd..556a979e915 100644 --- a/src/lib/grandExchange.ts +++ b/src/lib/grandExchange.ts @@ -1,21 +1,24 @@ -import { GEListing, GEListingType, GETransaction } from '@prisma/client'; -import { Stopwatch } from '@sapphire/stopwatch'; -import { bold, ButtonBuilder, ButtonStyle, userMention } from 'discord.js'; -import { calcPercentOfNum, clamp, noOp, sumArr, Time } from 'e'; +import type { GEListing, GETransaction } from '@prisma/client'; +import { GEListingType } from '@prisma/client'; +import { ButtonBuilder, ButtonStyle, bold, userMention } from 'discord.js'; +import { Time, calcPercentOfNum, clamp, noOp, sumArr, uniqueArr } from 'e'; +import { LRUCache } from 'lru-cache'; import { Bank } from 'oldschooljs'; -import { Item, ItemBank } from 'oldschooljs/dist/meta/types'; +import type { Item, ItemBank } from 'oldschooljs/dist/meta/types'; import PQueue from 'p-queue'; -import { ADMIN_IDS, OWNER_IDS, production } from '../config'; import { BLACKLISTED_USERS } from './blacklists'; -import { BitField, globalConfig, ONE_TRILLION, PerkTier } from './constants'; +import { BitField, ONE_TRILLION, globalConfig } from './constants'; import { isCustomItem } from './customItems/util'; import { marketPricemap } from './marketPrices'; -import { RobochimpUser, roboChimpUserFetch } from './roboChimp'; -import { prisma } from './settings/prisma'; +import type { RobochimpUser } from './roboChimp'; +import { roboChimpUserFetch } from './roboChimp'; + +import { ADMIN_IDS, OWNER_IDS } from '../config'; import { fetchTableBank, makeTransactFromTableBankQueries } from './tableBank'; import { assert, + PerkTier, generateGrandExchangeID, getInterval, isGEUntradeable, @@ -37,7 +40,7 @@ interface CreateListingArgs { } function validateNumber(num: number) { - if (num < 0 || isNaN(num) || !Number.isInteger(num) || num >= Number.MAX_SAFE_INTEGER) { + if (num < 0 || Number.isNaN(num) || !Number.isInteger(num) || num >= Number.MAX_SAFE_INTEGER) { throw new Error(`Invalid number: ${num}.`); } } @@ -117,6 +120,13 @@ class GrandExchangeSingleton { public locked = false; public isTicking = false; public ready = false; + public loggingEnabled = false; + + log(message: string, context?: any) { + if (this.loggingEnabled) { + debugLog(message, context); + } + } public config = { maxPricePerItem: ONE_TRILLION, @@ -171,7 +181,6 @@ class GrandExchangeSingleton { try { await this.fetchOwnedBank(); await this.extensiveVerification(); - await this.checkGECanFullFilAllListings(); } catch (err: any) { await this.lockGE(err.message); } finally { @@ -179,7 +188,15 @@ class GrandExchangeSingleton { } } - async calculateSlotsOfUser(user: MUser) { + private slotsCache = new LRUCache>>({ + ttl: Time.Hour, + max: 100 + }); + async calculateSlotsOfUser( + user: MUser + ): Promise<{ slots: number; doesntHaveNames: string[]; possibleExtra: number; maxPossible: number }> { + const cached = this.slotsCache.get(user.id); + if (cached) return cached; const robochimpUser = await roboChimpUserFetch(user.id); let slots = 0; const doesntHaveNames = []; @@ -194,7 +211,9 @@ class GrandExchangeSingleton { possibleExtra += boost.amount; } } - return { slots, doesntHaveNames, possibleExtra, maxPossible }; + const result = { slots, doesntHaveNames, possibleExtra, maxPossible }; + this.slotsCache.set(user.id, result); + return result; } getInterval() { @@ -225,12 +244,13 @@ class GrandExchangeSingleton { async lockGE(reason: string) { if (this.locked) return; + debugLog(`The Grand Exchange has encountered an error and has been locked. Reason: ${reason}`); const idsToNotify = [...ADMIN_IDS, ...OWNER_IDS]; await sendToChannelID(globalConfig.geAdminChannelID, { content: `The Grand Exchange has encountered an error and has been locked. Reason: ${reason}. ${idsToNotify .map(i => userMention(i)) .join(', ')}`, - allowedMentions: production ? { users: idsToNotify } : undefined + allowedMentions: globalConfig.isProduction ? { users: idsToNotify } : undefined }).catch(noOp); await mahojiClientSettingsUpdate({ grand_exchange_is_locked: true @@ -238,6 +258,10 @@ class GrandExchangeSingleton { this.locked = true; } + getItemBuyLimit(item: Item) { + return item.buy_limit ?? this.config.buyLimit.fallbackBuyLimit(item); + } + async checkBuyLimitForListing(geListing: GEListing) { const interval = this.getInterval(); @@ -254,8 +278,6 @@ class GrandExchangeSingleton { } }); - for (const tx of allActiveListingsInTimePeriod) sanityCheckTransaction(tx); - const item = getOSItem(geListing.item_id); let buyLimit = item.buy_limit ?? this.config.buyLimit.fallbackBuyLimit(item); if (!isCustomItem(item.id)) { @@ -299,12 +321,24 @@ class GrandExchangeSingleton { return { error: 'Invalid item.' }; } - if (!price || price <= 0 || isNaN(price) || !Number.isInteger(price) || price > this.config.maxPricePerItem) { + if ( + !price || + price <= 0 || + Number.isNaN(price) || + !Number.isInteger(price) || + price > this.config.maxPricePerItem + ) { return { error: `Invalid price, the price must be a number between 1 and ${toKMB(this.config.maxPricePerItem)}.` }; } - if (!quantity || quantity <= 0 || isNaN(quantity) || !Number.isInteger(quantity) || quantity > 5_000_000) { + if ( + !quantity || + quantity <= 0 || + Number.isNaN(quantity) || + !Number.isInteger(quantity) || + quantity > 5_000_000 + ) { return { error: 'Invalid quantity, the quantity must be a number between 1 and 5m.' }; } @@ -356,8 +390,8 @@ ${type} ${toKMB(quantity)} ${item.name} for ${toKMB(price)} each, for a total of type === 'Buy' ? '' : applicableTax.taxedAmount > 0 - ? ` At this price, you will receive ${toKMB(totalAfterTax)} after taxes.` - : ' No tax will be charged on these items.' + ? ` At this price, you will receive ${toKMB(totalAfterTax)} after taxes.` + : ' No tax will be charged on these items.' }`; const guidePrice = marketPricemap.get(item.id); @@ -404,7 +438,8 @@ ${type} ${toKMB(quantity)} ${item.name} for ${toKMB(price)} each, for a total of ...makeTransactFromTableBankQueries({ bankToAdd: result.cost }) ]); - debugLog(`${user.id} created ${type} listing, removing ${result.cost}, adding it to the g.e bank.`); + sanityCheckListing(listing); + this.log(`${user.id} created ${type} listing, removing ${result.cost}, adding it to the g.e bank.`); return { createdListing: listing, @@ -418,7 +453,7 @@ ${type} ${toKMB(quantity)} ${item.name} for ${toKMB(price)} each, for a total of sellerListing: GEListing, remainingItemsInBuyLimit: number ) { - let logContext: Record = { + const logContext: Record = { buyerListingID: buyerListing.id.toString(), sellerListingID: sellerListing.id.toString(), type: 'GE_TRANSACTION' @@ -454,7 +489,7 @@ ${type} ${toKMB(quantity)} ${item.name} for ${toKMB(price)} each, for a total of } let priceWinner: 'buyer' | 'seller' = 'buyer'; - let pricePerItemBeforeTax: number = -1; + let pricePerItemBeforeTax = -1; if (buyerListing.created_at < sellerListing.created_at) { pricePerItemBeforeTax = Number(buyerListing.asking_price_per_item); priceWinner = 'buyer'; @@ -514,7 +549,7 @@ ${type} ${toKMB(quantity)} ${item.name} for ${toKMB(price)} each, for a total of buyerLoot.add('Coins', buyerRefund); bankToRemoveFromGeBank.add('Coins', buyerRefund); - debugLog( + this.log( `Buyer got refunded ${buyerRefund} GP due to price difference. Buyer was asking ${buyerListing.asking_price_per_item}GP for each of the ${quantityToBuy}x items, seller was asking ${sellerListing.asking_price_per_item}GP, and the post-tax price per item was ${pricePerItemAfterTax}`, logContext ); @@ -527,9 +562,9 @@ ${type} ${toKMB(quantity)} ${item.name} for ${toKMB(price)} each, for a total of buyerListing.asking_price_per_item }] SellerPrice[${ sellerListing.asking_price_per_item - }] TotalPriceBeforeTax[${totalPriceBeforeTax}] QuantityToBuy[${quantityToBuy}] TotalTaxPaid[${totalTaxPaid}] BuyerRefund[${buyerRefund}] BuyerLoot[${buyerLoot}] SellerLoot[${sellerLoot}] CurrentGEBank[${geBank}] BankToRemoveFromGeBank[${bankToRemoveFromGeBank}] ExpectedAfterBank[${geBank - .clone() - .remove(bankToRemoveFromGeBank)}]`; + }] TotalPriceBeforeTax[${totalPriceBeforeTax}] QuantityToBuy[${quantityToBuy}] TotalTaxPaid[${totalTaxPaid}] BuyerRefund[${buyerRefund}] BuyerLoot[${JSON.stringify(buyerLoot)}] SellerLoot[${sellerLoot}] CurrentGEBank[${JSON.stringify(geBank)}] BankToRemoveFromGeBank[${JSON.stringify(bankToRemoveFromGeBank.bank)}] ExpectedAfterBank[${ + geBank.clone().remove(bankToRemoveFromGeBank).bank + }]`; assert( bankToRemoveFromGeBank.amount('Coins') === Number(buyerListing.asking_price_per_item) * quantityToBuy, @@ -542,22 +577,22 @@ ${type} ${toKMB(quantity)} ${item.name} for ${toKMB(price)} each, for a total of const missingItems = bankGEShouldHave.clone().remove(geBank); const str = `The GE did not have enough items to cover this transaction! We tried to remove ${bankGEShouldHave} missing: ${missingItems}. ${debug}`; logError(str, logContext); - debugLog(str, logContext); + this.log(str, logContext); throw new Error(str); } - debugLog( - `Completing a transaction, removing ${bankToRemoveFromGeBank} from the GE bank, ${totalTaxPaid} in taxed gp. The current GE bank is ${geBank.toString()}. ${debug}`, + this.log( + `Completing a transaction, removing ${JSON.stringify(bankToRemoveFromGeBank.bank)} from the GE bank, ${totalTaxPaid} in taxed gp. The current GE bank is ${JSON.stringify(geBank.bank)}. ${debug}`, { totalPriceAfterTax, totalTaxPaid, totalPriceBeforeTax, bankToRemoveFromGeBank: bankToRemoveFromGeBank.toString(), - currentGEBank: geBank.toString() + currentGEBank: JSON.stringify(geBank.bank) } ); - await prisma.$transaction([ + const [newTx] = await prisma.$transaction([ prisma.gETransaction.create({ data: { buy_listing_id: buyerListing.id, @@ -607,7 +642,9 @@ ${type} ${toKMB(quantity)} ${item.name} for ${toKMB(price)} each, for a total of ...makeTransactFromTableBankQueries({ bankToRemove: bankToRemoveFromGeBank }) ]); - debugLog(`Transaction completed, the new G.E bank is ${await this.fetchOwnedBank()}.`); + sanityCheckTransaction(newTx); + + this.log(`Transaction completed, the new G.E bank is ${JSON.stringify((await this.fetchOwnedBank()).bank)}.`); const buyerUser = await mUserFetch(buyerListing.user_id); const sellerUser = await mUserFetch(sellerListing.user_id); @@ -695,29 +732,36 @@ ${type} ${toKMB(quantity)} ${item.name} for ${toKMB(price)} each, for a total of } async fetchActiveListings() { - const [buyListings, sellListings, clientStorage, currentBankRaw] = await prisma.$transaction([ - prisma.gEListing.findMany({ - where: { - type: GEListingType.Buy, - fulfilled_at: null, - cancelled_at: null, - user_id: { not: null } + const buyListings = await prisma.gEListing.findMany({ + where: { + type: GEListingType.Buy, + fulfilled_at: null, + cancelled_at: null, + user_id: { + not: null + } + }, + orderBy: [ + { + asking_price_per_item: 'desc' }, - orderBy: [ - { - asking_price_per_item: 'desc' - }, - { - created_at: 'asc' - } - ] - }), + { + created_at: 'asc' + } + ] + }); + const [sellListings, clientStorage, currentBankRaw] = await prisma.$transaction([ prisma.gEListing.findMany({ where: { type: GEListingType.Sell, fulfilled_at: null, cancelled_at: null, - user_id: { not: null } + user_id: { + not: null + }, + item_id: { + in: uniqueArr(buyListings.map(i => i.item_id)) + } }, orderBy: [ { @@ -757,23 +801,63 @@ ${type} ${toKMB(quantity)} ${item.name} for ${toKMB(price)} each, for a total of } async extensiveVerification() { - const allListings = await prisma.gEListing.findMany(); - for (const listing of allListings) sanityCheckListing(listing); - - const allTransactions = await prisma.gETransaction.findMany(); - for (const transaction of allTransactions) sanityCheckTransaction(transaction); - await this.checkGECanFullFilAllListings(); - - debugLog('Validated GE and found no issues.'); - return true; } async checkGECanFullFilAllListings() { const shouldHave = new Bank(); - const { buyListings, sellListings, currentBank } = await this.fetchActiveListings(); - + const [buyListings, sellListings, currentBankRaw] = await prisma.$transaction([ + prisma.gEListing.findMany({ + where: { + type: GEListingType.Buy, + fulfilled_at: null, + cancelled_at: null, + user_id: { + not: null + } + }, + orderBy: [ + { + asking_price_per_item: 'desc' + }, + { + created_at: 'asc' + } + ] + }), + prisma.gEListing.findMany({ + where: { + type: GEListingType.Sell, + fulfilled_at: null, + cancelled_at: null, + user_id: { + not: null + } + }, + orderBy: [ + { + asking_price_per_item: 'asc' + }, + { + created_at: 'asc' + } + ], + // Take the last purchase transaction for each sell listing + include: { + sellTransactions: { + orderBy: { + created_at: 'desc' + }, + take: 1 + } + } + }), + prisma.$queryRawUnsafe<{ bank: ItemBank }[]>( + 'SELECT json_object_agg(item_id, quantity) as bank FROM ge_bank WHERE quantity != 0;' + ) + ]); + const currentBank = new Bank(currentBankRaw[0].bank); // How much GP the g.e still has from this listing for (const listing of buyListings) { shouldHave.add('Coins', Number(listing.asking_price_per_item) * listing.quantity_remaining); @@ -783,7 +867,7 @@ ${type} ${toKMB(quantity)} ${item.name} for ${toKMB(price)} each, for a total of shouldHave.add(listing.item_id, listing.quantity_remaining); } - debugLog(`Expected G.E Bank: ${shouldHave}`); + this.log(`Expected G.E Bank: ${JSON.stringify(shouldHave.bank)}`); if (!currentBank.equals(shouldHave)) { if (!currentBank.has(shouldHave)) { throw new Error( @@ -798,26 +882,21 @@ G.E Bank Has: ${currentBank} G.E Bank Should Have: ${shouldHave} Difference: ${shouldHave.difference(currentBank)}`); } else { - debugLog( - `GE has ${currentBank}, which is enough to cover the ${ - [...buyListings, ...sellListings].length - }x active listings! Difference: ${shouldHave.difference(currentBank)}` - ); + this.log('GE has enough to cover the listings.'); return true; } } async tick() { - return new Promise((resolve, reject) => { - this.queue.add(async () => { - if (this.isTicking) return reject(new Error('Already ticking.')); - this.isTicking = true; + return new Promise(async (resolve, reject) => { + await this.queue.add(async () => { + if (this.isTicking) return reject('Already ticking.'); try { await this._tick(); } catch (err: any) { logError(err.message); debugLog(err.message); - return reject(err); + reject(err); } finally { this.isTicking = false; resolve(); @@ -849,22 +928,35 @@ Difference: ${shouldHave.difference(currentBank)}`); private async _tick() { if (!this.ready) return; if (this.locked) return; - const stopwatch = new Stopwatch(); - stopwatch.start(); - const { buyListings: _buyListings, sellListings: _sellListings } = await this.fetchActiveListings(); + const { buyListings, sellListings } = await this.fetchActiveListings(); - // Filter out listings from Blacklisted users: - const buyListings = _buyListings.filter(l => !BLACKLISTED_USERS.has(l.user_id!)); - const sellListings = _sellListings.filter(l => !BLACKLISTED_USERS.has(l.user_id!)); + const minimumSellPricePerItem = new Map(); + for (const sellListing of sellListings) { + const currentPrice = minimumSellPricePerItem.get(sellListing.item_id); + if (currentPrice === undefined || sellListing.asking_price_per_item < currentPrice) { + minimumSellPricePerItem.set(sellListing.item_id, Number(sellListing.asking_price_per_item)); + } + } for (const buyListing of buyListings) { + const minPrice = minimumSellPricePerItem.get(buyListing.item_id); + if (!buyListing.user_id || minPrice === undefined || buyListing.asking_price_per_item < minPrice) { + continue; + } + + if (BLACKLISTED_USERS.has(buyListing.user_id)) { + continue; + } + // These are all valid, matching sell listings we can match with this buy listing. const matchingSellListings = sellListings.filter( sellListing => sellListing.item_id === buyListing.item_id && // "Trades succeed when one player's buy offer is greater than or equal to another player's sell offer." buyListing.asking_price_per_item >= sellListing.asking_price_per_item && - buyListing.user_id !== sellListing.user_id + buyListing.user_id !== sellListing.user_id && + sellListing.user_id !== null && + !BLACKLISTED_USERS.has(sellListing.user_id) ); /** @@ -898,12 +990,10 @@ Difference: ${shouldHave.difference(currentBank)}`); // Process only one transaction per tick break; } - - stopwatch.stop(); } async totalReset() { - if (production) throw new Error("You can't reset the GE in production."); + if (globalConfig.isProduction) throw new Error("You can't reset the GE in production."); await mahojiClientSettingsUpdate({ grand_exchange_is_locked: false, grand_exchange_tax_bank: 0, diff --git a/src/lib/growablePets.ts b/src/lib/growablePets.ts index d1d48a01d58..a833c147763 100644 --- a/src/lib/growablePets.ts +++ b/src/lib/growablePets.ts @@ -1,9 +1,9 @@ -import { randFloat, roll, Time } from 'e'; import { Bank } from 'oldschooljs'; +import { resolveItems } from 'oldschooljs/dist/util/util'; -import { ActivityTaskOptions } from './types/minions'; +import { Time, randFloat, roll } from 'e'; +import type { ActivityTaskOptions } from './types/minions'; import getOSItem from './util/getOSItem'; -import resolveItems from './util/resolveItems'; export const kittens = resolveItems([ 'Grey and black kitten', @@ -99,6 +99,5 @@ export async function handleGrowablePetGrowth(user: MUser, data: ActivityTaskOpt } export const growablePetsCL = growablePets - .map(i => i.stages) - .flat() + .flatMap(i => i.stages) .filter(i => !resolveItems(['Skip', 'Penguin egg']).includes(i)); diff --git a/src/lib/handleNewCLItems.ts b/src/lib/handleNewCLItems.ts index ebcf7e5efaf..9ee23af33be 100644 --- a/src/lib/handleNewCLItems.ts +++ b/src/lib/handleNewCLItems.ts @@ -1,19 +1,20 @@ import { formatOrdinal, roboChimpCLRankQuery } from '@oldschoolgg/toolkit'; -import { Prisma, UserEventType } from '@prisma/client'; +import type { Prisma } from '@prisma/client'; +import { UserEventType } from '@prisma/client'; import { roll, sumArr } from 'e'; -import { Bank } from 'oldschooljs'; +import type { Bank } from 'oldschooljs'; import { Events } from './constants'; import { allCLItems, allCollectionLogsFlat, calcCLDetails } from './data/Collections'; import { calculateMastery } from './mastery'; import { calculateOwnCLRanking, roboChimpSyncData } from './roboChimp'; -import { prisma } from './settings/prisma'; + +import { RawSQL } from './rawSql'; import { MUserStats } from './structures/MUserStats'; -import { fetchStatsForCL } from './util'; import { fetchCLLeaderboard } from './util/clLeaderboard'; import { insertUserEvent } from './util/userEvents'; -export async function createHistoricalData(user: MUser): Promise { +async function createHistoricalData(user: MUser): Promise { const clStats = calcCLDetails(user); const clRank = await roboChimpClient.$queryRawUnsafe<{ count: number }[]>(roboChimpCLRankQuery(BigInt(user.id))); const { totalMastery, compCapeProgress } = await calculateMastery(user, await MUserStats.fromID(user.id)); @@ -31,32 +32,6 @@ export async function createHistoricalData(user: MUser): Promise Number(i)); - const updateObj = { - cl_array: newCLArray, - cl_array_length: newCLArray.length - } as const; - - await prisma.userStats.upsert({ - where: { - user_id: id - }, - create: { - user_id: id, - ...updateObj - }, - update: { - ...updateObj - } - }); - - return { - newCLArray - }; -} - export async function handleNewCLItems({ itemsAdded, user, @@ -77,12 +52,16 @@ export async function handleNewCLItems({ await prisma.historicalData.create({ data: await createHistoricalData(user) }); } + if (didGetNewCLItem) { + await prisma.$queryRawUnsafe(RawSQL.updateCLArray(user.id)); + } + if (!didGetNewCLItem) return; const previousCLDetails = calcCLDetails(previousCL); const previousCLRank = previousCLDetails.percent >= 80 ? await calculateOwnCLRanking(user.id) : null; - await Promise.all([roboChimpSyncData(user), clArrayUpdate(user, newCL)]); + await roboChimpSyncData(user, newCL); const newCLRank = previousCLDetails.percent >= 80 ? await calculateOwnCLRanking(user.id) : null; const newCLDetails = calcCLDetails(newCL); @@ -127,19 +106,18 @@ export async function handleNewCLItems({ getKC: (id: number) => user.getKC(id), user, minigames: await user.fetchMinigames(), - stats: await fetchStatsForCL(user) - })}!` + stats: await MUserStats.fromID(user.id) + })}!` : ''; - const nthUser = ( - await fetchCLLeaderboard({ - ironmenOnly: false, - items: finishedCL.items, - resultLimit: 100_000, - method: 'raw_cl', - userEvents: null - }) - ).filter(u => u.qty === finishedCL.items.length).length; + const leaderboardUsers = await fetchCLLeaderboard({ + ironmenOnly: false, + items: finishedCL.items, + resultLimit: 100_000, + clName: finishedCL.name + }); + + const nthUser = leaderboardUsers.users.filter(u => u.qty === finishedCL.items.length).length; const placeStr = nthUser > 100 ? '' : ` They are the ${formatOrdinal(nthUser)} user to finish this CL.`; diff --git a/src/lib/http/githubApiTypes.ts b/src/lib/http/githubApiTypes.ts deleted file mode 100644 index 7c743fe84fe..00000000000 --- a/src/lib/http/githubApiTypes.ts +++ /dev/null @@ -1,105 +0,0 @@ -export interface GithubSponsorsWebhookData { - action: 'created' | 'cancelled' | 'edited' | 'tier_changed' | 'pending_cancellation' | 'pending_tier_change'; - sponsorship: Sponsorship; - sender: GithubUser; - changes?: Changes; - effective_date?: string; -} - -interface Sponsorship { - node_id: string; - created_at: string; - sponsorable: Sponsorable; - sponsor: Sponsor; - privacy_level: string; - tier: Tier; -} - -interface Sponsorable { - login: string; - id: number; - node_id: string; - avatar_url: string; - gravatar_id: string; - url: string; - html_url: string; - followers_url: string; - following_url: string; - gists_url: string; - starred_url: string; - subscriptions_url: string; - organizations_url: string; - repos_url: string; - events_url: string; - received_events_url: string; - type: string; - site_admin: boolean; -} - -interface Sponsor { - login: string; - id: number; - node_id: string; - avatar_url: string; - gravatar_id: string; - url: string; - html_url: string; - followers_url: string; - following_url: string; - gists_url: string; - starred_url: string; - subscriptions_url: string; - organizations_url: string; - repos_url: string; - events_url: string; - received_events_url: string; - type: string; - site_admin: boolean; -} - -interface Tier { - node_id: string; - created_at: string; - description: string; - monthly_price_in_cents: number; - monthly_price_in_dollars: number; - name: string; -} - -interface GithubUser { - login: string; - id: number; - node_id: string; - avatar_url: string; - gravatar_id: string; - url: string; - html_url: string; - followers_url: string; - following_url: string; - gists_url: string; - starred_url: string; - subscriptions_url: string; - organizations_url: string; - repos_url: string; - events_url: string; - received_events_url: string; - type: string; - site_admin: boolean; -} - -interface Changes { - tier: Tier2; -} - -interface Tier2 { - from: From; -} - -interface From { - node_id: string; - created_at: string; - description: string; - monthly_price_in_cents: number; - monthly_price_in_dollars: number; - name: string; -} diff --git a/src/lib/http/index.ts b/src/lib/http/index.ts deleted file mode 100644 index f49f086c759..00000000000 --- a/src/lib/http/index.ts +++ /dev/null @@ -1,74 +0,0 @@ -import fastifyCors from '@fastify/cors'; -import fastifyHelmet from '@fastify/helmet'; -import fastifySensible from '@fastify/sensible'; -import fastify from 'fastify'; -import fastifyRawBody from 'fastify-raw-body'; - -import { production } from '../../config'; -import { globalConfig } from '../constants'; -import { cryptoRand } from '../util'; -import { logError } from '../util/logError'; -import { initRoutes } from './routes'; - -export async function makeServer(port = globalConfig.httpPort) { - if (process.env.TEST) { - port = cryptoRand(10_000, 19_999); - } - const server = fastify({ - logger: false, - trustProxy: true - }); - - server.register(fastifyRawBody, { - field: 'rawBody', // change the default request.rawBody property name - global: true, // add the rawBody to every request. **Default true** - encoding: 'utf8', // set it to false to set rawBody as a Buffer **Default utf8** - runFirst: false, // get the body before any preParsing hook change/uncompress it. **Default false** - routes: [] // array of routes, **`global`** will be ignored, wildcard routes not supported - }); - - server.register(fastifySensible); - - await server.register(import('@fastify/rate-limit'), { - errorResponseBuilder: () => { - return server.httpErrors.tooManyRequests(); - } - }); - - server.setErrorHandler((error, _request, reply) => { - if (reply.statusCode) { - reply.send(error); - return; - } - if (production) { - logError(error); - reply.internalServerError(); - } else { - console.error(error); - reply.internalServerError(`A very bad error just occurred! ${error.message}`); - } - }); - - server.register(fastifyHelmet); - - server.register(fastifyCors); - - server.addContentTypeParser('text/plain', async () => { - throw server.httpErrors.badRequest('Bad content type.'); - }); - - server.addHook('onRequest', (request, _, done) => { - debugLog('Received HTTP request', { - type: 'HTTP_REQUEST', - url: request.raw.url, - method: request.raw.method, - headers: request.raw.headers - }); - done(); - }); - - initRoutes(server); - - await server.listen({ port }); - return server; -} diff --git a/src/lib/http/routes/commands.ts b/src/lib/http/routes/commands.ts deleted file mode 100644 index 4391392125e..00000000000 --- a/src/lib/http/routes/commands.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Time } from 'e'; -import { ApplicationCommandOptionType } from 'mahoji'; - -import { AbstractCommand } from '../../../mahoji/lib/inhibitors'; -import { allAbstractCommands } from '../../../mahoji/lib/util'; -import { stringMatches } from '../../util'; -import { FastifyServer } from '../types'; - -export const commandsRoute = (server: FastifyServer) => - server.route({ - method: 'GET', - url: '/commands', - async handler(_, reply) { - const mahojiCommands = globalClient.mahojiClient.commands.values; - const commandData = allAbstractCommands(globalClient.mahojiClient) - .filter(c => typeof c.attributes?.description === 'string' && c.attributes.description.length > 1) - .filter(i => !['admin'].includes(i.name)) - .map((cmd: AbstractCommand) => { - const mahojiCommand = mahojiCommands.find(i => stringMatches(i.name, cmd.name)); - const subOptions: string[] = []; - if (mahojiCommand) { - for (const option of mahojiCommand.options) { - if ( - option.type === ApplicationCommandOptionType.SubcommandGroup || - option.type === ApplicationCommandOptionType.Subcommand - ) { - subOptions.push(option.name); - } - } - } - return { - name: cmd.name, - desc: cmd.attributes?.description, - examples: cmd.attributes?.examples, - flags: cmd.attributes?.categoryFlags, - subOptions - }; - }) - .sort((a, b) => a.name.localeCompare(b.name)); - reply.header( - 'Cache-Control', - `public, max-age=${(Time.Minute * 5) / 1000}, s-maxage=${(Time.Minute * 5) / 1000}` - ); - reply.send(commandData); - } - }); diff --git a/src/lib/http/routes/index.ts b/src/lib/http/routes/index.ts deleted file mode 100644 index ccceab6eb87..00000000000 --- a/src/lib/http/routes/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { FastifyServer } from '../types'; -import { commandsRoute } from './commands'; -import root from './root'; -import githubSponsors from './webhooks/githubSponsors'; -import { patreonRoute } from './webhooks/patreon'; - -export const initRoutes = (server: FastifyServer) => - [root, githubSponsors, commandsRoute, patreonRoute].map(route => route(server)); diff --git a/src/lib/http/routes/root.ts b/src/lib/http/routes/root.ts deleted file mode 100644 index 8af2e114107..00000000000 --- a/src/lib/http/routes/root.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { FastifyServer } from '../types'; -import { rateLimit } from '../util'; - -export default (server: FastifyServer) => - server.route({ - method: 'GET', - url: '/', - async handler(request, reply) { - reply.send(`${JSON.stringify(request.headers, null, 4)}\n\n\n${server.printRoutes()}`); - }, - config: { - ...rateLimit(1, '1 minute') - } - }); diff --git a/src/lib/http/routes/webhooks/githubSponsors.ts b/src/lib/http/routes/webhooks/githubSponsors.ts deleted file mode 100644 index 86331fc0f51..00000000000 --- a/src/lib/http/routes/webhooks/githubSponsors.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { boxFrenzy } from '../../../boxFrenzy'; -import { Channel } from '../../../constants'; -import { addPatronLootTime } from '../../../doubleLoot'; -import { patreonTask } from '../../../patreon'; -import { syncLinkedAccounts } from '../../../util/linkedAccountsUtil'; -import { sendToChannelID } from '../../../util/webhook'; -import { GithubSponsorsWebhookData } from '../../githubApiTypes'; -import { FastifyServer } from '../../types'; -import { getUserIdFromGithubID, parseStrToTier, verifyGithubSecret } from '../../util'; - -const githubSponsors = (server: FastifyServer) => - server.route({ - method: 'POST', - url: '/webhooks/github_sponsors', - async handler(request, reply) { - const isVerified = verifyGithubSecret(JSON.stringify(request.body), request.headers['x-hub-signature']); - if (!isVerified) { - throw reply.badRequest(); - } - const data = request.body as GithubSponsorsWebhookData; - const userID = await getUserIdFromGithubID(data.sender.id.toString()); - // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check - switch (data.action) { - case 'created': { - const tier = parseStrToTier(data.sponsorship.tier.name); - if (!tier) return; - sendToChannelID(Channel.PatronLogs, { - content: `${data.sender.login}[${data.sender.id}] became a Tier ${tier - 1} sponsor.` - }); - if (userID) { - await patreonTask.givePerks(userID, tier); - addPatronLootTime(tier, await mUserFetch(userID)); - } - - for (const id of [Channel.BSOChannel, Channel.BSOGeneral]) { - boxFrenzy( - id, - `🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉 -🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉 -${data.sender.login} became a Github sponsor, as a reward for everyone, here is a box frenzy, guess any of the items in the image for a mystery box. -🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉 -🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉`, - tier * 3 - ); - } - break; - } - case 'tier_changed': - case 'pending_tier_change': { - const from = parseStrToTier(data.changes!.tier.from.name); - const to = parseStrToTier(data.sponsorship.tier.name); - if (!from || !to) return; - sendToChannelID(Channel.PatronLogs, { - content: `${data.sender.login}[${data.sender.id}] changed their sponsorship from Tier ${ - from - 1 - } to Tier ${to - 1}.` - }); - if (userID) { - await patreonTask.changeTier(userID, from, to); - } - break; - } - case 'cancelled': { - const tier = parseStrToTier(data.sponsorship.tier.name); - if (!tier) return; - if (userID) { - await patreonTask.removePerks(userID); - } - - sendToChannelID(Channel.PatronLogs, { - content: `${data.sender.login}[${data.sender.id}] cancelled being a Tier ${tier - 1} sponsor. ${ - userID ? 'Removing perks.' : "Cant remove perks because couldn't find discord user." - }` - }); - - break; - } - } - syncLinkedAccounts(); - return reply.send({}); - }, - config: {} - }); - -export default githubSponsors; diff --git a/src/lib/http/routes/webhooks/patreon.ts b/src/lib/http/routes/webhooks/patreon.ts deleted file mode 100644 index ff2f38c0def..00000000000 --- a/src/lib/http/routes/webhooks/patreon.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { patreonTask } from '../../../patreon'; -import { FastifyServer } from '../../types'; -import { verifyPatreonSecret } from '../../util'; - -export const patreonRoute = (server: FastifyServer) => - server.route({ - method: 'POST', - url: '/webhooks/patreon', - async handler(request, reply) { - if (!request.rawBody || typeof request.rawBody !== 'string') { - return reply.badRequest(); - } - const isVerified = verifyPatreonSecret(request.rawBody, request.headers['x-patreon-signature']); - if (!isVerified) { - return reply.unauthorized(); - } - patreonTask.run(); - return reply.send({}); - }, - config: {} - }); diff --git a/src/lib/http/types.ts b/src/lib/http/types.ts deleted file mode 100644 index 7937b8f7e3e..00000000000 --- a/src/lib/http/types.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { FastifyInstance, FastifyLoggerInstance } from 'fastify'; -import { IncomingMessage, Server, ServerResponse } from 'http'; - -interface UserAuth { - token: string; - user_id: string; -} - -declare module 'fastify' { - interface FastifyRequest { - auth: UserAuth | undefined; - } -} - -export type FastifyServer = FastifyInstance; diff --git a/src/lib/http/util.ts b/src/lib/http/util.ts deleted file mode 100644 index 2c2910adc05..00000000000 --- a/src/lib/http/util.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { graphql } from '@octokit/graphql'; -import { createHmac } from 'crypto'; - -import { CLIENT_SECRET, GITHUB_TOKEN } from '../../config'; -import { globalConfig, PerkTier } from '../constants'; - -export function rateLimit(max: number, timeWindow: string) { - return { - rateLimit: { - max, - timeWindow - } - }; -} - -export function verifyGithubSecret(body: string, signature?: string | string[]): boolean { - if (!signature) { - return false; - } - const hmac = createHmac('sha1', CLIENT_SECRET); - hmac.update(body); - const calculated = `sha1=${hmac.digest('hex')}`; - return signature === calculated; -} - -export function verifyPatreonSecret(body: string, signature?: string | string[]): boolean { - if (!signature) { - return false; - } - const hmac = createHmac('md5', globalConfig.patreonWebhookSecret); - hmac.update(body); - const calculated = hmac.digest('hex'); - return signature === calculated; -} - -export const graphqlWithAuth = graphql.defaults({ - headers: { - authorization: `token ${GITHUB_TOKEN}` - } -}); - -export function parseStrToTier(str: string) { - switch (str) { - case '$3 a month': - return PerkTier.Two; - case '$6 a month': - return PerkTier.Three; - case '$14 a month': - return PerkTier.Four; - case '$23 a month': - return PerkTier.Five; - case '$46 a month': - return PerkTier.Six; - case '$99 a month': - return PerkTier.Seven; - default: - return null; - } -} - -interface Sponsor { - githubID: string; - githubName: string; - tier: PerkTier; - createdAt: Date; - private: boolean; -} - -export async function fetchSponsors() { - const res: any = await graphqlWithAuth( - ` - { - viewer { - sponsorshipsAsMaintainer(includePrivate: true, first: 100) { - totalCount - pageInfo { - startCursor - hasPreviousPage - hasNextPage - endCursor - } - nodes { - id - privacyLevel - createdAt - tier { - name - } - sponsorEntity { - ... on User { - databaseId - login - } - } - } - } - id - } - } - - - ` - ); - const data: Sponsor[] = res.viewer.sponsorshipsAsMaintainer.nodes - .map((node: any) => ({ - githubID: node.sponsorEntity.databaseId, - githubName: node.sponsorEntity.login as string, - tier: parseStrToTier(node.tier.name), - createdAt: new Date(node.createdAt), - private: node.privacyLevel !== 'PUBLIC' - })) - .sort((a: any, b: any) => b.createdAt - a.createdAt); - - return data; -} - -export async function getUserIdFromGithubID(githubID: string) { - const result = await roboChimpClient.user.findFirst({ - select: { id: true }, - where: { github_id: Number(githubID) } - }); - if (!result) return null; - return result.id.toString(); -} diff --git a/src/lib/implings.ts b/src/lib/implings.ts index b0722ef37a3..46768c38c86 100644 --- a/src/lib/implings.ts +++ b/src/lib/implings.ts @@ -1,11 +1,11 @@ import { activity_type_enum } from '@prisma/client'; -import { objectEntries, reduceNumByPercent, Time } from 'e'; +import { Time, objectEntries, reduceNumByPercent } from 'e'; import { Bank, LootTable, Openables } from 'oldschooljs'; import { BitField } from './constants'; -import { inventionBoosts, InventionID, inventionItemBoost } from './invention/inventions'; +import { InventionID, inventionBoosts, inventionItemBoost } from './invention/inventions'; import { ChimplingImpling, EternalImpling, InfernalImpling, MysteryImpling } from './simulation/customImplings'; -import { ActivityTaskData } from './types/minions'; +import type { ActivityTaskData } from './types/minions'; import activityInArea, { WorldLocations } from './util/activityInArea'; const { @@ -101,7 +101,7 @@ export const puroImpHighTierTable = new LootTable() .add('Dragon impling jar', 1, 9) .add('Lucky impling jar', 1, 1); -export const defaultImpTable = new LootTable() +const defaultImpTable = new LootTable() .add('Baby impling jar', 1, 28_280) .add('Young impling jar', 1, 28_280) .add('Gourmet impling jar', 1, 35_350) @@ -167,7 +167,8 @@ export async function handlePassiveImplings( activity_type_enum.TombsOfAmascut, activity_type_enum.BalthazarsBigBonanza, activity_type_enum.DriftNet, - activity_type_enum.UnderwaterAgilityThieving + activity_type_enum.UnderwaterAgilityThieving, + activity_type_enum.Colosseum ].includes(data.type) ) return null; @@ -177,7 +178,7 @@ export async function handlePassiveImplings( const skills = user.skillsAsLevels; const level = skills.hunter; - let bank = new Bank(); + const bank = new Bank(); const missed = new Bank(); let baseChance = IMPLING_CHANCE_PER_MINUTE; diff --git a/src/lib/invention/MaterialBank.ts b/src/lib/invention/MaterialBank.ts index ef34ca1444b..91463e1650a 100644 --- a/src/lib/invention/MaterialBank.ts +++ b/src/lib/invention/MaterialBank.ts @@ -1,8 +1,8 @@ import { toTitleCase } from '@oldschoolgg/toolkit'; import { calcPercentOfNum } from 'e'; +import { type IMaterialBank, type MaterialType, materialTypes } from '.'; import { assert } from '../util'; -import { IMaterialBank, MaterialType, materialTypes } from '.'; export class MaterialBank { public bank: IMaterialBank; @@ -20,11 +20,12 @@ export class MaterialBank { for (const [key, value] of Object.entries(this.bank)) { if (!materialTypes.includes(key as any)) { delete this.bank[key as keyof IMaterialBank]; - return this.validate(); + this.validate(); + return; } assert(materialTypes.includes(key as any), `${key} ${value}`); - assert(typeof value === 'number' && value > 0 && !isNaN(value)); - assert(parseInt(value.toString()) === value, `${key} ${value} ${parseInt(value.toString())}`); + assert(typeof value === 'number' && value > 0 && !Number.isNaN(value)); + assert(Number.parseInt(value.toString()) === value, `${key} ${value} ${Number.parseInt(value.toString())}`); } } @@ -95,7 +96,7 @@ export class MaterialBank { } public values() { - let values: { type: MaterialType; quantity: number }[] = []; + const values: { type: MaterialType; quantity: number }[] = []; for (const [key, value] of Object.entries(this.bank)) { values.push({ type: key as MaterialType, quantity: value }); } @@ -120,7 +121,7 @@ export class MaterialBank { mutReduceAllValuesByPercent(percent: number) { for (const [key, value] of Object.entries(this.bank) as [MaterialType, number][]) { - let percentOfThisItem = Math.floor(calcPercentOfNum(value, percent)); + const percentOfThisItem = Math.floor(calcPercentOfNum(value, percent)); this.remove(key, percentOfThisItem); } return this; @@ -128,7 +129,7 @@ export class MaterialBank { mutIncreaseAllValuesByPercent(percent: number) { for (const [key, value] of Object.entries(this.bank) as [MaterialType, number][]) { - let percentOfThisItem = Math.floor(calcPercentOfNum(value, percent)); + const percentOfThisItem = Math.floor(calcPercentOfNum(value, percent)); this.add(key, percentOfThisItem); } return this; diff --git a/src/lib/invention/MaterialLootTable.ts b/src/lib/invention/MaterialLootTable.ts index 05b7e9ccee3..018105222f9 100644 --- a/src/lib/invention/MaterialLootTable.ts +++ b/src/lib/invention/MaterialLootTable.ts @@ -1,6 +1,6 @@ import { randInt } from 'e'; -import { IMaterialBank, MaterialType } from '.'; +import type { IMaterialBank, MaterialType } from '.'; interface MaterialTableItem { material: MaterialType; diff --git a/src/lib/invention/disassemble.ts b/src/lib/invention/disassemble.ts index a1eb0fe1cee..d1c5c63827c 100644 --- a/src/lib/invention/disassemble.ts +++ b/src/lib/invention/disassemble.ts @@ -1,27 +1,27 @@ -import { calcWhatPercent, clamp, percentChance, reduceNumByPercent, Time, uniqueArr } from 'e'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; +import type { CommandResponse } from '@oldschoolgg/toolkit'; +import { Time, calcWhatPercent, clamp, percentChance, reduceNumByPercent, uniqueArr } from 'e'; import { Bank } from 'oldschooljs'; -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; +import { + type DisassembleFlag, + type DisassemblyItem, + type DisassemblySourceGroup, + type MaterialType, + allItemsThatCanBeDisassembledIDs +} from '.'; import Skillcapes from '../skilling/skillcapes'; import { SkillsEnum } from '../skilling/types'; -import { DisassembleTaskOptions } from '../types/minions'; +import type { DisassembleTaskOptions } from '../types/minions'; import { calcPerHour, formatDuration, makeTable, toKMB } from '../util'; import addSubTaskToActivityTask from '../util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../util/calcMaxTripLength'; import { getItem } from '../util/getOSItem'; import { minionIsBusy } from '../util/minionIsBusy'; -import { - allItemsThatCanBeDisassembledIDs, - DisassembleFlag, - DisassemblyItem, - DisassemblySourceGroup, - MaterialType -} from '.'; -import { DisassemblyGroupMap, DisassemblySourceGroups } from './groups'; -import { inventionBoosts, InventionID, inventionItemBoost, materialBoosts } from './inventions'; import { MaterialBank } from './MaterialBank'; import MaterialLootTable from './MaterialLootTable'; +import { DisassemblyGroupMap, DisassemblySourceGroups } from './groups'; +import { InventionID, inventionBoosts, inventionItemBoost, materialBoosts } from './inventions'; const MASTER_CAPE_JUNK_REDUCTION = 5; @@ -73,7 +73,7 @@ function doesHaveMasterCapeBoost( ? { has: true, cape: skillCape.masterCape.name - } + } : { has: false }; } return { has: false }; @@ -92,7 +92,7 @@ export function calcJunkChance(lvl: number, hasMasterCape: boolean) { return clamp(base, 2, 100); } export function calculateDisXP(group: DisassemblySourceGroup, inventionLevel: number, quantity: number, lvl: number) { - let baseXPPerItem = 2 + floor(lvl / 11) + floor(inventionLevel / 5) + (lvl - lvl / 1.2) * (lvl / 7.5); + const baseXPPerItem = 2 + floor(lvl / 11) + floor(inventionLevel / 5) + (lvl - lvl / 1.2) * (lvl / 7.5); let xp = Math.ceil(quantity * baseXPPerItem); if (group.xpReductionDivisor) xp /= group.xpReductionDivisor; return { @@ -144,7 +144,7 @@ const flagToMaterialMap: [DisassembleFlag, MaterialType][] = [ function flagEffectsInDisassembly(item: DisassemblyItem, loot: MaterialBank) { const tertiaryChance = item.lvl; - let success = percentChance(tertiaryChance); + const success = percentChance(tertiaryChance); if (!success) return; for (const [flag, mat] of flagToMaterialMap) { if (item.flags?.has(flag)) { @@ -182,7 +182,7 @@ export async function handleDisassembly({ let timePer = Time.Second * 0.33; const maxTripLength = calcMaxTripLength(user, 'Disassembling'); - let messages: string[] = []; + const messages: string[] = []; if (bank.has('Dwarven toolkit')) { const boostedActionTime = reduceNumByPercent(timePer, inventionBoosts.dwarvenToolkit.disassembleBoostPercent); const boostRes = await inventionItemBoost({ @@ -222,7 +222,7 @@ export async function handleDisassembly({ const duration = realQuantity * timePer; const masterCapeBoost = doesHaveMasterCapeBoost(user, _group.group); - if (masterCapeBoost && masterCapeBoost.has) { + if (masterCapeBoost?.has) { messages.push(`${MASTER_CAPE_JUNK_REDUCTION}% junk chance reduction for ${masterCapeBoost.cape}`); } const junkChance = calcJunkChance(data.lvl, masterCapeBoost ? masterCapeBoost.has : false); @@ -265,19 +265,19 @@ async function materialAnalysis(user: MUser, bank: Bank) { let materialAnalysis = ''; let totalXP = 0; let totalDur = 0; - let totalCost = new Bank(); - let totalMats = new MaterialBank(); + const totalCost = new Bank(); + const totalMats = new MaterialBank(); - let start = Date.now(); + const start = Date.now(); for (const [item, qty] of bank.items()) { if (!allItemsThatCanBeDisassembledIDs.has(item.id)) continue; let thisXP = 0; let thisDur = 0; - let thisCost = new Bank(); - let thisMats = new MaterialBank(); + const thisCost = new Bank(); + const thisMats = new MaterialBank(); while (bank.amount(item.id) > 0) { - let res = await handleDisassembly({ user, inputQuantity: qty, item }); + const res = await handleDisassembly({ user, inputQuantity: qty, item }); if (res.error === null) { thisXP += res.xp; thisDur += res.duration; @@ -306,7 +306,7 @@ Total\t${toKMB(totalXP)}\t${formatDuration(totalDur)}\t${totalMats}\t${totalCost export async function bankDisassembleAnalysis({ bank, user }: { bank: Bank; user: MUser }): CommandResponse { let totalXP = 0; - let totalMaterials = new MaterialBank(); + const totalMaterials = new MaterialBank(); const results: ({ item: Item } & DisassemblyResult)[] = []; const cantBeDisassembled = []; for (const [item, qty] of bank.items()) { @@ -404,8 +404,8 @@ export function calcWholeDisXP(user: MUser, item: Item, quantity: number) { const duplicateItems = []; const foundItems: number[] = []; -for (let group of DisassemblySourceGroups) { - for (let itm of group.items) { +for (const group of DisassemblySourceGroups) { + for (const itm of group.items) { const items: Item[] = Array.isArray(itm.item) ? itm.item : [itm.item]; if (items.some(i => foundItems.includes(i.id))) { duplicateItems.push(items.map(i => ({ name: i.name, group: i.name }))); diff --git a/src/lib/invention/groups/Ashes.ts b/src/lib/invention/groups/Ashes.ts index 0531e141507..37e6a9ef7cc 100644 --- a/src/lib/invention/groups/Ashes.ts +++ b/src/lib/invention/groups/Ashes.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Axes.ts b/src/lib/invention/groups/Axes.ts index dfa921cd86e..896aa8ac0e8 100644 --- a/src/lib/invention/groups/Axes.ts +++ b/src/lib/invention/groups/Axes.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Barrows.ts b/src/lib/invention/groups/Barrows.ts index 3333bacf4df..59edd4446b3 100644 --- a/src/lib/invention/groups/Barrows.ts +++ b/src/lib/invention/groups/Barrows.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/BluntWeapons.ts b/src/lib/invention/groups/BluntWeapons.ts index 70ca17e19f4..45246e65e76 100644 --- a/src/lib/invention/groups/BluntWeapons.ts +++ b/src/lib/invention/groups/BluntWeapons.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/BoltTips.ts b/src/lib/invention/groups/BoltTips.ts index 82cf3f39528..d650e9b1b84 100644 --- a/src/lib/invention/groups/BoltTips.ts +++ b/src/lib/invention/groups/BoltTips.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Bones.ts b/src/lib/invention/groups/Bones.ts index 2d3523294b9..147acd105a3 100644 --- a/src/lib/invention/groups/Bones.ts +++ b/src/lib/invention/groups/Bones.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Bows.ts b/src/lib/invention/groups/Bows.ts index 2f7380d4d11..48b0be3379f 100644 --- a/src/lib/invention/groups/Bows.ts +++ b/src/lib/invention/groups/Bows.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Claws.ts b/src/lib/invention/groups/Claws.ts index 1cbffad339c..cb5905e956f 100644 --- a/src/lib/invention/groups/Claws.ts +++ b/src/lib/invention/groups/Claws.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Clothing.ts b/src/lib/invention/groups/Clothing.ts index 361a2750070..eac4530b9ab 100644 --- a/src/lib/invention/groups/Clothing.ts +++ b/src/lib/invention/groups/Clothing.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Dagger.ts b/src/lib/invention/groups/Dagger.ts index 42c9bb316f1..25a8d090af5 100644 --- a/src/lib/invention/groups/Dagger.ts +++ b/src/lib/invention/groups/Dagger.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Defender.ts b/src/lib/invention/groups/Defender.ts index df3a804e5cb..a7f18069589 100644 --- a/src/lib/invention/groups/Defender.ts +++ b/src/lib/invention/groups/Defender.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Drygores.ts b/src/lib/invention/groups/Drygores.ts index 87a7dba43f1..e9fdfd56dfb 100644 --- a/src/lib/invention/groups/Drygores.ts +++ b/src/lib/invention/groups/Drygores.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; export const Drygores: DisassemblySourceGroup = { name: 'Drygores', diff --git a/src/lib/invention/groups/Dwarven.ts b/src/lib/invention/groups/Dwarven.ts index d4d1fb258ce..b153adf839e 100644 --- a/src/lib/invention/groups/Dwarven.ts +++ b/src/lib/invention/groups/Dwarven.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/EnsouledHeads.ts b/src/lib/invention/groups/EnsouledHeads.ts index 579d0db2436..84315758477 100644 --- a/src/lib/invention/groups/EnsouledHeads.ts +++ b/src/lib/invention/groups/EnsouledHeads.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; export const EnsouledHeads: DisassemblySourceGroup = { name: 'Ensouled Heads', diff --git a/src/lib/invention/groups/Explosive.ts b/src/lib/invention/groups/Explosive.ts index 275440a12ac..1f760c16ee0 100644 --- a/src/lib/invention/groups/Explosive.ts +++ b/src/lib/invention/groups/Explosive.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Food.ts b/src/lib/invention/groups/Food.ts index 13fd14f3bb4..447c0d96275 100644 --- a/src/lib/invention/groups/Food.ts +++ b/src/lib/invention/groups/Food.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Gems.ts b/src/lib/invention/groups/Gems.ts index 060e1b6ea9b..1403948790e 100644 --- a/src/lib/invention/groups/Gems.ts +++ b/src/lib/invention/groups/Gems.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Gilded.ts b/src/lib/invention/groups/Gilded.ts index 42619bf39d3..d83a0025427 100644 --- a/src/lib/invention/groups/Gilded.ts +++ b/src/lib/invention/groups/Gilded.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Glass.ts b/src/lib/invention/groups/Glass.ts index 683aff6d335..a015cb10af1 100644 --- a/src/lib/invention/groups/Glass.ts +++ b/src/lib/invention/groups/Glass.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Halberd.ts b/src/lib/invention/groups/Halberd.ts index a87ec2cb854..4dd01164711 100644 --- a/src/lib/invention/groups/Halberd.ts +++ b/src/lib/invention/groups/Halberd.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Hasta.ts b/src/lib/invention/groups/Hasta.ts index 9f564c052fe..6225bd47148 100644 --- a/src/lib/invention/groups/Hasta.ts +++ b/src/lib/invention/groups/Hasta.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Herb.ts b/src/lib/invention/groups/Herb.ts index 5d137fe7e66..18fec6dec9f 100644 --- a/src/lib/invention/groups/Herb.ts +++ b/src/lib/invention/groups/Herb.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/HybridArmour.ts b/src/lib/invention/groups/HybridArmour.ts index 66513e806bb..a6a43328292 100644 --- a/src/lib/invention/groups/HybridArmour.ts +++ b/src/lib/invention/groups/HybridArmour.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Jewellery.ts b/src/lib/invention/groups/Jewellery.ts index 3786b20448b..40438da06b0 100644 --- a/src/lib/invention/groups/Jewellery.ts +++ b/src/lib/invention/groups/Jewellery.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/LeatherHides.ts b/src/lib/invention/groups/LeatherHides.ts index 1b72c2bdb19..116b4795ed7 100644 --- a/src/lib/invention/groups/LeatherHides.ts +++ b/src/lib/invention/groups/LeatherHides.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Logs.ts b/src/lib/invention/groups/Logs.ts index 78b08cb268f..60bb4505e3c 100644 --- a/src/lib/invention/groups/Logs.ts +++ b/src/lib/invention/groups/Logs.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Longsword.ts b/src/lib/invention/groups/Longsword.ts index e6982e9bf44..64d4cad4488 100644 --- a/src/lib/invention/groups/Longsword.ts +++ b/src/lib/invention/groups/Longsword.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Mace.ts b/src/lib/invention/groups/Mace.ts index 4b66f2a3c6b..a4c64ce4f12 100644 --- a/src/lib/invention/groups/Mace.ts +++ b/src/lib/invention/groups/Mace.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Magic.ts b/src/lib/invention/groups/Magic.ts index a70e25bd821..abca3dfeb19 100644 --- a/src/lib/invention/groups/Magic.ts +++ b/src/lib/invention/groups/Magic.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/MagicArmour.ts b/src/lib/invention/groups/MagicArmour.ts index 54d7a374aaa..892276d5fde 100644 --- a/src/lib/invention/groups/MagicArmour.ts +++ b/src/lib/invention/groups/MagicArmour.ts @@ -1,6 +1,6 @@ +import type { DisassemblySourceGroup } from '..'; import { brokenVirtusOutfit, virtusOutfit } from '../../data/CollectionsExport'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/MeleeArmour.ts b/src/lib/invention/groups/MeleeArmour.ts index 4d6daebfdc7..68f2e3a9b33 100644 --- a/src/lib/invention/groups/MeleeArmour.ts +++ b/src/lib/invention/groups/MeleeArmour.ts @@ -1,6 +1,6 @@ +import type { DisassemblySourceGroup } from '..'; import { brokenTorvaOutfit, torvaOutfit } from '../../data/CollectionsExport'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Metals.ts b/src/lib/invention/groups/Metals.ts index 88d9f2450ee..990620ba6be 100644 --- a/src/lib/invention/groups/Metals.ts +++ b/src/lib/invention/groups/Metals.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/MysteryBoxes.ts b/src/lib/invention/groups/MysteryBoxes.ts index 45ee467b212..291f22d6eb7 100644 --- a/src/lib/invention/groups/MysteryBoxes.ts +++ b/src/lib/invention/groups/MysteryBoxes.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Ores.ts b/src/lib/invention/groups/Ores.ts index 103691b9f62..cc96553a67a 100644 --- a/src/lib/invention/groups/Ores.ts +++ b/src/lib/invention/groups/Ores.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Organic.ts b/src/lib/invention/groups/Organic.ts index d3772ab27fb..eeed4de9d9d 100644 --- a/src/lib/invention/groups/Organic.ts +++ b/src/lib/invention/groups/Organic.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Pious.ts b/src/lib/invention/groups/Pious.ts index d7751c9162e..a27f2a25584 100644 --- a/src/lib/invention/groups/Pious.ts +++ b/src/lib/invention/groups/Pious.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; export const Pious: DisassemblySourceGroup = { name: 'Pious', diff --git a/src/lib/invention/groups/Planks.ts b/src/lib/invention/groups/Planks.ts index 905d1edfb9c..76a74c4b309 100644 --- a/src/lib/invention/groups/Planks.ts +++ b/src/lib/invention/groups/Planks.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Potion.ts b/src/lib/invention/groups/Potion.ts index 73fbabd76c6..3231a27c432 100644 --- a/src/lib/invention/groups/Potion.ts +++ b/src/lib/invention/groups/Potion.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Projectiles.ts b/src/lib/invention/groups/Projectiles.ts index f8a42d887ab..97969343fb0 100644 --- a/src/lib/invention/groups/Projectiles.ts +++ b/src/lib/invention/groups/Projectiles.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/RangedArmour.ts b/src/lib/invention/groups/RangedArmour.ts index 9cd4baae10a..ba6ad270a97 100644 --- a/src/lib/invention/groups/RangedArmour.ts +++ b/src/lib/invention/groups/RangedArmour.ts @@ -1,6 +1,6 @@ +import type { DisassemblySourceGroup } from '..'; import { brokenPernixOutfit, pernixOutfit } from '../../data/CollectionsExport'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/RawFood.ts b/src/lib/invention/groups/RawFood.ts index d29bf2bb381..db333607401 100644 --- a/src/lib/invention/groups/RawFood.ts +++ b/src/lib/invention/groups/RawFood.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Runes.ts b/src/lib/invention/groups/Runes.ts index dccd9848c62..efe6ae79970 100644 --- a/src/lib/invention/groups/Runes.ts +++ b/src/lib/invention/groups/Runes.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Scimitar.ts b/src/lib/invention/groups/Scimitar.ts index 848fbc41978..c5e8364e12e 100644 --- a/src/lib/invention/groups/Scimitar.ts +++ b/src/lib/invention/groups/Scimitar.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Shield.ts b/src/lib/invention/groups/Shield.ts index 2b35a06f5ac..d1830bd3d32 100644 --- a/src/lib/invention/groups/Shield.ts +++ b/src/lib/invention/groups/Shield.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Spear.ts b/src/lib/invention/groups/Spear.ts index 9ff26351632..f25976ba98e 100644 --- a/src/lib/invention/groups/Spear.ts +++ b/src/lib/invention/groups/Spear.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Staff.ts b/src/lib/invention/groups/Staff.ts index f51b4cf2e67..3d8915d0bca 100644 --- a/src/lib/invention/groups/Staff.ts +++ b/src/lib/invention/groups/Staff.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Sword.ts b/src/lib/invention/groups/Sword.ts index 186abd1df6a..0d3f01ed516 100644 --- a/src/lib/invention/groups/Sword.ts +++ b/src/lib/invention/groups/Sword.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/Talisman.ts b/src/lib/invention/groups/Talisman.ts index bee20ca5603..1533ac26c37 100644 --- a/src/lib/invention/groups/Talisman.ts +++ b/src/lib/invention/groups/Talisman.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/ThirdAge.ts b/src/lib/invention/groups/ThirdAge.ts index 59bbaffdda1..12c026db6a0 100644 --- a/src/lib/invention/groups/ThirdAge.ts +++ b/src/lib/invention/groups/ThirdAge.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/TreasureTrails.ts b/src/lib/invention/groups/TreasureTrails.ts index bfc43ee774d..1ddd886d28c 100644 --- a/src/lib/invention/groups/TreasureTrails.ts +++ b/src/lib/invention/groups/TreasureTrails.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/UncutGems.ts b/src/lib/invention/groups/UncutGems.ts index fee9ff4c74c..3049064ac0c 100644 --- a/src/lib/invention/groups/UncutGems.ts +++ b/src/lib/invention/groups/UncutGems.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/UnstrungBows.ts b/src/lib/invention/groups/UnstrungBows.ts index 30519a6ffb2..7d272ee1186 100644 --- a/src/lib/invention/groups/UnstrungBows.ts +++ b/src/lib/invention/groups/UnstrungBows.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; const i = getOSItem; diff --git a/src/lib/invention/groups/index.ts b/src/lib/invention/groups/index.ts index 5105b47dbf4..a1e0a9ddcca 100644 --- a/src/lib/invention/groups/index.ts +++ b/src/lib/invention/groups/index.ts @@ -1,5 +1,5 @@ +import type { DisassemblySourceGroup } from '..'; import getOSItem from '../../util/getOSItem'; -import { DisassemblySourceGroup } from '..'; import { Ashes } from './Ashes'; import { Axes } from './Axes'; import { Barrows } from './Barrows'; diff --git a/src/lib/invention/index.ts b/src/lib/invention/index.ts index e01abdd5917..ba670021f11 100644 --- a/src/lib/invention/index.ts +++ b/src/lib/invention/index.ts @@ -1,4 +1,4 @@ -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; import { DisassemblySourceGroups } from './groups'; diff --git a/src/lib/invention/inventions.ts b/src/lib/invention/inventions.ts index 40f4d355fda..b16b634222f 100644 --- a/src/lib/invention/inventions.ts +++ b/src/lib/invention/inventions.ts @@ -1,19 +1,19 @@ import { userMention } from '@discordjs/builders'; -import { Prisma } from '@prisma/client'; -import { clamp, reduceNumByPercent, Time } from 'e'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; +import type { CommandResponse } from '@oldschoolgg/toolkit'; +import type { Prisma } from '@prisma/client'; +import { Time, clamp, reduceNumByPercent } from 'e'; import { Bank } from 'oldschooljs'; -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; -import { ClueTier, ClueTiers } from '../clues/clueTiers'; -import { ItemBank } from '../types'; +import type { IMaterialBank, MaterialType } from '.'; +import { type ClueTier, ClueTiers } from '../clues/clueTiers'; +import type { ItemBank } from '../types'; import { formatDuration, stringMatches, toKMB } from '../util'; import { mahojiClientSettingsFetch, mahojiClientSettingsUpdate } from '../util/clientSettings'; import getOSItem from '../util/getOSItem'; import { logError } from '../util/logError'; import { minionIsBusy } from '../util/minionIsBusy'; import resolveItems from '../util/resolveItems'; -import { IMaterialBank, MaterialType } from '.'; import { MaterialBank } from './MaterialBank'; const InventionFlags = ['equipped', 'bank'] as const; @@ -118,15 +118,15 @@ export const inventionBoosts = { }, clueUpgrader: { chance: (clue: ClueTier) => { - let index = ClueTiers.indexOf(clue); - let chanceOfUpgradePercent = 45 - (index + 1) * 5; + const index = ClueTiers.indexOf(clue); + const chanceOfUpgradePercent = 45 - (index + 1) * 5; return chanceOfUpgradePercent; }, pickPocketChance: (clue: ClueTier) => { return Math.ceil(inventionBoosts.clueUpgrader.chance(clue) / 5); }, durationCalc: (clue: ClueTier) => { - let index = ClueTiers.indexOf(clue); + const index = ClueTiers.indexOf(clue); return (index + 1) * (Time.Minute * 3); } }, @@ -310,8 +310,8 @@ export const Inventions: readonly Invention[] = [ extraDescription: () => { let str = ''; for (const clue of ClueTiers.slice(0, 5)) { - let index = ClueTiers.indexOf(clue); - let next = ClueTiers[index + 1]; + const index = ClueTiers.indexOf(clue); + const next = ClueTiers[index + 1]; str += `**${clue.name}:** ${inventionBoosts.clueUpgrader.chance(clue)}% chance to upgrade into ${ next.name }, costs ${formatDuration(inventionBoosts.clueUpgrader.durationCalc(clue))}\n`; @@ -526,8 +526,8 @@ export const materialBoosts: Map { - let vals = Object.values(userStats.scattered_ashes_bank as ItemBank); + const vals = Object.values(userStats.scattered_ashes_bank as ItemBank); return vals.length === ashes.length && vals.every(i => i >= 1000); } }, @@ -682,7 +682,7 @@ export const eliteTasks: Task[] = [ id: 3091, name: 'Offer 100 of each bird egg', has: async ({ userStats }) => { - let vals = Object.values(userStats.bird_eggs_offered_bank as ItemBank); + const vals = Object.values(userStats.bird_eggs_offered_bank as ItemBank); return vals.length === eggs.length && vals.every(i => Number(i) >= 100); } }, @@ -704,7 +704,7 @@ export const eliteTasks: Task[] = [ id: 3094, name: 'Catch 30 of every impling passively (excluding lucky implings)', has: async ({ userStats }) => { - let loot = new Bank(userStats.passive_implings_bank as ItemBank); + const loot = new Bank(userStats.passive_implings_bank as ItemBank); for (const implingId of Object.keys(implings)) { if (Number(implingId) !== Openables.LuckyImpling.id && loot.amount(Number(implingId)) < 30) { return false; diff --git a/src/lib/leagues/hardTasks.ts b/src/lib/leagues/hardTasks.ts index d750c1f3669..b05c1ca9151 100644 --- a/src/lib/leagues/hardTasks.ts +++ b/src/lib/leagues/hardTasks.ts @@ -3,9 +3,6 @@ import { Bank, Monsters, Openables } from 'oldschooljs'; import { eggs } from '../../mahoji/commands/offer'; import { divinationEnergies } from '../bso/divination'; -import { circusBuyables } from '../data/buyables/circusBuyables'; -import { fistOfGuthixBuyables } from '../data/buyables/fistOfGuthixBuyables'; -import { stealingCreationBuyables } from '../data/buyables/stealingCreationBuyables'; import { allGildedItems, brokenPernixOutfit, @@ -19,6 +16,9 @@ import { torvaOutfit, virtusOutfit } from '../data/CollectionsExport'; +import { circusBuyables } from '../data/buyables/circusBuyables'; +import { fistOfGuthixBuyables } from '../data/buyables/fistOfGuthixBuyables'; +import { stealingCreationBuyables } from '../data/buyables/stealingCreationBuyables'; import { PartyhatTable } from '../data/holidayItems'; import { ArdougneDiary, @@ -30,10 +30,10 @@ import { KourendKebosDiary, LumbridgeDraynorDiary, MorytaniaDiary, - userhasDiaryTier, VarrockDiary, WesternProv, - WildernessDiary + WildernessDiary, + userhasDiaryTier } from '../diaries'; import { dyedItems } from '../dyedItems'; import { implings } from '../implings'; @@ -45,10 +45,10 @@ import { allThirdAgeItems } from '../simulation/sharedTables'; import Darts from '../skilling/skills/fletching/fletchables/darts'; import Javelins from '../skilling/skills/fletching/fletchables/javelins'; import { ashes } from '../skilling/skills/prayer'; -import { ItemBank } from '../types'; +import type { ItemBank } from '../types'; import { calcCombatLevel, calcTotalLevel } from '../util'; import resolveItems from '../util/resolveItems'; -import { leaguesHasCatches, leaguesHasKC, Task } from './leaguesUtils'; +import { type Task, leaguesHasCatches, leaguesHasKC } from './leaguesUtils'; import { calculateChargedItems, calculateTiarasMade } from './stats'; export const hardTasks: Task[] = [ @@ -1112,7 +1112,7 @@ export const hardTasks: Task[] = [ id: 2148, name: 'Scatter 500 of every ashes', has: async ({ userStats }) => { - let vals = Object.values(userStats.scattered_ashes_bank as ItemBank); + const vals = Object.values(userStats.scattered_ashes_bank as ItemBank); return vals.length === ashes.length && vals.every(i => i >= 500); } }, @@ -1127,7 +1127,7 @@ export const hardTasks: Task[] = [ id: 2150, name: 'Offer 25 of each bird egg', has: async ({ userStats }) => { - let vals = Object.values(userStats.bird_eggs_offered_bank as ItemBank); + const vals = Object.values(userStats.bird_eggs_offered_bank as ItemBank); return vals.length === eggs.length && vals.every(i => Number(i) >= 25); } }, @@ -1149,7 +1149,7 @@ export const hardTasks: Task[] = [ id: 2153, name: 'Catch 20 of every impling passively (excluding Lucky implings)', has: async ({ userStats }) => { - let loot = new Bank(userStats.passive_implings_bank as ItemBank); + const loot = new Bank(userStats.passive_implings_bank as ItemBank); for (const implingId of Object.keys(implings)) { if (Number(implingId) !== Openables.LuckyImpling.id && loot.amount(Number(implingId)) < 20) { return false; diff --git a/src/lib/leagues/leagues.ts b/src/lib/leagues/leagues.ts index bd024de4074..8564343c293 100644 --- a/src/lib/leagues/leagues.ts +++ b/src/lib/leagues/leagues.ts @@ -1,6 +1,6 @@ -import { activity_type_enum, User } from '@prisma/client'; +import type { CommandResponse } from '@oldschoolgg/toolkit'; +import { type User, activity_type_enum } from '@prisma/client'; import { calcWhatPercent, sumArr } from 'e'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; import { Bank } from 'oldschooljs'; import { getPOH } from '../../mahoji/lib/abstracted_commands/pohCommand'; @@ -19,16 +19,16 @@ import { import { BitField } from '../constants'; import { calcCLDetails } from '../data/Collections'; import { getMinigameEntity } from '../settings/minigames'; -import { prisma } from '../settings/prisma'; + import smithables from '../skilling/skills/smithing/smithables'; import { getSlayerTaskStats } from '../slayer/slayerUtil'; import { getAllUserTames } from '../tames'; -import { ItemBank } from '../types'; +import type { ItemBank } from '../types'; import { getItem } from '../util/getOSItem'; import { easyTasks } from './easyTasks'; import { eliteTasks } from './eliteTasks'; import { hardTasks } from './hardTasks'; -import { betterHerbloreStats, HasFunctionArgs, Task } from './leaguesUtils'; +import { type HasFunctionArgs, type Task, betterHerbloreStats } from './leaguesUtils'; import { masterTasks } from './masterTasks'; import { mediumTasks } from './mediumTasks'; import { @@ -55,8 +55,8 @@ export function generateLeaguesTasksTextFile(finishedTasksIDs: number[], exclude let totalPoints = 0; let str = ''; for (const { name, tasks, points } of leagueTasks) { - let realTasks = excludeFinished ? tasks.filter(i => !finishedTasksIDs.includes(i.id)) : tasks; - let ptsThisGroup = realTasks.length * points; + const realTasks = excludeFinished ? tasks.filter(i => !finishedTasksIDs.includes(i.id)) : tasks; + const ptsThisGroup = realTasks.length * points; str += `--------- ${name} (${realTasks.length} tasks - ${ptsThisGroup.toLocaleString()} points) -----------\n`; str += realTasks .map(i => i.name) @@ -71,14 +71,13 @@ export function generateLeaguesTasksTextFile(finishedTasksIDs: number[], exclude } async function getActivityCounts(user: User) { - const result: { type: activity_type_enum; count: number }[] = - await prisma.$queryRawUnsafe(`SELECT type, COUNT(type)::int + const result: { type: activity_type_enum; count: number }[] = await prisma.$queryRawUnsafe(`SELECT type, COUNT(type)::int FROM activity WHERE user_id = ${user.id} GROUP BY type;`); const parsed = result.map(i => ({ type: i.type, count: Number(i.count) })); // @ts-ignore trust me - let rec: Record = {}; + const rec: Record = {}; for (const a of Object.values(activity_type_enum)) { rec[a] = 0; } @@ -89,8 +88,7 @@ GROUP BY type;`); } export async function personalHerbloreStatsWithoutZahur(user: User) { - const result: { id: number; qty: number }[] = - await prisma.$queryRawUnsafe(`SELECT (data->>'mixableID')::int AS id, SUM((data->>'quantity')::int)::int AS qty + const result: { id: number; qty: number }[] = await prisma.$queryRawUnsafe(`SELECT (data->>'mixableID')::int AS id, SUM((data->>'quantity')::int)::int AS qty FROM activity WHERE type = 'Herblore' AND user_id = '${user.id}'::bigint @@ -108,7 +106,7 @@ GROUP BY data->>'mixableID';`); } function calcSuppliesUsedForSmithing(itemsSmithed: Bank) { - let input = new Bank(); + const input = new Bank(); for (const [item, qty] of itemsSmithed.items()) { const smithable = smithables.find(i => i.id === item.id); if (!smithable) continue; @@ -333,7 +331,7 @@ export async function leaguesClaimCommand(userID: string, finishedTaskIDs: numbe const newTotal = newUser.leagues_points_total; - let response: Awaited = { + const response: Awaited = { content: `You claimed ${newlyFinishedTasks.length} tasks, and received ${pointsToAward} points. You now have a balance of ${newUser.leagues_points_balance_osb} OSB points and ${newUser.leagues_points_balance_bso} BSO points, and ${newTotal} total points. You have completed a total of ${newUser.leagues_completed_tasks_ids.length} tasks.` }; diff --git a/src/lib/leagues/leaguesUtils.ts b/src/lib/leagues/leaguesUtils.ts index 5db0c0c39fa..fe36b35157b 100644 --- a/src/lib/leagues/leaguesUtils.ts +++ b/src/lib/leagues/leaguesUtils.ts @@ -1,17 +1,17 @@ -import { activity_type_enum, Minigame, PlayerOwnedHouse, Tame, User, UserStats } from '@prisma/client'; +import type { Minigame, PlayerOwnedHouse, Tame, User, UserStats, activity_type_enum } from '@prisma/client'; import { Bank } from 'oldschooljs'; -import Monster from 'oldschooljs/dist/structures/Monster'; +import type Monster from 'oldschooljs/dist/structures/Monster'; -import { ParsedUnit } from '../../mahoji/lib/abstracted_commands/stashUnitsCommand'; -import { personalSpellCastStats } from '../../mahoji/lib/abstracted_commands/statCommand'; -import { UserFullGearSetup } from '../gear'; -import { CustomMonster } from '../minions/data/killableMonsters/custom/customMonsters'; +import type { ParsedUnit } from '../../mahoji/lib/abstracted_commands/stashUnitsCommand'; +import type { personalSpellCastStats } from '../../mahoji/lib/abstracted_commands/statCommand'; +import type { UserFullGearSetup } from '../gear'; +import type { CustomMonster } from '../minions/data/killableMonsters/custom/customMonsters'; import Grimy from '../skilling/skills/herblore/mixables/grimy'; import Potions from '../skilling/skills/herblore/mixables/potions'; import unfinishedPotions from '../skilling/skills/herblore/mixables/unfinishedPotions'; import creatures from '../skilling/skills/hunter/creatures'; -import { getSlayerTaskStats } from '../slayer/slayerUtil'; -import { ItemBank, Skills } from '../types'; +import type { getSlayerTaskStats } from '../slayer/slayerUtil'; +import type { ItemBank, Skills } from '../types'; import { stringMatches } from '../util'; export interface HasFunctionArgs { @@ -72,7 +72,7 @@ export function leaguesHasCatches(args: HasFunctionArgs, name: string, amount = } export function leaguesSlayerTaskForMonster(args: HasFunctionArgs, mon: Monster | CustomMonster, amount: number) { - let data = args.slayerStats.find(i => i.monsterID === mon.id); + const data = args.slayerStats.find(i => i.monsterID === mon.id); return data !== undefined && data.total_tasks >= amount; } diff --git a/src/lib/leagues/masterTasks.ts b/src/lib/leagues/masterTasks.ts index 2578fca68fd..953c7a4fc88 100644 --- a/src/lib/leagues/masterTasks.ts +++ b/src/lib/leagues/masterTasks.ts @@ -23,11 +23,11 @@ import { dungBuyables } from '../skilling/skills/dung/dungData'; import { ashes } from '../skilling/skills/prayer'; import Dwarven from '../skilling/skills/smithing/smithables/dwarven'; import { slayerUnlockableRewards } from '../slayer/slayerUnlocks'; -import { ItemBank } from '../types'; +import type { ItemBank } from '../types'; import { calcTotalLevel } from '../util'; import resolveItems from '../util/resolveItems'; import { getTameSpecies } from '../util/tameUtil'; -import { Task } from './leaguesUtils'; +import type { Task } from './leaguesUtils'; export const masterTasks: Task[] = [ { @@ -360,7 +360,7 @@ export const masterTasks: Task[] = [ id: 4045, name: 'Create every Dwarven item from scratch', has: async ({ cl, skillsLevels }) => { - let totalInput = new Bank(); + const totalInput = new Bank(); for (const item of Dwarven) { if (skillsLevels.smithing < item.level) return false; if (!cl.has(item.id)) return false; @@ -1089,7 +1089,7 @@ export const masterTasks: Task[] = [ id: 4150, name: 'Scatter 5000 of every ashes', has: async ({ userStats }) => { - let vals = Object.values(userStats.scattered_ashes_bank as ItemBank); + const vals = Object.values(userStats.scattered_ashes_bank as ItemBank); return vals.length === ashes.length && vals.every(i => i >= 5000); } }, @@ -1097,7 +1097,7 @@ export const masterTasks: Task[] = [ id: 4151, name: 'Catch 50 mystery implings, and 100 of every other imping passively (excluding Lucky implings)', has: async ({ userStats }) => { - let loot = new Bank(userStats.passive_implings_bank as ItemBank); + const loot = new Bank(userStats.passive_implings_bank as ItemBank); const excludedImplings = [LuckyImpling.id, MysteryImpling.id]; for (const implingId of Object.keys(implings)) { if ( diff --git a/src/lib/leagues/mediumTasks.ts b/src/lib/leagues/mediumTasks.ts index 4c50198cb34..22430d5ea98 100644 --- a/src/lib/leagues/mediumTasks.ts +++ b/src/lib/leagues/mediumTasks.ts @@ -23,23 +23,23 @@ import { KourendKebosDiary, LumbridgeDraynorDiary, MorytaniaDiary, - userhasDiaryTier, VarrockDiary, WesternProv, - WildernessDiary + WildernessDiary, + userhasDiaryTier } from '../diaries'; import { implings } from '../implings'; -import { QueenBlackDragon } from '../minions/data/killableMonsters/custom/demiBosses'; import { TormentedDemon } from '../minions/data/killableMonsters/custom/TormentedDemon'; -import { prisma } from '../settings/prisma'; +import { QueenBlackDragon } from '../minions/data/killableMonsters/custom/demiBosses'; + import Darts from '../skilling/skills/fletching/fletchables/darts'; import Javelins from '../skilling/skills/fletching/fletchables/javelins'; import { ashes } from '../skilling/skills/prayer'; -import { ItemBank } from '../types'; +import type { ItemBank } from '../types'; import { calcCombatLevel, calcTotalLevel } from '../util'; import resolveItems from '../util/resolveItems'; import { LampTable } from '../xpLamps'; -import { leaguesHasCatches, leaguesHasKC, leaguesSlayerTaskForMonster, Task } from './leaguesUtils'; +import { type Task, leaguesHasCatches, leaguesHasKC, leaguesSlayerTaskForMonster } from './leaguesUtils'; import { calculateChargedItems, calculateTiarasMade, calculateTotalMahoganyHomesPoints } from './stats'; export const mediumTasks: Task[] = [ @@ -925,7 +925,7 @@ export const mediumTasks: Task[] = [ id: 1128, name: 'Offer 5 of each bird egg', has: async ({ userStats }) => { - let vals = Object.values(userStats.bird_eggs_offered_bank as ItemBank); + const vals = Object.values(userStats.bird_eggs_offered_bank as ItemBank); return vals.length === eggs.length && vals.every(i => Number(i) >= 5); } }, @@ -940,7 +940,7 @@ export const mediumTasks: Task[] = [ id: 1130, name: 'Scatter 100 of every ashes', has: async ({ userStats }) => { - let vals = Object.values(userStats.scattered_ashes_bank as ItemBank); + const vals = Object.values(userStats.scattered_ashes_bank as ItemBank); return vals.length === ashes.length && vals.every(i => i >= 100); } }, diff --git a/src/lib/leagues/stats.ts b/src/lib/leagues/stats.ts index 65296f111c4..f330d644ae7 100644 --- a/src/lib/leagues/stats.ts +++ b/src/lib/leagues/stats.ts @@ -1,12 +1,12 @@ -import { UserStats, XpGainSource } from '@prisma/client'; -import { User as RoboChimpUser } from '@prisma/robochimp'; +import type { UserStats, XpGainSource } from '@prisma/client'; +import type { User as RoboChimpUser } from '@prisma/robochimp'; import { sumArr } from 'e'; import { Bank } from 'oldschooljs'; import { gloriesInventorySize, wealthInventorySize } from '../constants'; -import { prisma } from '../settings/prisma'; + import Darts from '../skilling/skills/fletching/fletchables/darts'; -import { ItemBank } from '../types'; +import type { ItemBank } from '../types'; import { getItem } from '../util/getOSItem'; export function totalLampedXP(userStats: UserStats) { @@ -33,9 +33,7 @@ WHERE COALESCE(cardinality(leagues_completed_tasks_ids), 0) > ${user.leagues_com } export async function calculateAllFletchedItems(user: MUser) { - const result = await prisma.$queryRawUnsafe< - { name: string; total: number }[] - >(`SELECT data->>'fletchableName' AS name, SUM((data->>'quantity')::int) AS total + const result = await prisma.$queryRawUnsafe<{ name: string; total: number }[]>(`SELECT data->>'fletchableName' AS name, SUM((data->>'quantity')::int) AS total FROM activity WHERE type = 'Fletching' AND user_id = '${user.id}'::bigint @@ -71,9 +69,7 @@ export function calculateDartsFletchedFromScratch({ } export async function calculateTiarasMade(user: MUser) { - const tiarasMade = await prisma.$queryRawUnsafe< - { tiara_id: string; total: bigint }[] - >(`SELECT data->>'tiaraID' AS tiara_id, SUM((data->>'tiaraQuantity')::int) AS total + const tiarasMade = await prisma.$queryRawUnsafe<{ tiara_id: string; total: bigint }[]>(`SELECT data->>'tiaraID' AS tiara_id, SUM((data->>'tiaraQuantity')::int) AS total FROM activity WHERE type = 'TiaraRunecraft' AND user_id = '${user.id}'::bigint @@ -118,9 +114,7 @@ AND data->>'points' IS NOT NULL;`); } export async function calculateXPSources(user: MUser) { - const result = await prisma.$queryRawUnsafe< - { source: XpGainSource; total: bigint }[] - >(`SELECT "xp_gains"."source" AS source, SUM(xp) AS total + const result = await prisma.$queryRawUnsafe<{ source: XpGainSource; total: bigint }[]>(`SELECT "xp_gains"."source" AS source, SUM(xp) AS total FROM xp_gains WHERE "xp_gains"."source" IS NOT NULL AND user_id = '${user.id}'::bigint diff --git a/src/lib/lootTrack.ts b/src/lib/lootTrack.ts index 664551c36f9..c35cbffa033 100644 --- a/src/lib/lootTrack.ts +++ b/src/lib/lootTrack.ts @@ -1,9 +1,8 @@ -import { loot_track_type, LootTrack } from '@prisma/client'; +import type { LootTrack, loot_track_type } from '@prisma/client'; import { Time } from 'e'; import { Bank } from 'oldschooljs'; -import { prisma } from './settings/prisma'; -import { ItemBank } from './types'; +import type { ItemBank } from './types'; import { cleanString, formatDuration } from './util'; import { makeBankImage } from './util/makeBankImage'; @@ -50,7 +49,7 @@ async function trackIndividualsLoot({ type: loot_track_type; }) { // Find the existing loot track - let current = await prisma.lootTrack.findFirst({ + const current = await prisma.lootTrack.findFirst({ where: { key, user_id: userID, @@ -81,13 +80,13 @@ async function trackIndividualsLoot({ data.changeType === 'loot' ? { increment: duration - } + } : undefined, total_kc: data.changeType === 'loot' ? { increment: data.kc - } + } : undefined, [data.changeType]: new Bank(current?.[data.changeType] as ItemBank | undefined).add(bankToAdd).bank, user_id: userID diff --git a/src/lib/marketPrices.ts b/src/lib/marketPrices.ts index 7f9d378c5ba..9333c0e5d30 100644 --- a/src/lib/marketPrices.ts +++ b/src/lib/marketPrices.ts @@ -1,9 +1,17 @@ import { notEmpty } from 'e'; -import _ from 'lodash'; -import { Bank } from 'oldschooljs'; -import * as ss from 'simple-statistics'; +import groupBy from 'lodash/groupBy'; +import mapValues from 'lodash/mapValues'; +import max from 'lodash/max'; +import min from 'lodash/min'; +import orderBy from 'lodash/orderBy'; +import pickBy from 'lodash/pickBy'; +import sortBy from 'lodash/sortBy'; +import sumBy from 'lodash/sumBy'; +import uniqBy from 'lodash/uniqBy'; +import type { Bank } from 'oldschooljs'; + +import { mean, medianSorted, quantileSorted } from 'simple-statistics'; -import { prisma } from './settings/prisma'; import { getItem } from './util/getOSItem'; interface MarketPriceData { @@ -47,47 +55,47 @@ export const cacheGEPrices = async () => { }); // Group transactions by item_id - const groupedByItem = _.groupBy(rawTransactions, transaction => transaction.sell_listing.item_id); + const groupedByItem = groupBy(rawTransactions, transaction => transaction.sell_listing.item_id); // Pick items that have at least 5 transactions from 4 different buyers - const filtered = _.pickBy(groupedByItem, group => { - const uniqueBuyers = _.uniqBy(group, transaction => transaction.buy_listing.user_id); + const filtered = pickBy(groupedByItem, group => { + const uniqueBuyers = uniqBy(group, transaction => transaction.buy_listing.user_id); return uniqueBuyers.length >= 4 && group.length >= 5; }); // For each group, calculate necessary metrics. - _.mapValues(filtered, transactions => { + mapValues(filtered, transactions => { const prices = transactions.map(t => Number(t.price_per_item_before_tax)); // Calculate percentiles and IQR - const sortedPrices = _.sortBy(prices); + const sortedPrices = sortBy(prices); - const q1 = ss.quantileSorted(sortedPrices, 0.25); - const q3 = ss.quantileSorted(sortedPrices, 0.75); + const q1 = quantileSorted(sortedPrices, 0.25); + const q3 = quantileSorted(sortedPrices, 0.75); const iqr = q3 - q1; // Filter outliers const filteredPrices = sortedPrices.filter(price => price >= q1 - 1.5 * iqr && price <= q3 + 1.5 * iqr); - const medianSalePrice = ss.medianSorted(sortedPrices); - const avgSalePriceWithoutOutliers = ss.mean(filteredPrices); + const medianSalePrice = medianSorted(sortedPrices); + const avgSalePriceWithoutOutliers = mean(filteredPrices); const guidePrice = Math.round((medianSalePrice + avgSalePriceWithoutOutliers) / 2); // Sort transactions by date (newest to oldest) - const sortedTransactions = _.orderBy(transactions, 'created_at', 'desc'); + const sortedTransactions = orderBy(transactions, 'created_at', 'desc'); const latest100Transactions = sortedTransactions.slice(0, 100); - const averagePriceLast100 = ss.mean(latest100Transactions.map(t => Number(t.price_per_item_before_tax))); + const averagePriceLast100 = mean(latest100Transactions.map(t => Number(t.price_per_item_before_tax))); const totalUniqueTraders = new Set( ...transactions.map(t => [t.buy_listing.user_id, t.sell_listing.user_id].filter(notEmpty)) ); const data = { - totalSold: _.sumBy(transactions, 'quantity_bought'), + totalSold: sumBy(transactions, 'quantity_bought'), transactionCount: transactions.length, - avgSalePrice: ss.mean(sortedPrices), - minSalePrice: _.min(sortedPrices), - maxSalePrice: _.max(sortedPrices), + avgSalePrice: mean(sortedPrices), + minSalePrice: min(sortedPrices), + maxSalePrice: max(sortedPrices), medianSalePrice, avgSalePriceWithoutOutliers, itemID: transactions[0].sell_listing.item_id, diff --git a/src/lib/mastery.ts b/src/lib/mastery.ts index ea65cf1044c..8a9a1c51819 100644 --- a/src/lib/mastery.ts +++ b/src/lib/mastery.ts @@ -1,14 +1,14 @@ import { calcWhatPercent, clamp, round, sumArr } from 'e'; import { calculateAchievementDiaryProgress } from '../mahoji/lib/abstracted_commands/achievementDiaryCommand'; -import { MAX_QP } from '../mahoji/lib/abstracted_commands/questCommand'; import { calculateCompCapeProgress } from './bso/calculateCompCapeProgress'; import { allCombatAchievementTasks } from './combat_achievements/combatAchievements'; import { MAX_XP } from './constants'; import { getTotalCl } from './data/Collections'; import { maxLeaguesPoints } from './leagues/leagues'; +import { MAX_QP } from './minions/data/quests'; import { SkillsEnum } from './skilling/types'; -import { MUserStats } from './structures/MUserStats'; +import type { MUserStats } from './structures/MUserStats'; export async function calculateMastery(user: MUser, stats: MUserStats) { const [totalClItems, clItems] = await getTotalCl(user, 'collection', stats); @@ -46,8 +46,7 @@ export async function calculateMastery(user: MUser, stats: MUserStats) { }, { name: 'Achievement Diaries', - percentage: (await calculateAchievementDiaryProgress(user, stats, await user.fetchMinigameScores())) - .percentComplete + percentage: calculateAchievementDiaryProgress(user, stats, await user.fetchMinigames()).percentComplete }, { name: 'Leagues', diff --git a/src/lib/metrics.ts b/src/lib/metrics.ts index ecca295c99b..9bf21dfa479 100644 --- a/src/lib/metrics.ts +++ b/src/lib/metrics.ts @@ -1,13 +1,7 @@ -import { miniID } from '@oldschoolgg/toolkit'; -import { Prisma } from '@prisma/client'; -import { Time } from 'e'; -import { writeFile } from 'fs/promises'; -import os, { CpuInfo } from 'os'; -import { monitorEventLoopDelay } from 'perf_hooks'; - -import { prisma } from './settings/prisma'; -import { LOG_FILE_NAME, sonicBoom } from './util/logger'; -import { formatDuration, tailFile } from './util/smallUtils'; +import type { CpuInfo } from 'node:os'; +import os from 'node:os'; +import { monitorEventLoopDelay } from 'node:perf_hooks'; +import type { Prisma } from '@prisma/client'; const h = monitorEventLoopDelay(); h.enable(); @@ -36,18 +30,18 @@ function totalCpusTime(cpus: CpuInfo[]) { let cpus = os.cpus(); let startUsage = process.cpuUsage(); function getCPUMetrics() { - let newCpus = os.cpus(); - let newStartUsage = process.cpuUsage(); + const newCpus = os.cpus(); + const newStartUsage = process.cpuUsage(); - let elapCpuTimeMs = totalCpusTime(newCpus) - totalCpusTime(cpus); - let elapUsage = process.cpuUsage(startUsage); + const elapCpuTimeMs = totalCpusTime(newCpus) - totalCpusTime(cpus); + const elapUsage = process.cpuUsage(startUsage); cpus = newCpus; startUsage = newStartUsage; - let cpuUser = elapUsage.user / 1000; // microseconds to milliseconds - let cpuSystem = elapUsage.system / 1000; - let cpuPercent = (100 * (cpuUser + cpuSystem)) / elapCpuTimeMs; + const cpuUser = elapUsage.user / 1000; // microseconds to milliseconds + const cpuSystem = elapUsage.system / 1000; + const cpuPercent = (100 * (cpuUser + cpuSystem)) / elapCpuTimeMs; return { cpuUser, @@ -62,7 +56,7 @@ export async function collectMetrics() { [...prismaMetrics.counters, ...prismaMetrics.gauges, ...prismaMetrics.histograms].map(i => [i.key, i.value]) ); - let metrics: Omit = { + const metrics: Omit = { eventLoopDelayMin: h.min * 1e-6, eventLoopDelayMax: h.max * 1e-6, eventLoopDelayMean: h.mean * 1e-6, @@ -75,19 +69,6 @@ export async function collectMetrics() { prisma_query_active_transactions: transformed.query_active_transactions as number }; h.reset(); - debugLog('Collected metrics', { ...metrics, type: 'COLLECT_METRICS' }); - - const threshold = Time.Second * 5; - if (metrics.eventLoopDelayMax > threshold) { - sonicBoom.flush(); - const last200Lines = await tailFile(LOG_FILE_NAME, 300); - const fileDescription = `This is a log snapshot taken when the event loop delay exceeded the threshold of ${formatDuration( - threshold - )}. - It contains the last 300 lines of the log file: ${LOG_FILE_NAME} \n\n`; - const fileName = `event-loop-lag-dump-${miniID(5)}.txt`; - await writeFile(fileName, fileDescription + last200Lines); - } return metrics; } diff --git a/src/lib/minions/data/bankBackgrounds.ts b/src/lib/minions/data/bankBackgrounds.ts index e9fd5689e2d..2de059d55b2 100644 --- a/src/lib/minions/data/bankBackgrounds.ts +++ b/src/lib/minions/data/bankBackgrounds.ts @@ -2,7 +2,7 @@ import { StoreBitfield } from '@oldschoolgg/toolkit'; import { Bank } from 'oldschooljs'; import { BitField, PerkTier } from '../../constants'; -import { BankBackground } from '../types'; +import type { BankBackground } from '../types'; const backgroundImages: BankBackground[] = [ { @@ -493,6 +493,13 @@ const backgroundImages: BankBackground[] = [ gpCost: 500_000_000, perkTierNeeded: PerkTier.Four }, + { + id: 508, + name: 'Maledict Mortimer', + image: null, + available: true, + perkTierNeeded: PerkTier.Four + }, { id: 1000, name: 'Kiddo CustomBG', diff --git a/src/lib/minions/data/combatConstants.ts b/src/lib/minions/data/combatConstants.ts index 12914b97d12..e5c17dc884b 100644 --- a/src/lib/minions/data/combatConstants.ts +++ b/src/lib/minions/data/combatConstants.ts @@ -1,7 +1,6 @@ import { Bank } from 'oldschooljs'; - import { inventionBoosts } from '../../invention/inventions'; -import { Consumable } from '../types'; +import type { Consumable } from '../types'; // Configure boost percents export const boostCannon = 30; @@ -16,23 +15,23 @@ export const xpPercentToCannonM = 70; // Amount to vary cannon vs regular XP export const xpCannonVaryPercent = 10; -export interface CombatOptionsDesc { +interface CombatOptionsDesc { id: number; name: string; desc: string; aliases: string[]; } export enum CombatOptionsEnum { - NullOption, - AlwaysCannon, - AlwaysIceBurst, - AlwaysIceBarrage + NullOption = 0, + AlwaysCannon = 1, + AlwaysIceBurst = 2, + AlwaysIceBarrage = 3 } export enum SlayerActivityConstants { - None, - IceBarrage, - IceBurst, - Cannon + None = 0, + IceBarrage = 1, + IceBurst = 2, + Cannon = 3 } export const CombatCannonItemBank = new Bank({ 'Cannon barrels': 1, diff --git a/src/lib/minions/data/killableMonsters/bosses/dt.ts b/src/lib/minions/data/killableMonsters/bosses/dt.ts index 2355ed86616..44ac00bf79b 100644 --- a/src/lib/minions/data/killableMonsters/bosses/dt.ts +++ b/src/lib/minions/data/killableMonsters/bosses/dt.ts @@ -1,16 +1,16 @@ -import { roll, Time } from 'e'; +import { Time, roll } from 'e'; import { Bank, Monsters } from 'oldschooljs'; import { VirtusTable } from 'oldschooljs/dist/simulation/subtables/VirtusTable'; -import { QuestID } from '../../../../../mahoji/lib/abstracted_commands/questCommand'; +import { deepResolveItems, resolveItems } from 'oldschooljs/dist/util/util'; import { OSB_VIRTUS_IDS } from '../../../../constants'; import { dukeSucellusCL, theLeviathanCL, theWhispererCL, vardorvisCL } from '../../../../data/CollectionsExport'; import { GearStat } from '../../../../gear/types'; import { SkillsEnum } from '../../../../skilling/types'; -import { removeItemsFromLootTable } from '../../../../util'; import itemID from '../../../../util/itemID'; -import resolveItems, { deepResolveItems } from '../../../../util/resolveItems'; -import { KillableMonster } from '../../../types'; +import { removeItemsFromLootTable } from '../../../../util/smallUtils'; +import type { KillableMonster } from '../../../types'; +import { QuestID } from '../../quests'; const awakenedDeathProps = { hardness: 0.9, diff --git a/src/lib/minions/data/killableMonsters/bosses/gwd.ts b/src/lib/minions/data/killableMonsters/bosses/gwd.ts index 6d0f7866862..fdf31d15c1d 100644 --- a/src/lib/minions/data/killableMonsters/bosses/gwd.ts +++ b/src/lib/minions/data/killableMonsters/bosses/gwd.ts @@ -1,6 +1,7 @@ import { Time } from 'e'; import { Monsters } from 'oldschooljs'; +import { deepResolveItems, resolveItems } from 'oldschooljs/dist/util/util'; import { commanderZilyanaCL, generalGraardorCL, @@ -10,8 +11,7 @@ import { import { GearStat } from '../../../../gear/types'; import { SkillsEnum } from '../../../../skilling/types'; import itemID from '../../../../util/itemID'; -import resolveItems, { deepResolveItems } from '../../../../util/resolveItems'; -import { KillableMonster } from '../../../types'; +import type { KillableMonster } from '../../../types'; const killableBosses: KillableMonster[] = [ { diff --git a/src/lib/minions/data/killableMonsters/bosses/misc.ts b/src/lib/minions/data/killableMonsters/bosses/misc.ts index c83d77891cb..f119624f176 100644 --- a/src/lib/minions/data/killableMonsters/bosses/misc.ts +++ b/src/lib/minions/data/killableMonsters/bosses/misc.ts @@ -1,14 +1,14 @@ -import { roll, Time } from 'e'; +import { Time, roll } from 'e'; import { Bank, Monsters } from 'oldschooljs'; import SimpleMonster from 'oldschooljs/dist/structures/SimpleMonster'; +import { deepResolveItems, resolveItems } from 'oldschooljs/dist/util/util'; import { corporealBeastCL, muspahCL } from '../../../../data/CollectionsExport'; import { GearStat } from '../../../../gear/types'; import { CorporealBeastTable } from '../../../../simulation/Corp'; import { SkillsEnum } from '../../../../skilling/types'; import itemID from '../../../../util/itemID'; -import resolveItems, { deepResolveItems } from '../../../../util/resolveItems'; -import { KillableMonster } from '../../../types'; +import type { KillableMonster } from '../../../types'; const killableBosses: KillableMonster[] = [ { diff --git a/src/lib/minions/data/killableMonsters/bosses/wildy.ts b/src/lib/minions/data/killableMonsters/bosses/wildy.ts index 81529c86706..d36e9b9ef91 100644 --- a/src/lib/minions/data/killableMonsters/bosses/wildy.ts +++ b/src/lib/minions/data/killableMonsters/bosses/wildy.ts @@ -1,12 +1,12 @@ import { Time } from 'e'; import { Bank, Monsters } from 'oldschooljs'; -import { WildernessDiary } from '../../../../diaries'; +import { deepResolveItems } from 'oldschooljs/dist/util/util'; import { GearStat } from '../../../../gear/types'; import { SkillsEnum } from '../../../../skilling/types'; import itemID from '../../../../util/itemID'; -import { deepResolveItems } from '../../../../util/resolveItems'; -import { KillableMonster } from '../../../types'; +import type { KillableMonster } from '../../../types'; +import { DiaryID } from '../../../types'; export const wildyKillableMonsters: KillableMonster[] = [ { @@ -251,7 +251,7 @@ export const wildyKillableMonsters: KillableMonster[] = [ ranged: 65, magic: 70 }, - diaryRequirement: [WildernessDiary, WildernessDiary.medium], + diaryRequirement: [DiaryID.Wilderness, 'medium'], defaultAttackStyles: [SkillsEnum.Ranged], healAmountNeeded: 8 * 20, attackStyleToUse: GearStat.AttackRanged, @@ -480,7 +480,7 @@ export const wildyKillableMonsters: KillableMonster[] = [ defence: 70, magic: 70 }, - diaryRequirement: [WildernessDiary, WildernessDiary.medium], + diaryRequirement: [DiaryID.Wilderness, 'medium'], defaultAttackStyles: [SkillsEnum.Attack], customMonsterHP: 420, healAmountNeeded: 8 * 20, @@ -760,7 +760,7 @@ export const wildyKillableMonsters: KillableMonster[] = [ defence: 70, magic: 70 }, - diaryRequirement: [WildernessDiary, WildernessDiary.medium], + diaryRequirement: [DiaryID.Wilderness, 'medium'], defaultAttackStyles: [SkillsEnum.Attack], healAmountNeeded: 8 * 20, attackStyleToUse: GearStat.AttackCrush, @@ -774,24 +774,33 @@ export const wildyKillableMonsters: KillableMonster[] = [ timeToFinish: Time.Minute * 4.3, emoji: '<:Pet_chaos_elemental:324127377070227456>', wildy: true, - + canBePked: true, + pkActivityRating: 4, + pkBaseDeathChance: 5, difficultyRating: 8, itemsRequired: deepResolveItems([ ['Pernix body', "Black d'hide body", "Karil's leathertop"], ['Pernix chaps', "Black d'hide chaps", "Karil's leatherskirt"] ]), qpRequired: 0, - itemInBankBoosts: [ + equippedItemBoosts: [ { - [itemID("Craw's bow")]: 20, - [itemID('Webweaver bow')]: 25 + items: [ + { boostPercent: 25, itemID: itemID('Webweaver bow') }, + { boostPercent: 20, itemID: itemID("Craw's bow") } + ], + gearSetup: 'wildy' }, { - [itemID('Archers ring')]: 3, - [itemID('Archers ring (i)')]: 5 + items: [ + { boostPercent: 5, itemID: itemID('Archers ring (i)') }, + { boostPercent: 3, itemID: itemID('Archers ring') } + ], + gearSetup: 'wildy' }, { - [itemID('Barrows gloves')]: 3 + items: [{ boostPercent: 3, itemID: itemID('Barrows gloves') }], + gearSetup: 'wildy' } ], defaultAttackStyles: [SkillsEnum.Attack], @@ -808,15 +817,27 @@ export const wildyKillableMonsters: KillableMonster[] = [ timeToFinish: Time.Minute * 3.3, emoji: '<:Ancient_staff:412845709453426689>', wildy: true, + canBePked: true, + pkActivityRating: 4, + pkBaseDeathChance: 2, difficultyRating: 6, qpRequired: 0, - itemInBankBoosts: [ + equippedItemBoosts: [ { - [itemID("Craw's bow")]: 20, - [itemID('Webweaver bow')]: 25 + items: [ + { boostPercent: 25, itemID: itemID('Webweaver bow') }, + { boostPercent: 20, itemID: itemID("Craw's bow") } + ], + gearSetup: 'wildy' }, - { [itemID("Karil's leathertop")]: 3 }, - { [itemID("Karil's leatherskirt")]: 3 } + { + items: [{ boostPercent: 3, itemID: itemID("Karil's leathertop") }], + gearSetup: 'wildy' + }, + { + items: [{ boostPercent: 3, itemID: itemID("Karil's leatherskirt") }], + gearSetup: 'wildy' + } ], defaultAttackStyles: [SkillsEnum.Ranged], combatXpMultiplier: 1.125, @@ -832,10 +853,17 @@ export const wildyKillableMonsters: KillableMonster[] = [ timeToFinish: Time.Minute * 2.9, emoji: '<:Fedora:456179157303427092>', wildy: true, - + canBePked: true, + pkActivityRating: 6, + pkBaseDeathChance: 7, difficultyRating: 6, qpRequired: 0, - itemInBankBoosts: [{ [itemID('Occult necklace')]: 10 }], + equippedItemBoosts: [ + { + items: [{ boostPercent: 10, itemID: itemID('Occult necklace') }], + gearSetup: 'wildy' + } + ], defaultAttackStyles: [SkillsEnum.Magic], combatXpMultiplier: 1.25, healAmountNeeded: 4 * 20, @@ -850,9 +878,21 @@ export const wildyKillableMonsters: KillableMonster[] = [ timeToFinish: Time.Minute * 3.0, emoji: '<:Scorpias_offspring:324127378773377024>', wildy: true, + canBePked: true, + pkActivityRating: 6, + pkBaseDeathChance: 7, difficultyRating: 7, qpRequired: 0, - itemInBankBoosts: [{ [itemID('Occult necklace')]: 10 }, { [itemID('Harmonised nightmare staff')]: 10 }], + equippedItemBoosts: [ + { + items: [{ boostPercent: 10, itemID: itemID('Occult necklace') }], + gearSetup: 'wildy' + }, + { + items: [{ boostPercent: 10, itemID: itemID('Harmonised nightmare staff') }], + gearSetup: 'wildy' + } + ], defaultAttackStyles: [SkillsEnum.Magic], combatXpMultiplier: 1.3, healAmountNeeded: 4 * 20, diff --git a/src/lib/minions/data/killableMonsters/camdozaalMonsters.ts b/src/lib/minions/data/killableMonsters/camdozaalMonsters.ts index 5aba8279dbb..31b5b5ba4ba 100644 --- a/src/lib/minions/data/killableMonsters/camdozaalMonsters.ts +++ b/src/lib/minions/data/killableMonsters/camdozaalMonsters.ts @@ -1,7 +1,7 @@ import { Time } from 'e'; import { Monsters } from 'oldschooljs'; -import { KillableMonster } from '../../types'; +import type { KillableMonster } from '../../types'; export const camdozaalMonsters: KillableMonster[] = [ { @@ -45,5 +45,3 @@ export const camdozaalMonsters: KillableMonster[] = [ qpRequired: 17 } ]; - -export default camdozaalMonsters; diff --git a/src/lib/minions/data/killableMonsters/chaeldarMonsters.ts b/src/lib/minions/data/killableMonsters/chaeldarMonsters.ts index 0c315a829f3..5eced686ef9 100644 --- a/src/lib/minions/data/killableMonsters/chaeldarMonsters.ts +++ b/src/lib/minions/data/killableMonsters/chaeldarMonsters.ts @@ -2,10 +2,10 @@ import { Time } from 'e'; import { Bank, Monsters } from 'oldschooljs'; import { itemID } from 'oldschooljs/dist/util'; +import { deepResolveItems, resolveItems } from 'oldschooljs/dist/util/util'; import { GearStat } from '../../../gear/types'; import { SkillsEnum } from '../../../skilling/types'; -import resolveItems, { deepResolveItems } from '../../../util/resolveItems'; -import { KillableMonster } from '../../types'; +import type { KillableMonster } from '../../types'; export const chaeldarMonsters: KillableMonster[] = [ { @@ -32,12 +32,15 @@ export const chaeldarMonsters: KillableMonster[] = [ timeToFinish: Time.Second * 60, table: Monsters.Aviansie, - wildy: false, + wildy: true, difficultyRating: 4, qpRequired: 0, defaultAttackStyles: [SkillsEnum.Ranged], disallowedAttackStyles: [SkillsEnum.Attack, SkillsEnum.Strength, SkillsEnum.Magic], - healAmountNeeded: 24 + healAmountNeeded: 24, + pkActivityRating: 7, + pkBaseDeathChance: 10, + revsWeaponBoost: true }, { id: Monsters.BlackDemon.id, @@ -45,7 +48,7 @@ export const chaeldarMonsters: KillableMonster[] = [ aliases: Monsters.BlackDemon.aliases, timeToFinish: Time.Second * 36, table: Monsters.BlackDemon, - wildy: false, + wildy: true, difficultyRating: 3, existsInCatacombs: true, @@ -64,7 +67,11 @@ export const chaeldarMonsters: KillableMonster[] = [ canCannon: true, // Even if no multi, can safespot for same effect cannonMulti: false, - canBarrage: false + canBarrage: false, + pkActivityRating: 7, + pkBaseDeathChance: 9, + revsWeaponBoost: true, + wildySlayerCave: true }, { id: Monsters.CaveHorror.id, @@ -209,7 +216,7 @@ export const chaeldarMonsters: KillableMonster[] = [ timeToFinish: Time.Second * 25, table: Monsters.GreaterDemon, - wildy: false, + wildy: true, existsInCatacombs: true, difficultyRating: 2, @@ -227,7 +234,11 @@ export const chaeldarMonsters: KillableMonster[] = [ attackStylesUsed: [GearStat.AttackSlash], canCannon: true, cannonMulti: true, - canBarrage: false + canBarrage: false, + pkActivityRating: 7, + pkBaseDeathChance: 9, + revsWeaponBoost: true, + wildySlayerCave: true }, { id: Monsters.IronDragon.id, diff --git a/src/lib/minions/data/killableMonsters/creatureCreation.ts b/src/lib/minions/data/killableMonsters/creatureCreation.ts index 9933d953955..2328356be2d 100644 --- a/src/lib/minions/data/killableMonsters/creatureCreation.ts +++ b/src/lib/minions/data/killableMonsters/creatureCreation.ts @@ -4,7 +4,7 @@ import { SkillsEnum } from 'oldschooljs/dist/constants'; import { GearStat } from '../../../gear/types'; import itemID from '../../../util/itemID'; -import { KillableMonster } from '../../types'; +import type { KillableMonster } from '../../types'; export const creatureCreationCreatures: KillableMonster[] = []; const creatures = [ diff --git a/src/lib/minions/data/killableMonsters/custom/SunMoon.ts b/src/lib/minions/data/killableMonsters/custom/SunMoon.ts index 836a19c632f..daeb4adbffc 100644 --- a/src/lib/minions/data/killableMonsters/custom/SunMoon.ts +++ b/src/lib/minions/data/killableMonsters/custom/SunMoon.ts @@ -2,10 +2,10 @@ import { Time } from 'e'; import { Bank, LootTable, Monsters } from 'oldschooljs'; import { GearStat } from '../../../../gear'; -import { addStatsOfItemsTogether, Gear } from '../../../../structures/Gear'; +import { Gear, addStatsOfItemsTogether } from '../../../../structures/Gear'; import itemID from '../../../../util/itemID'; import resolveItems from '../../../../util/resolveItems'; -import { CustomMonster } from './customMonsters'; +import type { CustomMonster } from './customMonsters'; const solisMinGear = new Gear(); solisMinGear.equip('Gorajan warrior helmet'); diff --git a/src/lib/minions/data/killableMonsters/custom/TormentedDemon.ts b/src/lib/minions/data/killableMonsters/custom/TormentedDemon.ts index 4f0e760de61..c9c5f60fe29 100644 --- a/src/lib/minions/data/killableMonsters/custom/TormentedDemon.ts +++ b/src/lib/minions/data/killableMonsters/custom/TormentedDemon.ts @@ -5,7 +5,7 @@ import { resolveNameBank } from 'oldschooljs/dist/util'; import { GearStat } from '../../../../gear'; import { GrimyHerbTable, lowRuneHighAdamantTable, runeAlchablesTable } from '../../../../simulation/sharedTables'; -import { CustomMonster } from './customMonsters'; +import type { CustomMonster } from './customMonsters'; export const TormentedDemon: CustomMonster = { id: 941_944, diff --git a/src/lib/minions/data/killableMonsters/custom/VladimirDrakan.ts b/src/lib/minions/data/killableMonsters/custom/VladimirDrakan.ts index d230f63556c..85448184852 100644 --- a/src/lib/minions/data/killableMonsters/custom/VladimirDrakan.ts +++ b/src/lib/minions/data/killableMonsters/custom/VladimirDrakan.ts @@ -1,4 +1,4 @@ -import { roll, shuffleArr, Time, uniqueArr } from 'e'; +import { Time, roll, shuffleArr, uniqueArr } from 'e'; import { Bank, LootTable, Monsters } from 'oldschooljs'; import { vladDrakanCL } from '../../../../data/CollectionsExport'; @@ -7,7 +7,7 @@ import { GearStat } from '../../../../gear'; import { runeWeaponTable } from '../../../../simulation/sharedTables'; import { clAdjustedDroprate, randomVariation, resolveNameBank } from '../../../../util'; import resolveItems from '../../../../util/resolveItems'; -import { CustomMonster } from './customMonsters'; +import type { CustomMonster } from './customMonsters'; const JewelleryTable = new LootTable() .add(new LootTable().add('Onyx ring').add('Onyx necklace').add('Onyx bracelet').add('Onyx amulet')) diff --git a/src/lib/minions/data/killableMonsters/custom/Yeti.ts b/src/lib/minions/data/killableMonsters/custom/Yeti.ts index 7ed630520aa..3dcaf290cde 100644 --- a/src/lib/minions/data/killableMonsters/custom/Yeti.ts +++ b/src/lib/minions/data/killableMonsters/custom/Yeti.ts @@ -7,7 +7,7 @@ import LootTable from 'oldschooljs/dist/structures/LootTable'; import { BitField, YETI_ID } from '../../../../constants'; import { GearStat } from '../../../../gear'; -import { CustomMonster } from './customMonsters'; +import type { CustomMonster } from './customMonsters'; const DeadTable = new LootTable().add('Iron med helm').add('Bones').add('Skull').add('Bronze sword'); diff --git a/src/lib/minions/data/killableMonsters/custom/bosses/Akumu.ts b/src/lib/minions/data/killableMonsters/custom/bosses/Akumu.ts index e7cfaac6c4f..2f0cb554f13 100644 --- a/src/lib/minions/data/killableMonsters/custom/bosses/Akumu.ts +++ b/src/lib/minions/data/killableMonsters/custom/bosses/Akumu.ts @@ -7,7 +7,7 @@ import { GearStat } from '../../../../../gear'; import { UncutGemTable } from '../../../../../simulation/sharedTables'; import itemID from '../../../../../util/itemID'; import resolveItems from '../../../../../util/resolveItems'; -import { CustomMonster } from '../customMonsters'; +import type { CustomMonster } from '../customMonsters'; export const AkumuLootTable = new LootTable() .tertiary(1000, 'Mini akumu') diff --git a/src/lib/minions/data/killableMonsters/custom/bosses/KalphiteKing.ts b/src/lib/minions/data/killableMonsters/custom/bosses/KalphiteKing.ts index d228af8edd7..2d020aea622 100644 --- a/src/lib/minions/data/killableMonsters/custom/bosses/KalphiteKing.ts +++ b/src/lib/minions/data/killableMonsters/custom/bosses/KalphiteKing.ts @@ -6,7 +6,7 @@ import { kalphiteKingCL } from '../../../../../data/CollectionsExport'; import { GearStat } from '../../../../../gear'; import { SeedTable } from '../../../../../simulation/seedTable'; import setCustomMonster, { makeKillTable } from '../../../../../util/setCustomMonster'; -import { KillableMonster } from '../../../../types'; +import type { KillableMonster } from '../../../../types'; import { GrimyHerbTable } from '../Treebeard'; export const kalphiteKingLootTable = new LootTable() diff --git a/src/lib/minions/data/killableMonsters/custom/bosses/Moktang.ts b/src/lib/minions/data/killableMonsters/custom/bosses/Moktang.ts index a92d2d72b30..d5fef2df7a2 100644 --- a/src/lib/minions/data/killableMonsters/custom/bosses/Moktang.ts +++ b/src/lib/minions/data/killableMonsters/custom/bosses/Moktang.ts @@ -5,9 +5,9 @@ import { ClueTable, FletchingTipsTable, HighTierStoneSpiritTable, + StoneSpiritTable, lowRuneHighAdamantTable, - runeWeaponTable, - StoneSpiritTable + runeWeaponTable } from '../../../../../simulation/sharedTables'; export const MOKTANG_ID = 391_241; diff --git a/src/lib/minions/data/killableMonsters/custom/bosses/Naxxus.ts b/src/lib/minions/data/killableMonsters/custom/bosses/Naxxus.ts index e5751661737..7f791c00a29 100644 --- a/src/lib/minions/data/killableMonsters/custom/bosses/Naxxus.ts +++ b/src/lib/minions/data/killableMonsters/custom/bosses/Naxxus.ts @@ -5,7 +5,7 @@ import LootTable from 'oldschooljs/dist/structures/LootTable'; import { GearStat } from '../../../../../gear'; import resolveItems from '../../../../../util/resolveItems'; import setCustomMonster, { makeKillTable } from '../../../../../util/setCustomMonster'; -import { KillableMonster } from '../../../../types'; +import type { KillableMonster } from '../../../../types'; const runes = new LootTable() .add('Air rune', [5000, 15_000]) diff --git a/src/lib/minions/data/killableMonsters/custom/bosses/Venatrix.ts b/src/lib/minions/data/killableMonsters/custom/bosses/Venatrix.ts index 50a0fb1b191..a1b5fee1831 100644 --- a/src/lib/minions/data/killableMonsters/custom/bosses/Venatrix.ts +++ b/src/lib/minions/data/killableMonsters/custom/bosses/Venatrix.ts @@ -7,7 +7,7 @@ import { GearStat } from '../../../../../gear'; import { addStatsOfItemsTogether } from '../../../../../structures/Gear'; import itemID from '../../../../../util/itemID'; import resolveItems from '../../../../../util/resolveItems'; -import { CustomMonster } from '../customMonsters'; +import type { CustomMonster } from '../customMonsters'; export const VenatrixLootTable = new LootTable() .every('Venatrix eggs') diff --git a/src/lib/minions/data/killableMonsters/custom/customMonsters.ts b/src/lib/minions/data/killableMonsters/custom/customMonsters.ts index a8c988d937e..dbe005d703d 100644 --- a/src/lib/minions/data/killableMonsters/custom/customMonsters.ts +++ b/src/lib/minions/data/killableMonsters/custom/customMonsters.ts @@ -1,13 +1,13 @@ -import { LootTable } from 'oldschooljs'; -import { MonsterData } from 'oldschooljs/dist/meta/monsterData'; -import Monster from 'oldschooljs/dist/structures/Monster'; +import type { LootTable } from 'oldschooljs'; +import type { MonsterData } from 'oldschooljs/dist/meta/monsterData'; +import type Monster from 'oldschooljs/dist/structures/Monster'; import setCustomMonster, { makeKillTable } from '../../../../util/setCustomMonster'; -import { KillableMonster } from '../../../types'; +import type { KillableMonster } from '../../../types'; +import { SunMoonMonsters } from './SunMoon'; import { customDemiBosses } from './demiBosses'; import { MiscCustomMonsters } from './misc'; import { resourceDungeonMonsters } from './resourceDungeons'; -import { SunMoonMonsters } from './SunMoon'; declare module 'oldschooljs/dist/structures/Monster' { export default interface Monster { diff --git a/src/lib/minions/data/killableMonsters/custom/demiBosses.ts b/src/lib/minions/data/killableMonsters/custom/demiBosses.ts index e8b182432c8..793d141bd1a 100644 --- a/src/lib/minions/data/killableMonsters/custom/demiBosses.ts +++ b/src/lib/minions/data/killableMonsters/custom/demiBosses.ts @@ -10,10 +10,10 @@ import { GearStat } from '../../../../gear'; import { SkillsEnum } from '../../../../skilling/types'; import resolveItems, { deepResolveItems } from '../../../../util/resolveItems'; import { AbyssalDragonLootTable } from './AbyssalDragon'; -import { CustomMonster } from './customMonsters'; import { NihilizLootTable } from './Nihiliz'; import { KrakenTable } from './SeaKraken'; import { TreebeardLootTable } from './Treebeard'; +import type { CustomMonster } from './customMonsters'; const SeaKraken: CustomMonster = { id: 53_466_534, @@ -103,7 +103,8 @@ const Malygos: CustomMonster = { uniques: resolveItems(['Abyssal thread', 'Abyssal cape', 'Ori', 'Dragon hunter lance']), notifyDrops: resolveItems(['Abyssal cape', 'Ori']), baseMonster: Monsters.Vorkath, - customMonsterData: { attributes: [MonsterAttribute.Dragon, MonsterAttribute.Fiery] } + customMonsterData: { attributes: [MonsterAttribute.Dragon, MonsterAttribute.Fiery] }, + canBePked: true }; const Treebeard: CustomMonster = { @@ -139,7 +140,8 @@ const Treebeard: CustomMonster = { resolveNameBank({ 'Axe of the high sungod': 10 }) - ] + ], + canBePked: true }; export const QueenBlackDragon: CustomMonster = { diff --git a/src/lib/minions/data/killableMonsters/custom/misc.ts b/src/lib/minions/data/killableMonsters/custom/misc.ts index ba70a12db1a..5dd88626363 100644 --- a/src/lib/minions/data/killableMonsters/custom/misc.ts +++ b/src/lib/minions/data/killableMonsters/custom/misc.ts @@ -4,14 +4,14 @@ import { GemTable } from 'oldschooljs/dist/simulation/subtables/RareDropTable'; import { LowSeedPackTable } from '../../../../data/seedPackTables'; import { GearStat } from '../../../../gear'; -import { BattlestaffTable, runeAlchablesTable, StoneSpiritTable } from '../../../../simulation/sharedTables'; +import { BattlestaffTable, StoneSpiritTable, runeAlchablesTable } from '../../../../simulation/sharedTables'; import resolveItems from '../../../../util/resolveItems'; -import { Akumu } from './bosses/Akumu'; -import { Venatrix } from './bosses/Venatrix'; -import { CustomMonster } from './customMonsters'; import { TormentedDemon } from './TormentedDemon'; import { VladimirDrakan } from './VladimirDrakan'; import { Yeti } from './Yeti'; +import { Akumu } from './bosses/Akumu'; +import { Venatrix } from './bosses/Venatrix'; +import type { CustomMonster } from './customMonsters'; export const CockroachSoldier: CustomMonster = { id: 31_621, diff --git a/src/lib/minions/data/killableMonsters/custom/resourceDungeons.ts b/src/lib/minions/data/killableMonsters/custom/resourceDungeons.ts index 2e3c706e0b3..475feac43a1 100644 --- a/src/lib/minions/data/killableMonsters/custom/resourceDungeons.ts +++ b/src/lib/minions/data/killableMonsters/custom/resourceDungeons.ts @@ -8,8 +8,8 @@ import { GearStat } from '../../../../gear'; import { lowRuneHighAdamantTable, runeWeaponTable } from '../../../../simulation/sharedTables'; import { SkillsEnum } from '../../../../skilling/types'; import resolveItems from '../../../../util/resolveItems'; -import { CustomMonster } from './customMonsters'; import { GrimyHerbTable } from './Treebeard'; +import type { CustomMonster } from './customMonsters'; function neemCost(extraCost?: Bank) { const cost = new Bank().add('Neem oil', 1); diff --git a/src/lib/minions/data/killableMonsters/index.ts b/src/lib/minions/data/killableMonsters/index.ts index f5d61ee64f4..c23aa06e33f 100644 --- a/src/lib/minions/data/killableMonsters/index.ts +++ b/src/lib/minions/data/killableMonsters/index.ts @@ -7,7 +7,7 @@ import { PUMPKINHEAD_ID } from '../../../simulation/pumpkinHead'; import { SkillsEnum } from '../../../skilling/types'; import itemID from '../../../util/itemID'; import resolveItems, { deepResolveItems } from '../../../util/resolveItems'; -import { KillableMonster } from '../../types'; +import type { KillableMonster } from '../../types'; import { NIGHTMARES_HP } from './../../../constants'; import { bossKillables } from './bosses'; import { camdozaalMonsters } from './camdozaalMonsters'; @@ -255,10 +255,16 @@ const killableMonsters: KillableMonster[] = [ [itemID('Scythe of vitur')]: 15 }, { + [itemID('Masori body (f)')]: 4, [itemID("Karil's leathertop")]: 3 }, { + [itemID('Masori chaps (f)')]: 3, [itemID("Karil's leatherskirt")]: 2 + }, + // Transformation ring + { + [itemID('Ring of stone')]: 10 } ], levelRequirements: { diff --git a/src/lib/minions/data/killableMonsters/konarMonsters.ts b/src/lib/minions/data/killableMonsters/konarMonsters.ts index aafb9832bf0..49c0aa5656d 100644 --- a/src/lib/minions/data/killableMonsters/konarMonsters.ts +++ b/src/lib/minions/data/killableMonsters/konarMonsters.ts @@ -2,9 +2,9 @@ import { Time } from 'e'; import { Bank, Monsters } from 'oldschooljs'; import { itemID } from 'oldschooljs/dist/util'; +import { resolveItems } from 'oldschooljs/dist/util/util'; import { GearStat } from '../../../gear/types'; -import resolveItems from '../../../util/resolveItems'; -import { KillableMonster } from '../../types'; +import type { KillableMonster } from '../../types'; export const konarMonsters: KillableMonster[] = [ { diff --git a/src/lib/minions/data/killableMonsters/krystiliaMonsters.ts b/src/lib/minions/data/killableMonsters/krystiliaMonsters.ts index 3bfb7eda8c9..460a1c108de 100644 --- a/src/lib/minions/data/killableMonsters/krystiliaMonsters.ts +++ b/src/lib/minions/data/killableMonsters/krystiliaMonsters.ts @@ -1,8 +1,8 @@ import { Time } from 'e'; import { Monsters } from 'oldschooljs'; -import resolveItems from '../../../util/resolveItems'; -import { KillableMonster } from '../../types'; +import resolveItems, { deepResolveItems } from '../../../util/resolveItems'; +import type { KillableMonster } from '../../types'; export const krystiliaMonsters: KillableMonster[] = [ { @@ -14,7 +14,12 @@ export const krystiliaMonsters: KillableMonster[] = [ wildy: true, difficultyRating: 5, - qpRequired: 0 + qpRequired: 0, + canCannon: true, + pkActivityRating: 1, + pkBaseDeathChance: 1, + revsWeaponBoost: true, + wildyMulti: true }, { id: Monsters.ChaosDruid.id, @@ -28,8 +33,11 @@ export const krystiliaMonsters: KillableMonster[] = [ difficultyRating: 2, qpRequired: 0, canCannon: true, - cannonMulti: true, - canBarrage: false + cannonMulti: false, + canBarrage: false, + pkActivityRating: 1, + pkBaseDeathChance: 1, + revsWeaponBoost: true }, { id: Monsters.DarkWarrior.id, @@ -44,7 +52,11 @@ export const krystiliaMonsters: KillableMonster[] = [ qpRequired: 0, canCannon: true, cannonMulti: false, - canBarrage: false + canBarrage: false, + pkActivityRating: 1, + pkBaseDeathChance: 1, + revsWeaponBoost: true, + wildyMulti: true }, { id: Monsters.DeadlyRedSpider.id, @@ -59,7 +71,8 @@ export const krystiliaMonsters: KillableMonster[] = [ qpRequired: 0, canCannon: true, cannonMulti: false, - canBarrage: false + canBarrage: false, + revsWeaponBoost: true }, { id: Monsters.ElderChaosDruid.id, @@ -74,7 +87,11 @@ export const krystiliaMonsters: KillableMonster[] = [ qpRequired: 0, canCannon: true, cannonMulti: true, - canBarrage: false + canBarrage: false, + pkActivityRating: 2, + pkBaseDeathChance: 7, + revsWeaponBoost: true, + wildyMulti: true }, { id: Monsters.Ent.id, @@ -86,11 +103,14 @@ export const krystiliaMonsters: KillableMonster[] = [ wildy: true, difficultyRating: 3, - itemsRequired: resolveItems(['Dragon axe', 'Rune axe']), + itemsRequired: deepResolveItems([['Dragon axe', 'Rune axe']]), qpRequired: 0, canCannon: true, cannonMulti: false, - canBarrage: false + canBarrage: false, + pkActivityRating: 2, + pkBaseDeathChance: 4, + revsWeaponBoost: true }, { id: Monsters.GuardBandit.id, @@ -105,7 +125,12 @@ export const krystiliaMonsters: KillableMonster[] = [ qpRequired: 0, canCannon: true, cannonMulti: true, - canBarrage: false + canBarrage: false, + pkActivityRating: 1, + pkBaseDeathChance: 1, + revsWeaponBoost: true, + canBePked: true, + wildyMulti: true }, { id: Monsters.LavaDragon.id, @@ -119,7 +144,12 @@ export const krystiliaMonsters: KillableMonster[] = [ difficultyRating: 4, itemsRequired: resolveItems(['Anti-dragon shield']), notifyDrops: resolveItems(['Draconic visage']), - qpRequired: 0 + qpRequired: 0, + pkActivityRating: 3, + pkBaseDeathChance: 4, + revsWeaponBoost: true, + canBePked: true, + wildyMulti: true }, { id: Monsters.MagicAxe.id, @@ -131,11 +161,15 @@ export const krystiliaMonsters: KillableMonster[] = [ wildy: true, difficultyRating: 3, - // itemsRequired: resolveItems(['Lockpick']), + itemsRequired: resolveItems(['Lockpick']), qpRequired: 0, levelRequirements: { - // theiving: 23 - } + thieving: 23 + }, + pkActivityRating: 1, + pkBaseDeathChance: 1, + revsWeaponBoost: true, + canCannon: true }, { id: Monsters.Mammoth.id, @@ -150,7 +184,12 @@ export const krystiliaMonsters: KillableMonster[] = [ qpRequired: 0, canCannon: true, cannonMulti: true, - canBarrage: false + canBarrage: false, + pkActivityRating: 1, + pkBaseDeathChance: 1, + revsWeaponBoost: true, + canBePked: true, + wildyMulti: true }, { id: Monsters.Pirate.id, @@ -162,10 +201,13 @@ export const krystiliaMonsters: KillableMonster[] = [ wildy: true, difficultyRating: 3, - // itemsRequired: resolveItems(['Lockpick']), + itemsRequired: resolveItems(['Lockpick']), levelRequirements: { - // thieving: 39 + thieving: 39 }, - qpRequired: 0 + qpRequired: 0, + pkActivityRating: 1, + pkBaseDeathChance: 1, + revsWeaponBoost: true } ]; diff --git a/src/lib/minions/data/killableMonsters/low.ts b/src/lib/minions/data/killableMonsters/low.ts index 48ea7da3d41..0e4b8dd7f02 100644 --- a/src/lib/minions/data/killableMonsters/low.ts +++ b/src/lib/minions/data/killableMonsters/low.ts @@ -2,10 +2,10 @@ import { Time } from 'e'; import { Monsters } from 'oldschooljs'; import { SkillsEnum } from 'oldschooljs/dist/constants'; +import { resolveItems } from 'oldschooljs/dist/util/util'; import { GearStat } from '../../../gear/types'; import itemID from '../../../util/itemID'; -import resolveItems from '../../../util/resolveItems'; -import { KillableMonster } from '../../types'; +import type { KillableMonster } from '../../types'; const killableMonsters: KillableMonster[] = [ { diff --git a/src/lib/minions/data/killableMonsters/mazchnaMonsters.ts b/src/lib/minions/data/killableMonsters/mazchnaMonsters.ts index e7c6a0a5e8d..18e68b5ada7 100644 --- a/src/lib/minions/data/killableMonsters/mazchnaMonsters.ts +++ b/src/lib/minions/data/killableMonsters/mazchnaMonsters.ts @@ -3,7 +3,7 @@ import { Bank, Monsters } from 'oldschooljs'; import { GearStat } from '../../../gear/types'; import itemID from '../../../util/itemID'; -import { KillableMonster } from '../../types'; +import type { KillableMonster } from '../../types'; export const mazchnaMonsters: KillableMonster[] = [ { @@ -86,7 +86,11 @@ export const mazchnaMonsters: KillableMonster[] = [ attackStylesUsed: [GearStat.AttackCrush], canCannon: true, cannonMulti: false, - canBarrage: false + canBarrage: false, + pkActivityRating: 4, + pkBaseDeathChance: 2, + revsWeaponBoost: true, + canBePked: true }, { id: Monsters.FeralVampyre.id, @@ -151,7 +155,7 @@ export const mazchnaMonsters: KillableMonster[] = [ aliases: Monsters.HillGiant.aliases, timeToFinish: Time.Second * 10, table: Monsters.HillGiant, - wildy: false, + wildy: true, existsInCatacombs: true, difficultyRating: 1, @@ -161,7 +165,10 @@ export const mazchnaMonsters: KillableMonster[] = [ attackStylesUsed: [GearStat.AttackCrush], canCannon: true, cannonMulti: false, - canBarrage: false + canBarrage: false, + pkActivityRating: 1, + pkBaseDeathChance: 5, + revsWeaponBoost: true }, { id: Monsters.Obor.id, @@ -211,7 +218,10 @@ export const mazchnaMonsters: KillableMonster[] = [ attackStylesUsed: [GearStat.AttackCrush], canCannon: true, cannonMulti: false, - canBarrage: false + canBarrage: false, + pkActivityRating: 4, + pkBaseDeathChance: 3, + revsWeaponBoost: true }, { id: Monsters.Killerwatt.id, diff --git a/src/lib/minions/data/killableMonsters/nieveMonsters.ts b/src/lib/minions/data/killableMonsters/nieveMonsters.ts index e9fee1abf49..30117b37168 100644 --- a/src/lib/minions/data/killableMonsters/nieveMonsters.ts +++ b/src/lib/minions/data/killableMonsters/nieveMonsters.ts @@ -2,9 +2,9 @@ import { Time } from 'e'; import { Monsters } from 'oldschooljs'; import { itemID } from 'oldschooljs/dist/util'; +import { deepResolveItems, resolveItems } from 'oldschooljs/dist/util/util'; import { GearStat } from '../../../gear/types'; -import resolveItems, { deepResolveItems } from '../../../util/resolveItems'; -import { KillableMonster } from '../../types'; +import type { KillableMonster } from '../../types'; export const nieveMonsters: KillableMonster[] = [ { @@ -32,7 +32,7 @@ export const nieveMonsters: KillableMonster[] = [ timeToFinish: Time.Second * 76, table: Monsters.BlackDragon, - wildy: false, + wildy: true, difficultyRating: 4, itemsRequired: resolveItems(['Anti-dragon shield']), @@ -42,7 +42,11 @@ export const nieveMonsters: KillableMonster[] = [ attackStylesUsed: [GearStat.AttackSlash], canCannon: true, cannonMulti: false, - canBarrage: false + canBarrage: false, + pkActivityRating: 2, + pkBaseDeathChance: 7, + revsWeaponBoost: true, + wildySlayerCave: true }, { id: Monsters.BrutalBlackDragon.id, diff --git a/src/lib/minions/data/killableMonsters/reanimated.ts b/src/lib/minions/data/killableMonsters/reanimated.ts index b95dd92eaaa..e92845dccc0 100644 --- a/src/lib/minions/data/killableMonsters/reanimated.ts +++ b/src/lib/minions/data/killableMonsters/reanimated.ts @@ -3,7 +3,7 @@ import { Bank, Monsters } from 'oldschooljs'; import { GearStat } from '../../../gear/types'; import { SkillsEnum } from '../../../skilling/types'; -import { KillableMonster } from '../../types'; +import type { KillableMonster } from '../../types'; const renanimatedMonstersRaw = [ { diff --git a/src/lib/minions/data/killableMonsters/revs.ts b/src/lib/minions/data/killableMonsters/revs.ts index a08f3f89392..f0beada3a08 100644 --- a/src/lib/minions/data/killableMonsters/revs.ts +++ b/src/lib/minions/data/killableMonsters/revs.ts @@ -1,7 +1,7 @@ import { Time } from 'e'; import { Bank, Monsters } from 'oldschooljs'; -import { KillableMonster } from '../../types'; +import type { KillableMonster } from '../../types'; export const revenantMonsters: KillableMonster[] = [ { @@ -74,7 +74,7 @@ export const revenantMonsters: KillableMonster[] = [ timeToFinish: Time.Second * 147, table: Monsters.RevenantDragon, wildy: true, - difficultyRating: 9, + difficultyRating: 10, qpRequired: 0, pkActivityRating: 9, pkBaseDeathChance: 8, diff --git a/src/lib/minions/data/killableMonsters/turaelMonsters.ts b/src/lib/minions/data/killableMonsters/turaelMonsters.ts index c7ea433b62b..88983bd266d 100644 --- a/src/lib/minions/data/killableMonsters/turaelMonsters.ts +++ b/src/lib/minions/data/killableMonsters/turaelMonsters.ts @@ -2,10 +2,10 @@ import { Time } from 'e'; import { Monsters } from 'oldschooljs'; import { itemID } from 'oldschooljs/dist/util'; +import { deepResolveItems } from 'oldschooljs/dist/util/util'; import { GearStat } from '../../../gear/types'; import { SkillsEnum } from '../../../skilling/types'; -import { deepResolveItems } from '../../../util/resolveItems'; -import { KillableMonster } from '../../types'; +import type { KillableMonster } from '../../types'; export const turaelMonsters: KillableMonster[] = [ { @@ -455,7 +455,9 @@ export const turaelMonsters: KillableMonster[] = [ qpRequired: 0, canCannon: true, cannonMulti: false, - canBarrage: false + canBarrage: false, + pkActivityRating: 1, + pkBaseDeathChance: 1 }, { id: Monsters.Goblin.id, @@ -483,7 +485,10 @@ export const turaelMonsters: KillableMonster[] = [ qpRequired: 0, canCannon: true, cannonMulti: false, - canBarrage: false + canBarrage: false, + pkActivityRating: 8, + pkBaseDeathChance: 4, + revsWeaponBoost: true }, { id: Monsters.GrizzlyBearCub.id, @@ -840,7 +845,7 @@ export const turaelMonsters: KillableMonster[] = [ timeToFinish: Time.Second * 10, table: Monsters.Scorpion, - wildy: false, + wildy: true, difficultyRating: 1, qpRequired: 0, @@ -849,7 +854,10 @@ export const turaelMonsters: KillableMonster[] = [ canBarrage: false, healAmountNeeded: 8, attackStyleToUse: GearStat.AttackSlash, - attackStylesUsed: [GearStat.AttackCrush] + attackStylesUsed: [GearStat.AttackCrush], + pkActivityRating: 3, + pkBaseDeathChance: 2, + revsWeaponBoost: true }, { id: Monsters.Seagull.id, @@ -890,7 +898,7 @@ export const turaelMonsters: KillableMonster[] = [ timeToFinish: Time.Second * 10, table: Monsters.Skeleton, - wildy: false, + wildy: true, existsInCatacombs: true, difficultyRating: 1, @@ -900,7 +908,10 @@ export const turaelMonsters: KillableMonster[] = [ canBarrage: false, healAmountNeeded: 11, attackStyleToUse: GearStat.AttackSlash, - attackStylesUsed: [GearStat.AttackCrush] + attackStylesUsed: [GearStat.AttackCrush], + pkActivityRating: 1, + pkBaseDeathChance: 1, + revsWeaponBoost: true }, { id: Monsters.SkeletonFremennik.id, @@ -971,13 +982,16 @@ export const turaelMonsters: KillableMonster[] = [ timeToFinish: Time.Second * 5, table: Monsters.Spider, - wildy: false, + wildy: true, difficultyRating: 1, qpRequired: 0, canCannon: true, cannonMulti: false, - canBarrage: false + canBarrage: false, + pkActivityRating: 1, + pkBaseDeathChance: 1, + revsWeaponBoost: true }, { id: Monsters.SulphurLizard.id, @@ -1200,7 +1214,7 @@ export const turaelMonsters: KillableMonster[] = [ aliases: Monsters.Zombie.aliases, timeToFinish: Time.Second * 10, table: Monsters.Zombie, - wildy: false, + wildy: true, difficultyRating: 1, qpRequired: 0, @@ -1209,7 +1223,10 @@ export const turaelMonsters: KillableMonster[] = [ canBarrage: false, healAmountNeeded: 9, attackStyleToUse: GearStat.AttackSlash, - attackStylesUsed: [GearStat.AttackCrush] + attackStylesUsed: [GearStat.AttackCrush], + pkActivityRating: 6, + pkBaseDeathChance: 4, + revsWeaponBoost: true }, { id: Monsters.ZombieRat.id, diff --git a/src/lib/minions/data/killableMonsters/vannakaMonsters.ts b/src/lib/minions/data/killableMonsters/vannakaMonsters.ts index 3e1b739de18..8893da4fd3d 100644 --- a/src/lib/minions/data/killableMonsters/vannakaMonsters.ts +++ b/src/lib/minions/data/killableMonsters/vannakaMonsters.ts @@ -2,10 +2,10 @@ import { Time } from 'e'; import { Bank, Monsters } from 'oldschooljs'; import { itemID } from 'oldschooljs/dist/util'; +import { deepResolveItems, resolveItems } from 'oldschooljs/dist/util/util'; import { GearStat } from '../../../gear/types'; -import resolveItems, { deepResolveItems } from '../../../util/resolveItems'; import { makeKillTable } from '../../../util/setCustomMonster'; -import { KillableMonster } from '../../types'; +import type { KillableMonster } from '../../types'; export const vannakaMonsters: KillableMonster[] = [ { @@ -38,7 +38,7 @@ export const vannakaMonsters: KillableMonster[] = [ timeToFinish: Time.Second * 29, table: Monsters.AbyssalDemon, - wildy: false, + wildy: true, difficultyRating: 3, qpRequired: 0, @@ -58,7 +58,11 @@ export const vannakaMonsters: KillableMonster[] = [ healAmountNeeded: 35, attackStyleToUse: GearStat.AttackSlash, attackStylesUsed: [GearStat.AttackStab], - canBarrage: true + canBarrage: true, + pkActivityRating: 7, + pkBaseDeathChance: 10, + revsWeaponBoost: true, + wildySlayerCave: true }, { id: Monsters.AbyssalSire.id, @@ -119,7 +123,11 @@ export const vannakaMonsters: KillableMonster[] = [ [itemID('Kodai wand')]: 12, [itemID('Staff of the dead')]: 8 } - ] + ], + pkActivityRating: 4, + pkBaseDeathChance: 3, + revsWeaponBoost: true, + wildySlayerCave: true }, { id: Monsters.BabyBlueDragon.id, @@ -208,7 +216,7 @@ export const vannakaMonsters: KillableMonster[] = [ timeToFinish: Time.Second * 27, table: Monsters.Bloodveld, - wildy: false, + wildy: true, difficultyRating: 1, qpRequired: 0, @@ -236,7 +244,9 @@ export const vannakaMonsters: KillableMonster[] = [ healAmountNeeded: 12, attackStyleToUse: GearStat.AttackRanged, attackStylesUsed: [GearStat.AttackMagic], - canCannon: true + pkActivityRating: 4, + pkBaseDeathChance: 6, + revsWeaponBoost: true }, { id: Monsters.BlueDragon.id, @@ -439,7 +449,7 @@ export const vannakaMonsters: KillableMonster[] = [ timeToFinish: Time.Second * 18, table: Monsters.DustDevil, - wildy: false, + wildy: true, difficultyRating: 2, existsInCatacombs: true, @@ -460,7 +470,11 @@ export const vannakaMonsters: KillableMonster[] = [ canBarrage: true, healAmountNeeded: 16, attackStyleToUse: GearStat.AttackSlash, - attackStylesUsed: [GearStat.AttackCrush] + attackStylesUsed: [GearStat.AttackCrush], + pkActivityRating: 6, + pkBaseDeathChance: 8, + revsWeaponBoost: true, + wildySlayerCave: true }, { id: Monsters.ElfArcher.id, @@ -532,7 +546,10 @@ export const vannakaMonsters: KillableMonster[] = [ healAmountNeeded: 17, attackStyleToUse: GearStat.AttackSlash, attackStylesUsed: [GearStat.AttackSlash], - canCannon: true + canCannon: true, + pkActivityRating: 3, + pkBaseDeathChance: 8, + revsWeaponBoost: true }, { id: Monsters.Gargoyle.id, @@ -617,7 +634,7 @@ export const vannakaMonsters: KillableMonster[] = [ timeToFinish: Time.Second * 37.2, table: Monsters.GreaterNechryael, - wildy: false, + wildy: true, difficultyRating: 5, qpRequired: 0, @@ -637,7 +654,11 @@ export const vannakaMonsters: KillableMonster[] = [ attackStyleToUse: GearStat.AttackSlash, attackStylesUsed: [GearStat.AttackCrush], canBarrage: true, - canCannon: true + canCannon: true, + pkActivityRating: 8, + pkBaseDeathChance: 9, + revsWeaponBoost: true, + wildySlayerCave: true }, { id: Monsters.GreenDragon.id, @@ -654,7 +675,11 @@ export const vannakaMonsters: KillableMonster[] = [ healAmountNeeded: 20, attackStyleToUse: GearStat.AttackSlash, attackStylesUsed: [GearStat.AttackSlash], - canCannon: true + canCannon: true, + revsWeaponBoost: true, + wildySlayerCave: true, + pkActivityRating: 2, + pkBaseDeathChance: 4 }, { id: Monsters.HarpieBugSwarm.id, @@ -682,7 +707,7 @@ export const vannakaMonsters: KillableMonster[] = [ timeToFinish: Time.Second * 39, table: Monsters.Hellhound, - wildy: false, + wildy: true, existsInCatacombs: true, difficultyRating: 3, @@ -699,7 +724,11 @@ export const vannakaMonsters: KillableMonster[] = [ canCannon: true, // Not multi but you can safespot for the same effect cannonMulti: true, - canBarrage: false + canBarrage: false, + pkActivityRating: 5, + pkBaseDeathChance: 8, + revsWeaponBoost: true, + wildySlayerCave: true }, { id: Monsters.IceGiant.id, @@ -716,8 +745,12 @@ export const vannakaMonsters: KillableMonster[] = [ attackStyleToUse: GearStat.AttackSlash, attackStylesUsed: [GearStat.AttackSlash], canCannon: true, - cannonMulti: false, - canBarrage: false + cannonMulti: true, + canBarrage: false, + pkActivityRating: 2, + pkBaseDeathChance: 6, + revsWeaponBoost: true, + wildySlayerCave: true }, { id: Monsters.IceTroll.id, @@ -799,7 +832,7 @@ export const vannakaMonsters: KillableMonster[] = [ timeToFinish: Time.Second * 25, table: Monsters.Jelly, - wildy: false, + wildy: true, difficultyRating: 2, qpRequired: 0, @@ -809,7 +842,11 @@ export const vannakaMonsters: KillableMonster[] = [ superior: Monsters.VitreousJelly, healAmountNeeded: 14, attackStyleToUse: GearStat.AttackRanged, - attackStylesUsed: [GearStat.AttackMagic] + attackStylesUsed: [GearStat.AttackMagic], + pkActivityRating: 6, + pkBaseDeathChance: 8, + revsWeaponBoost: true, + wildySlayerCave: true }, { id: Monsters.JungleHorror.id, @@ -877,7 +914,11 @@ export const vannakaMonsters: KillableMonster[] = [ canCannon: true, // No multi spots (i think) but you can safespot for same effect. cannonMulti: true, - canBarrage: false + canBarrage: false, + pkActivityRating: 7, + pkBaseDeathChance: 9, + revsWeaponBoost: true, + wildySlayerCave: true }, { id: Monsters.Molanisk.id, @@ -912,7 +953,10 @@ export const vannakaMonsters: KillableMonster[] = [ healAmountNeeded: 17, attackStyleToUse: GearStat.AttackSlash, attackStylesUsed: [GearStat.AttackSlash], - canCannon: true + canCannon: true, + pkActivityRating: 4, + pkBaseDeathChance: 3, + revsWeaponBoost: true }, { id: Monsters.Bryophyta.id, @@ -1145,7 +1189,7 @@ export const vannakaMonsters: KillableMonster[] = [ timeToFinish: Time.Second * 15, table: Monsters.SpiritualMage, - wildy: false, + wildy: true, difficultyRating: 4, qpRequired: 0, @@ -1154,7 +1198,10 @@ export const vannakaMonsters: KillableMonster[] = [ }, healAmountNeeded: 27, attackStyleToUse: GearStat.AttackRanged, - attackStylesUsed: [GearStat.AttackMagic] + attackStylesUsed: [GearStat.AttackMagic], + pkActivityRating: 4, + pkBaseDeathChance: 6, + revsWeaponBoost: true }, { id: Monsters.SpiritualRanger.id, @@ -1163,7 +1210,7 @@ export const vannakaMonsters: KillableMonster[] = [ timeToFinish: Time.Second * 18, table: Monsters.SpiritualRanger, - wildy: false, + wildy: true, difficultyRating: 3, qpRequired: 0, @@ -1172,7 +1219,10 @@ export const vannakaMonsters: KillableMonster[] = [ }, healAmountNeeded: 25, attackStyleToUse: GearStat.AttackSlash, - attackStylesUsed: [GearStat.AttackRanged] + attackStylesUsed: [GearStat.AttackRanged], + pkActivityRating: 4, + pkBaseDeathChance: 6, + revsWeaponBoost: true }, { id: Monsters.SpiritualWarrior.id, @@ -1181,7 +1231,7 @@ export const vannakaMonsters: KillableMonster[] = [ timeToFinish: Time.Second * 19, table: Monsters.SpiritualWarrior, - wildy: false, + wildy: true, difficultyRating: 3, qpRequired: 0, @@ -1190,7 +1240,10 @@ export const vannakaMonsters: KillableMonster[] = [ }, healAmountNeeded: 26, attackStyleToUse: GearStat.AttackSlash, - attackStylesUsed: [GearStat.AttackSlash] + attackStylesUsed: [GearStat.AttackSlash], + pkActivityRating: 4, + pkBaseDeathChance: 6, + revsWeaponBoost: true }, { id: Monsters.TerrorDog.id, diff --git a/src/lib/minions/data/planks.ts b/src/lib/minions/data/planks.ts index 14c0ed1e261..aa0cc6c8c41 100644 --- a/src/lib/minions/data/planks.ts +++ b/src/lib/minions/data/planks.ts @@ -1,4 +1,4 @@ -import { Plankable } from '../../skilling/types'; +import type { Plankable } from '../../skilling/types'; import itemID from '../../util/itemID'; export const Planks: Plankable[] = [ diff --git a/src/lib/minions/data/plunder.ts b/src/lib/minions/data/plunder.ts index cb9529722d7..cc0b9480cac 100644 --- a/src/lib/minions/data/plunder.ts +++ b/src/lib/minions/data/plunder.ts @@ -3,9 +3,9 @@ import { Bank } from 'oldschooljs'; import LootTable from 'oldschooljs/dist/structures/LootTable'; import { SkillsEnum } from '../../skilling/types'; -import { ItemBank } from '../../types'; +import type { ItemBank } from '../../types'; import { roll, skillingPetDropRate } from '../../util'; -import { MUserClass } from './../../MUser'; +import type { MUserClass } from './../../MUser'; const Room1Table = new LootTable().add('Ivory Comb', 1, 3).add('Pottery scarab').add('Pottery statuette'); diff --git a/src/lib/minions/data/potions.ts b/src/lib/minions/data/potions.ts index b436ab1ed51..cda87774b38 100644 --- a/src/lib/minions/data/potions.ts +++ b/src/lib/minions/data/potions.ts @@ -1,4 +1,4 @@ -import resolveItems from '../../util/resolveItems'; +import { resolveItems } from 'oldschooljs/dist/util/util'; const Potions = [ { diff --git a/src/lib/minions/data/quests.ts b/src/lib/minions/data/quests.ts new file mode 100644 index 00000000000..0bc83656323 --- /dev/null +++ b/src/lib/minions/data/quests.ts @@ -0,0 +1,216 @@ +import { Time, sumArr } from 'e'; +import { Bank } from 'oldschooljs'; + +import type { Skills } from '../../types'; + +interface Quest { + id: QuestID; + qp: number; + name: string; + skillReqs?: Skills; + ironmanSkillReqs?: Skills; + qpReq?: number; + rewards?: Bank; + skillsRewards?: { + [skill: string]: number; + }; + combatLevelReq?: number; + prerequisitesQuests?: QuestID[]; + calcTime: (user: MUser) => number; +} + +export enum QuestID { + DesertTreasureII = 1, + ThePathOfGlouphrie = 2, + ChildrenOfTheSun = 3, + DefenderOfVarrock = 4, + TheRibbitingTaleOfALillyPadLabourDispute = 5, + PerilousMoons = 6, + AtFirstLight = 7, + TwilightsPromise = 8 +} + +export const quests: Quest[] = [ + { + id: QuestID.DesertTreasureII, + qp: 5, + name: 'Desert Treasure II - The Fallen Empire', + skillReqs: { + firemaking: 75, + magic: 75, + thieving: 70, + herblore: 62, + runecraft: 60, + construction: 60 + }, + combatLevelReq: 110, + qpReq: 150, + rewards: new Bank().add(28_409, 3).add('Ring of shadows').freeze(), + calcTime: (user: MUser) => { + let duration = Time.Hour * 3; + if (user.combatLevel < 100) { + duration += Time.Minute * 30; + } + if (user.combatLevel < 90) { + duration += Time.Minute * 40; + } + const percentOfBossCL = user.percentOfBossCLFinished(); + if (percentOfBossCL < 10) { + duration += Time.Minute * 20; + } else if (percentOfBossCL < 30) { + duration += Time.Minute * 10; + } else if (percentOfBossCL > 80) { + duration -= Time.Minute * 60; + } else if (percentOfBossCL > 50) { + duration -= Time.Minute * 30; + } + return duration; + } + }, + { + id: QuestID.ThePathOfGlouphrie, + qp: 2, + name: 'The Path of Glouphrie', + skillReqs: { + strength: 60, + slayer: 56, + thieving: 56, + ranged: 47, + agility: 45 + }, + ironmanSkillReqs: { + fletching: 59, + smithing: 59 + }, + combatLevelReq: 50, + qpReq: 10, + rewards: new Bank().add(28_587).add(28_588).add(28_589).add(28_590).freeze(), + calcTime: (user: MUser) => { + let duration = Time.Minute * 10; + if (user.combatLevel < 90) { + duration += Time.Minute * 5; + } + return duration; + } + }, + { + id: QuestID.ChildrenOfTheSun, + qp: 1, + name: 'Children of the Sun', + calcTime: () => { + const duration = Time.Minute * 3; + return duration; + } + }, + { + id: QuestID.DefenderOfVarrock, + qp: 2, + name: 'Defender of Varrock', + skillReqs: { + smithing: 55, + hunter: 52 + }, + combatLevelReq: 65, + qpReq: 20, + rewards: new Bank().add(28_820).freeze(), + skillsRewards: { + smithing: 15_000, + hunter: 15_000 + }, + calcTime: (user: MUser) => { + let duration = Time.Minute * 12; + if (user.combatLevel < 100) { + duration += Time.Minute * 8; + } + return duration; + } + }, + { + id: QuestID.TheRibbitingTaleOfALillyPadLabourDispute, + qp: 1, + name: 'The Ribbiting Tale of a Lily Pad Labour Dispute', + skillReqs: { + woodcutting: 15 + }, + prerequisitesQuests: [QuestID.ChildrenOfTheSun], + skillsRewards: { + woodcutting: 2000 + }, + calcTime: () => { + const duration = Time.Minute * 3; + return duration; + } + }, + { + id: QuestID.PerilousMoons, + qp: 2, + name: 'Perilous Moons', + skillReqs: { + slayer: 48, + hunter: 20, + fishing: 20, + runecraft: 20, + construction: 10 + }, + combatLevelReq: 75, + prerequisitesQuests: [QuestID.ChildrenOfTheSun, QuestID.TwilightsPromise], + skillsRewards: { + slayer: 40_000, + runecraft: 5000, + hunter: 5000, + fishing: 5000 + }, + calcTime: (user: MUser) => { + let duration = Time.Minute * 20; + if (user.combatLevel < 120) { + duration += Time.Minute * 5; + } + if (user.combatLevel < 100) { + duration += Time.Minute * 10; + } + return duration; + } + }, + { + id: QuestID.AtFirstLight, + qp: 1, + name: 'At First Light', + skillReqs: { + hunter: 46, + herblore: 30, + construction: 27 + }, + combatLevelReq: 75, + qpReq: 2, + prerequisitesQuests: [QuestID.ChildrenOfTheSun], + skillsRewards: { + hunter: 4500, + construction: 800, + herblore: 500 + }, + calcTime: () => { + const duration = Time.Minute * 6; + return duration; + } + }, + { + id: QuestID.TwilightsPromise, + qp: 1, + name: "Twilight's Promise", + skillsRewards: { + thieving: 3000 + }, + combatLevelReq: 40, + prerequisitesQuests: [QuestID.ChildrenOfTheSun], + calcTime: (user: MUser) => { + let duration = Time.Minute * 9; + if (user.combatLevel < 75) { + duration += Time.Minute * 5; + } + return duration; + } + } +]; + +export const MAX_GLOBAL_QP = 5000; +export const MAX_QP = MAX_GLOBAL_QP + sumArr(quests.map(i => i.qp)); diff --git a/src/lib/minions/data/sepulchre.ts b/src/lib/minions/data/sepulchre.ts index f61a728c570..ce1af1057f0 100644 --- a/src/lib/minions/data/sepulchre.ts +++ b/src/lib/minions/data/sepulchre.ts @@ -1,11 +1,11 @@ -import { randInt, roll, Time } from 'e'; +import { Time, randInt, roll } from 'e'; import { Bank } from 'oldschooljs'; import HerbDropTable from 'oldschooljs/dist/simulation/subtables/HerbDropTable'; import RareDropTable from 'oldschooljs/dist/simulation/subtables/RareDropTable'; import LootTable from 'oldschooljs/dist/structures/LootTable'; -import { ItemBank } from '../../types'; -import resolveItems from '../../util/resolveItems'; +import { resolveItems } from 'oldschooljs/dist/util/util'; +import type { ItemBank } from '../../types'; const LowTierCoffin = new LootTable() .add("Monk's robe top") diff --git a/src/lib/minions/data/stoneSpirits.ts b/src/lib/minions/data/stoneSpirits.ts index 15ed279ced9..ea8406f95f2 100644 --- a/src/lib/minions/data/stoneSpirits.ts +++ b/src/lib/minions/data/stoneSpirits.ts @@ -1,4 +1,4 @@ -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; import getOSItem from '../../util/getOSItem'; diff --git a/src/lib/minions/data/templeTrekking.ts b/src/lib/minions/data/templeTrekking.ts index 89542e5a654..f18c002e1a2 100644 --- a/src/lib/minions/data/templeTrekking.ts +++ b/src/lib/minions/data/templeTrekking.ts @@ -3,8 +3,8 @@ import { Bank } from 'oldschooljs'; import LootTable from 'oldschooljs/dist/structures/LootTable'; import { GearStat } from '../../gear/types'; -import { Skills } from '../../types'; -import { GearRequirements } from '../types'; +import type { Skills } from '../../types'; +import type { GearRequirements } from '../types'; interface TrekDifficulty { difficulty: string; diff --git a/src/lib/minions/farming/types.ts b/src/lib/minions/farming/types.ts index 778a9072e0d..e777731a63d 100644 --- a/src/lib/minions/farming/types.ts +++ b/src/lib/minions/farming/types.ts @@ -1,7 +1,7 @@ -import { CropUpgradeType } from '@prisma/client'; +import type { CropUpgradeType } from '@prisma/client'; -import { Plant } from '../../skilling/types'; -import { FarmingPatchName } from '../../util/farmingHelpers'; +import type { Plant } from '../../skilling/types'; +import type { FarmingPatchName } from '../../util/farmingHelpers'; export interface IPatchData { lastPlanted: string | null; diff --git a/src/lib/minions/functions/addSkillingClueToLoot.ts b/src/lib/minions/functions/addSkillingClueToLoot.ts index 771978f63d4..dbc12da2843 100644 --- a/src/lib/minions/functions/addSkillingClueToLoot.ts +++ b/src/lib/minions/functions/addSkillingClueToLoot.ts @@ -1,5 +1,5 @@ import { percentChance, sumArr } from 'e'; -import { Bank } from 'oldschooljs'; +import type { Bank } from 'oldschooljs'; import { birdsNestID, @@ -69,7 +69,7 @@ export default function addSkillingClueToLoot( } if (!roll(chance)) continue; - let nextTier = false; + const nextTier = false; let gotClue = false; let clueRoll = randFloat(0, cluesTotalWeight); for (const clue of clues) { diff --git a/src/lib/minions/functions/announceLoot.ts b/src/lib/minions/functions/announceLoot.ts index eed78964c7b..f444d525726 100644 --- a/src/lib/minions/functions/announceLoot.ts +++ b/src/lib/minions/functions/announceLoot.ts @@ -1,7 +1,7 @@ -import { Bank } from 'oldschooljs'; +import type { Bank } from 'oldschooljs'; +import type { ArrayItemsResolved } from 'oldschooljs/dist/util/util'; import { Events } from '../../constants'; -import { ArrayItemsResolved } from '../../types'; import { minionName } from '../../util/minionUtils'; import { effectiveMonsters } from '../data/killableMonsters'; @@ -19,21 +19,21 @@ export default async function announceLoot({ team?: { leader: MUser; lootRecipient: MUser; size: number }; }) { if (!_notifyDrops) return; - const notifyDrops = _notifyDrops.flat(Infinity); - const kc = await user.getKC(monsterID); + const notifyDrops = _notifyDrops.flat(Number.POSITIVE_INFINITY); const itemsToAnnounce = loot.clone().filter(i => notifyDrops.includes(i.id)); if (itemsToAnnounce.length > 0) { let notif = ''; if (team && team.size > 1) { notif = `In ${team.leader.badgedUsername}'s party of ${team.size} minions killing ${ - effectiveMonsters.find(m => m.id === monsterID)!.name + effectiveMonsters.find(m => m.id === monsterID)?.name }, **${team.lootRecipient.badgedUsername}** just received **${itemsToAnnounce}**!`; } else { + const kc = await user.getKC(monsterID); notif = `**${user.badgedUsername}'s** minion, ${minionName( user )}, just received **${itemsToAnnounce}**, their ${ - effectiveMonsters.find(m => m.id === monsterID)!.name + effectiveMonsters.find(m => m.id === monsterID)?.name } KC is ${kc.toLocaleString()}!`; } diff --git a/src/lib/minions/functions/autoFarm.ts b/src/lib/minions/functions/autoFarm.ts index 85c393cd8a1..fe5bac9c328 100644 --- a/src/lib/minions/functions/autoFarm.ts +++ b/src/lib/minions/functions/autoFarm.ts @@ -3,8 +3,8 @@ import { SkillsEnum } from 'oldschooljs/dist/constants'; import { farmingPlantCommand } from '../../../mahoji/lib/abstracted_commands/farmingCommand'; import { plants } from '../../skilling/skills/farming'; -import { IPatchDataDetailed } from '../farming/types'; -import { Plant } from './../../skilling/types'; +import type { IPatchDataDetailed } from '../farming/types'; +import type { Plant } from './../../skilling/types'; import { allFarm, replant } from './autoFarmFilters'; export async function autoFarm(user: MUser, patchesDetailed: IPatchDataDetailed[], channelID: string) { @@ -41,7 +41,7 @@ export async function autoFarm(user: MUser, patchesDetailed: IPatchDataDetailed[ .sort((a, b) => b.level - a.level); if (autoFarmFilter === AutoFarmFilterEnum.AllFarm) { - canHarvest = elligible.find(p => patchesDetailed.find(_p => _p.patchName === p.seedType)!.ready); + canHarvest = elligible.find(p => patchesDetailed.find(_p => _p.patchName === p.seedType)?.ready); errorString = "There's no Farming crops that you have the requirements to plant, and nothing to harvest."; } if (autoFarmFilter === AutoFarmFilterEnum.Replant) { diff --git a/src/lib/minions/functions/autoFarmFilters.ts b/src/lib/minions/functions/autoFarmFilters.ts index 267e582e69e..46ceef3ecae 100644 --- a/src/lib/minions/functions/autoFarmFilters.ts +++ b/src/lib/minions/functions/autoFarmFilters.ts @@ -1,9 +1,9 @@ import { Bank } from 'oldschooljs'; -import { MUserClass } from '../../MUser'; +import type { MUserClass } from '../../MUser'; import { calcNumOfPatches } from '../../skilling/functions/calcsFarming'; -import { Plant } from '../../skilling/types'; -import { IPatchDataDetailed } from '../farming/types'; +import type { Plant } from '../../skilling/types'; +import type { IPatchDataDetailed } from '../farming/types'; export function replant( p: Plant, diff --git a/src/lib/minions/functions/blowpipeCommand.ts b/src/lib/minions/functions/blowpipeCommand.ts index c7c1fab3642..140608de2bc 100644 --- a/src/lib/minions/functions/blowpipeCommand.ts +++ b/src/lib/minions/functions/blowpipeCommand.ts @@ -1,8 +1,8 @@ -import { Prisma } from '@prisma/client'; +import type { Prisma } from '@prisma/client'; import { Bank } from 'oldschooljs'; import getOSItem, { getItem } from '../../util/getOSItem'; -import { BlowpipeData } from '../types'; +import type { BlowpipeData } from '../types'; const defaultBlowpipe: BlowpipeData = { scales: 0, @@ -66,7 +66,7 @@ Zulrah's scales: ${rawBlowpipeData.scales.toLocaleString()}x const item = rawBlowpipeData.dartID ? getOSItem(rawBlowpipeData.dartID) : null; if (item) { - str += `${item.name}'s: ${rawBlowpipeData.dartQuantity!.toLocaleString()}x`; + str += `${item.name}'s: ${rawBlowpipeData.dartQuantity?.toLocaleString()}x`; } return str; @@ -94,7 +94,7 @@ async function addCommand(user: MUser, itemName: string, quantity = 1) { const userBank = user.bank; - let itemsToRemove = new Bank(); + const itemsToRemove = new Bank(); if (!blowpipeDarts.includes(item) && item !== getOSItem("Zulrah's scales")) { return "You can only charge your blowpipe with darts and Zulrah's scales."; } @@ -114,7 +114,7 @@ async function addCommand(user: MUser, itemName: string, quantity = 1) { }'s in your Blowpipe, do \`/minion blowpipe remove_darts:true\` to remove them first.`; } - let currentData: BlowpipeData = { ...rawBlowpipeData }; + const currentData: BlowpipeData = { ...rawBlowpipeData }; validateBlowpipeData(currentData); currentData.scales += itemsToRemove.amount("Zulrah's scales"); @@ -172,7 +172,7 @@ async function unchargeCommand(user: MUser) { } const rawBlowpipeData = { ...user.blowpipe }; - let returnedBank = new Bank(); + const returnedBank = new Bank(); if (rawBlowpipeData.scales) { returnedBank.add("Zulrah's scales", rawBlowpipeData.scales); } diff --git a/src/lib/minions/functions/calculateMonsterFood.ts b/src/lib/minions/functions/calculateMonsterFood.ts index 9d83b1b4ce9..1d6fb6341fd 100644 --- a/src/lib/minions/functions/calculateMonsterFood.ts +++ b/src/lib/minions/functions/calculateMonsterFood.ts @@ -1,10 +1,11 @@ import { calcWhatPercent, reduceNumByPercent } from 'e'; import { inverseOfOffenceStat } from '../../gear/functions/inverseOfStat'; -import { GearSetupType, GearStat } from '../../gear/types'; +import type { GearSetupType } from '../../gear/types'; +import { GearStat } from '../../gear/types'; import { maxDefenceStats, maxOffenceStats } from '../../structures/Gear'; import { readableStatName } from '../../util/smallUtils'; -import { KillableMonster } from '../types'; +import type { KillableMonster } from '../types'; const { floor, max } = Math; diff --git a/src/lib/minions/functions/darkAltarCommand.ts b/src/lib/minions/functions/darkAltarCommand.ts index 90be38548a4..3609ff72e6a 100644 --- a/src/lib/minions/functions/darkAltarCommand.ts +++ b/src/lib/minions/functions/darkAltarCommand.ts @@ -1,11 +1,12 @@ -import { increaseNumByPercent, reduceNumByPercent, Time } from 'e'; +import { formatDuration, stringMatches } from '@oldschoolgg/toolkit'; +import { Time, increaseNumByPercent, reduceNumByPercent } from 'e'; import { SkillsEnum } from 'oldschooljs/dist/constants'; import { userHasGracefulEquipped } from '../../../mahoji/mahojiSettings'; import { KourendKebosDiary, userhasDiaryTier } from '../../diaries'; -import { inventionBoosts, InventionID, inventionItemBoost } from '../../invention/inventions'; -import { DarkAltarOptions } from '../../types/minions'; -import { formatDuration, hasSkillReqs, stringMatches } from '../../util'; +import { InventionID, inventionBoosts, inventionItemBoost } from '../../invention/inventions'; +import type { DarkAltarOptions } from '../../types/minions'; +import { hasSkillReqs } from '../../util'; import addSubTaskToActivityTask from '../../util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../util/calcMaxTripLength'; import getOSItem from '../../util/getOSItem'; diff --git a/src/lib/minions/functions/decantPotionFromBank.ts b/src/lib/minions/functions/decantPotionFromBank.ts index 1e531be5232..fbb3359dd2d 100644 --- a/src/lib/minions/functions/decantPotionFromBank.ts +++ b/src/lib/minions/functions/decantPotionFromBank.ts @@ -32,7 +32,7 @@ export default function decantPotionFromBank( for (let i = 0; i < potionToDecant.items.length; i++) { if (i === dose - 1) continue; - let qty = userBank.amount(potionToDecant.items[i]); + const qty = userBank.amount(potionToDecant.items[i]); if (qty > 0) { potionsToRemove.add(potionToDecant.items[i], qty); sumOfPots += qty; diff --git a/src/lib/minions/functions/degradeableItemsCommand.ts b/src/lib/minions/functions/degradeableItemsCommand.ts index ef6bcfdf460..79a2985eaff 100644 --- a/src/lib/minions/functions/degradeableItemsCommand.ts +++ b/src/lib/minions/functions/degradeableItemsCommand.ts @@ -1,5 +1,5 @@ -import { ChatInputCommandInteraction } from 'discord.js'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; +import type { CommandResponse } from '@oldschoolgg/toolkit'; +import type { ChatInputCommandInteraction } from 'discord.js'; import { Bank } from 'oldschooljs'; import { mahojiParseNumber } from '../../../mahoji/mahojiSettings'; @@ -36,7 +36,7 @@ ${degradeableItems const needConvert = item.convertOnCharge && item.unchargedItem; if (needConvert && !user.hasEquippedOrInBank(item.item.id) && !user.owns(item.unchargedItem!.id)) { - return `You don't own a ${item.item.name} or ${item.unchargedItem!.name}.`; + return `You don't own a ${item.item.name} or ${item.unchargedItem?.name}.`; } await handleMahojiConfirmation( @@ -48,13 +48,13 @@ ${degradeableItems if (needConvert && !user.hasEquippedOrInBank(item.item.id)) { if (!user.owns(item.unchargedItem!.id)) { - return `Your ${item.unchargedItem!.name} disappeared and cannot be charged`; + return `Your ${item.unchargedItem?.name} disappeared and cannot be charged`; } await user.transactItems({ filterLoot: false, collectionLog: true, itemsToAdd: new Bank().add(item.item.id), - itemsToRemove: new Bank().add(item.unchargedItem!.id).add(cost) + itemsToRemove: new Bank().add(item.unchargedItem?.id).add(cost) }); } else { await transactItems({ userID: user.id, itemsToRemove: cost }); diff --git a/src/lib/minions/functions/getUserBestGearFromBank.ts b/src/lib/minions/functions/getUserBestGearFromBank.ts index 47a0dd96510..bdc94119c5d 100644 --- a/src/lib/minions/functions/getUserBestGearFromBank.ts +++ b/src/lib/minions/functions/getUserBestGearFromBank.ts @@ -1,15 +1,16 @@ import { Bank } from 'oldschooljs'; -import { EquipmentSlot, Item } from 'oldschooljs/dist/meta/types'; +import type { EquipmentSlot, Item } from 'oldschooljs/dist/meta/types'; -import { GearSetupType, GearStat } from '../../gear/types'; -import { Gear } from '../../structures/Gear'; -import { Skills } from '../../types'; +import type { GearSetupType } from '../../gear/types'; +import { GearStat } from '../../gear/types'; +import type { Gear } from '../../structures/Gear'; +import type { Skills } from '../../types'; import { assert, skillsMeetRequirements } from '../../util'; import getOSItem from '../../util/getOSItem'; function getItemScore(item: Item) { return Object.values(item.equipment!).reduce( - (a, b) => (!isNaN(Number(a)) ? Number(a) : 0) + (!isNaN(Number(b)) ? Number(b) : 0), + (a, b) => (!Number.isNaN(Number(a)) ? Number(a) : 0) + (!Number.isNaN(Number(b)) ? Number(b) : 0), 0 ); } @@ -23,8 +24,8 @@ export default function getUserBestGearFromBank( extra: string | null = null ) { assert(Object.values(GearStat).includes(gearStat as any)); - let toRemoveFromGear: Bank = new Bank(); - let toRemoveFromBank: Bank = new Bank(); + const toRemoveFromGear: Bank = new Bank(); + const toRemoveFromBank: Bank = new Bank(); const gearToEquip = { ...userGear.raw() }; let score2h = 0; @@ -134,16 +135,16 @@ export default function getUserBestGearFromBank( // Removes weapon/shield or 2h, depending on what has the highest stats if ((!gearStatExtra && scoreWs > score2h) || (gearStatExtra && scoreWsExtra > score2hExtra)) { if (gearToEquip['2h']) { - toRemoveFromBank.remove(gearToEquip['2h']!.item, gearToEquip['2h']!.quantity); + toRemoveFromBank.remove(gearToEquip['2h']?.item, gearToEquip['2h']?.quantity); gearToEquip['2h'] = null; } } else { if (gearToEquip.weapon) { - toRemoveFromBank.remove(gearToEquip.weapon!.item, gearToEquip.weapon!.quantity); + toRemoveFromBank.remove(gearToEquip.weapon?.item, gearToEquip.weapon?.quantity); gearToEquip.weapon = null; } if (gearToEquip.shield) { - toRemoveFromBank.remove(gearToEquip.shield!.item, gearToEquip.shield!.quantity); + toRemoveFromBank.remove(gearToEquip.shield?.item, gearToEquip.shield?.quantity); gearToEquip.shield = null; } } diff --git a/src/lib/minions/functions/getUserFoodFromBank.ts b/src/lib/minions/functions/getUserFoodFromBank.ts index 2ceb3e51a87..0734550108a 100644 --- a/src/lib/minions/functions/getUserFoodFromBank.ts +++ b/src/lib/minions/functions/getUserFoodFromBank.ts @@ -29,7 +29,7 @@ export default function getUserFoodFromBank({ let userBank = user.bank; if (unavailableBank) userBank = userBank.clone().remove(unavailableBank); let totalHealingCalc = totalHealingNeeded; - let foodToRemove = new Bank(); + const foodToRemove = new Bank(); const key = raw ? 'raw' : 'id'; let sorted = [...Eatables.filter(e => (isWilderness ? true : !e.wildyOnly))] diff --git a/src/lib/minions/functions/hasEnoughFoodForMonster.ts b/src/lib/minions/functions/hasEnoughFoodForMonster.ts index c5e770f2c53..f5bf2567141 100644 --- a/src/lib/minions/functions/hasEnoughFoodForMonster.ts +++ b/src/lib/minions/functions/hasEnoughFoodForMonster.ts @@ -1,4 +1,4 @@ -import { KillableMonster } from '../types'; +import type { KillableMonster } from '../types'; import calculateMonsterFood from './calculateMonsterFood'; import getUserFoodFromBank from './getUserFoodFromBank'; diff --git a/src/lib/minions/functions/index.ts b/src/lib/minions/functions/index.ts index 92ce5bd5d3a..a2433b00070 100644 --- a/src/lib/minions/functions/index.ts +++ b/src/lib/minions/functions/index.ts @@ -1,6 +1,6 @@ -import { User } from '@prisma/client'; +import type { User } from '@prisma/client'; import { Monsters } from 'oldschooljs'; -import Monster from 'oldschooljs/dist/structures/Monster'; +import type Monster from 'oldschooljs/dist/structures/Monster'; import { NIGHTMARES_HP } from '../../constants'; import { NexMonster } from '../../nex'; @@ -11,15 +11,15 @@ import killableMonsters from '../data/killableMonsters'; import { Ignecarus } from '../data/killableMonsters/custom/bosses/Ignecarus'; import { KalphiteKingMonster } from '../data/killableMonsters/custom/bosses/KalphiteKing'; import KingGoldemar from '../data/killableMonsters/custom/bosses/KingGoldemar'; -import { Naxxus, NAXXUS_HP } from '../data/killableMonsters/custom/bosses/Naxxus'; +import { NAXXUS_HP, Naxxus } from '../data/killableMonsters/custom/bosses/Naxxus'; import { VasaMagus } from '../data/killableMonsters/custom/bosses/VasaMagus'; import { BSOMonsters } from '../data/killableMonsters/custom/customMonsters'; -import { AddMonsterXpParams, KillableMonster, ResolveAttackStylesParams } from '../types'; +import type { AddMonsterXpParams, KillableMonster, ResolveAttackStylesParams } from '../types'; export { default as calculateMonsterFood } from './calculateMonsterFood'; export { default as reducedTimeForGroup } from './reducedTimeForGroup'; -export const attackStylesArr = [ +const attackStylesArr = [ SkillsEnum.Attack, SkillsEnum.Strength, SkillsEnum.Defence, @@ -30,7 +30,7 @@ export type AttackStyles = (typeof attackStylesArr)[number]; const miscHpMap: Record = { 3127: 250, - 46_274: 5000, + 46274: 5000, 9415: NIGHTMARES_HP, [KingGoldemar.id]: 10_000, [VasaMagus.id]: 3900, @@ -84,7 +84,7 @@ export function resolveAttackStyles( // Automatically use magic if barrage/burst is chosen if ( params.boostMethod && - (params.boostMethod === 'barrage' || params.boostMethod === 'burst') && + (params.boostMethod.includes('barrage') || params.boostMethod.includes('burst')) && !attackStyles.includes(SkillsEnum.Magic) ) { if (attackStyles.includes(SkillsEnum.Defence)) { @@ -97,7 +97,7 @@ export function resolveAttackStyles( } export async function addMonsterXP(user: MUser, params: AddMonsterXpParams) { - const boostMethod = params.burstOrBarrage ? 'barrage' : 'none'; + const boostMethod = params.burstOrBarrage ? ['barrage'] : ['none']; const [, osjsMon, attackStyles] = resolveAttackStyles(user, { monsterID: params.monsterID, @@ -109,8 +109,8 @@ export async function addMonsterXP(user: MUser, params: AddMonsterXpParams) { const cannonQty = params.cannonMulti ? randomVariation(Math.floor((xpPercentToCannonM / 100) * params.quantity), xpCannonVaryPercent) : params.usingCannon - ? randomVariation(Math.floor((xpPercentToCannon / 100) * params.quantity), xpCannonVaryPercent) - : 0; + ? randomVariation(Math.floor((xpPercentToCannon / 100) * params.quantity), xpCannonVaryPercent) + : 0; // Remove superiors from the regular count to be added separately. let normalQty = 0; @@ -129,27 +129,27 @@ export async function addMonsterXP(user: MUser, params: AddMonsterXpParams) { } // Calculate regular monster XP - if (monster && monster.customMonsterHP) { + if (monster?.customMonsterHP) { hp = monster.customMonsterHP; } else if (osjsMon?.data?.hitpoints) { hp = osjsMon.data.hitpoints; } - if (monster && monster.combatXpMultiplier) { + if (monster?.combatXpMultiplier) { xpMultiplier = monster.combatXpMultiplier; } // Calculate superior XP: let superiorSlayXp = 0; let superiorXp = 0; - if (superiorQty && osjsSuperior!.data!.hitpoints) { - superiorXp = 4 * superiorQty * osjsSuperior!.data!.hitpoints; - superiorSlayXp = superiorQty * osjsSuperior!.data!.slayerXP; + if (superiorQty && osjsSuperior?.data?.hitpoints) { + superiorXp = 4 * superiorQty * osjsSuperior?.data?.hitpoints; + superiorSlayXp = superiorQty * osjsSuperior?.data?.slayerXP; } const totalXP = hp * 4 * normalQty * xpMultiplier + superiorXp; const xpPerSkill = totalXP / attackStyles.length; - let res: string[] = []; + const res: string[] = []; for (const style of attackStyles) { res.push( diff --git a/src/lib/minions/functions/isImportantItemForMonster.ts b/src/lib/minions/functions/isImportantItemForMonster.ts index 418f3fd5d25..9478a0aae0d 100644 --- a/src/lib/minions/functions/isImportantItemForMonster.ts +++ b/src/lib/minions/functions/isImportantItemForMonster.ts @@ -1,4 +1,4 @@ -import { KillableMonster } from '../types'; +import type { KillableMonster } from '../types'; export default function isImportantItemForMonster(itemID: number, monster: KillableMonster) { if (!monster.uniques) return false; diff --git a/src/lib/minions/functions/lmsSimCommand.ts b/src/lib/minions/functions/lmsSimCommand.ts deleted file mode 100644 index 68f67f8bbb1..00000000000 --- a/src/lib/minions/functions/lmsSimCommand.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { Channel, Message, TextChannel } from 'discord.js'; -import { chunk, sleep } from 'e'; - -import LastManStandingUsage, { LMS_FINAL, LMS_PREP, LMS_ROUND } from '../../structures/LastManStandingUsage'; -import { channelIsSendable, cleanMentions } from '../../util'; - -const playing = new Set(); - -function calculateMaxDeaths(game: LastManStandingGame) { - return game.prep // have 0 deaths during the preparation phase - ? 0 - : // For 16 people, 5 die, 36 -> 7, and so on keeps the game interesting. - game.contestants.size >= 16 - ? Math.ceil(Math.sqrt(game.contestants.size) + 1) - : // If there are more than 7 contestants, proceed to kill them in 4s. - game.contestants.size > 7 - ? 4 - : // If there are more than 3 contestants, eliminate 2, else 1 (3 -> 2, 2 -> 1) - game.contestants.size > 3 - ? 2 - : 1; -} - -function shuffle(contestants: string[]) { - let m = contestants.length; - while (m) { - const i = Math.floor(Math.random() * m--); - [contestants[m], contestants[i]] = [contestants[i], contestants[m]]; - } - return new Set(contestants); -} - -function makeResultEvents(game: LastManStandingGame, events: readonly LastManStandingUsage[]) { - const results = [] as string[]; - const deaths = [] as string[]; - let maxDeaths = calculateMaxDeaths(game); - - const round = new Set([...game.contestants]); - for (const contestant of game.contestants) { - // If the player already had its round, skip - if (!round.has(contestant)) continue; - - // Pick a valid event - const event = pick(events, round.size, maxDeaths); - - // Pick the contestants - const pickedcontestants = pickcontestants(contestant, round, event.contestants); - - // Delete all the picked contestants from this round - for (const picked of pickedcontestants) { - round.delete(picked); - } - - // Kill all the unfortunate contestants - for (const death of event.deaths) { - game.contestants.delete(pickedcontestants[death]); - deaths.push(pickedcontestants[death]); - maxDeaths--; - } - - // Push the result of this match - results.push(event.display(...pickedcontestants)); - } - - return { results, deaths }; -} - -function buildTexts(game: LastManStandingGame, results: string[], deaths: string[]) { - const header = game.prep ? 'Preparation' : game.final ? `Finals, Round: ${game.round}` : `Round: ${game.round}`; - const death = - deaths.length > 0 - ? `${`**${deaths.length} new gravestone${ - deaths.length === 1 ? ' litters' : 's litter' - } the battlefield.**`}\n\n${deaths.map(d => `- ${d}`).join('\n')}` - : ''; - const panels = chunk(results, 5); - - const texts = panels.map( - panel => `**Last Man Standing ${header}:**\n\n${panel.map(text => `- ${text}`).join('\n')}` - ); - if (deaths.length > 0) texts.push(`${death}`); - return texts; -} - -function pick(events: readonly LastManStandingUsage[], contestants: number, maxDeaths: number) { - events = events.filter(event => event.contestants <= contestants && event.deaths.size <= maxDeaths); - return events[Math.floor(Math.random() * events.length)]; -} - -function pickcontestants(contestant: string, round: Set, amount: number) { - if (amount === 0) return []; - if (amount === 1) return [contestant]; - const array = [...round]; - array.splice(array.indexOf(contestant), 1); - - let m = array.length; - while (m) { - const i = Math.floor(Math.random() * m--); - [array[m], array[i]] = [array[i], array[m]]; - } - array.unshift(contestant); - return array.slice(0, amount); -} - -export async function lmsSimCommand(channel: Channel | undefined, names?: string) { - if (!channel) return; - if (!(channel instanceof TextChannel)) return; - let filtered = new Set(); - const splitContestants = names ? cleanMentions(channel.guild!, names).split(',') : []; - // Autofill using authors from the last 100 messages, if none are given to the command - if (names === 'auto' || !names || splitContestants.length === 0) { - const messages = await channel.messages.fetch({ limit: 100 }); - - for (const { author } of messages.values()) { - const name = cleanMentions(channel.guild, author.username); - if (!filtered.has(name)) filtered.add(name); - } - } else { - filtered = new Set(splitContestants); - if (filtered.size !== splitContestants.length) { - return channel.send('I am sorry, but a user cannot play twice.'); - } - - if (filtered.size < 4) { - return channel.send( - 'Please specify atleast 4 players for Last Man Standing, like so: `+lms Alex, Kyra, Magna, Rick`, or type `+lms auto` to automatically pick people from the chat.' - ); - } - - if (filtered.size > 48) { - return channel.send('I am sorry but the amount of players can be no greater than 48.'); - } - } - - if (playing.has(channel.guildId)) { - return channel.send('There is a game in progress in this server already, try again when it finishes.'); - } - - playing.add(channel.guildId); - - let gameMessage: Message | null = null; - const game: LastManStandingGame = Object.seal({ - prep: true, - final: false, - contestants: shuffle([...filtered]), - round: 0 - }); - - while (game.contestants.size > 1) { - // If it's not prep, increase the round - if (!game.prep) ++game.round; - const events = game.prep ? LMS_PREP : game.final ? LMS_FINAL : LMS_ROUND; - - // Main logic of the game - const { results, deaths } = makeResultEvents(game, events); - const texts = buildTexts(game, results, deaths); - - // Ask for the user to proceed: - for (const text of texts) { - // If the channel is not postable, break: - if (!channelIsSendable(channel)) return; - - gameMessage = await channel.send(text); - await sleep(Math.max(gameMessage!.content.length / 20, 7) * 700); - - // Delete the previous message, and if stopped, send stop. - gameMessage?.delete(); - } - - if (game.prep) game.prep = false; - else if (game.contestants.size < 4) game.final = true; - } - - // The match finished with one remaining player - const winner = game.contestants.values().next().value; - playing.delete(channel.guildId); - return channel.send(`And the Last Man Standing is... **${winner}**!`); -} - -export interface LastManStandingGame { - prep: boolean; - final: boolean; - contestants: Set; - round: number; -} diff --git a/src/lib/minions/functions/reducedTimeForGroup.ts b/src/lib/minions/functions/reducedTimeForGroup.ts index 19a4275a74e..4628e61a52c 100644 --- a/src/lib/minions/functions/reducedTimeForGroup.ts +++ b/src/lib/minions/functions/reducedTimeForGroup.ts @@ -1,7 +1,7 @@ import { resolveAvailableItemBoosts } from '../../../mahoji/mahojiSettings'; import { calcPOHBoosts } from '../../poh'; -import { prisma } from '../../settings/prisma'; -import { KillableMonster } from '../types'; + +import type { KillableMonster } from '../types'; import reducedTimeFromKC from './reducedTimeFromKC'; export default async function reducedTimeForGroup( @@ -9,7 +9,7 @@ export default async function reducedTimeForGroup( monster: KillableMonster ): Promise<[number, string[]]> { let reductionMultiplier = 0; - let messages = []; + const messages = []; if (monster.name === 'Corporeal Beast') { for (let i = 0; i < users.length; i++) { @@ -33,7 +33,7 @@ export default async function reducedTimeForGroup( userItemBoost += boostAmount; } // 1 per user, i/15 for incentive to group (more people compounding i bonus), then add the users kc and item boost percent - let multiplier = 1 + i / 15 + userKcReduction / 100 + userItemBoost / 100; + const multiplier = 1 + i / 15 + userKcReduction / 100 + userItemBoost / 100; reductionMultiplier += multiplier; messages.push(`${multiplier.toFixed(2)}x bonus from ${user.usernameOrMention}`); } diff --git a/src/lib/minions/functions/reducedTimeFromKC.ts b/src/lib/minions/functions/reducedTimeFromKC.ts index eb772908eb5..cb055d193c3 100644 --- a/src/lib/minions/functions/reducedTimeFromKC.ts +++ b/src/lib/minions/functions/reducedTimeFromKC.ts @@ -1,6 +1,6 @@ import { Time } from 'e'; -import { KillableMonster } from '../types'; +import type { KillableMonster } from '../types'; const FIVE_HOURS = Time.Hour * 5; diff --git a/src/lib/minions/functions/removeFoodFromUser.ts b/src/lib/minions/functions/removeFoodFromUser.ts index 616a0d31b3f..4b813ae3ddf 100644 --- a/src/lib/minions/functions/removeFoodFromUser.ts +++ b/src/lib/minions/functions/removeFoodFromUser.ts @@ -1,11 +1,11 @@ -import { UserError } from '@oldschoolgg/toolkit/dist/lib/UserError'; +import { UserError } from '@oldschoolgg/toolkit'; import { objectEntries, reduceNumByPercent } from 'e'; -import { Bank } from 'oldschooljs'; +import type { Bank } from 'oldschooljs'; import { itemID } from 'oldschooljs/dist/util'; import { Emoji } from '../../constants'; import { Eatables } from '../../data/eatables'; -import { GearSetupType } from '../../gear/types'; +import type { GearSetupType } from '../../gear/types'; import { updateBankSetting } from '../../util/updateBankSetting'; import getUserFoodFromBank, { getRealHealAmount } from './getUserFoodFromBank'; @@ -74,8 +74,7 @@ export default async function removeFoodFromUser({ ); } else { await transactItems({ userID: user.id, itemsToRemove: foodToRemove }); - - updateBankSetting('economyStats_PVMCost', foodToRemove); + await updateBankSetting('economyStats_PVMCost', foodToRemove); return { foodRemoved: foodToRemove, diff --git a/src/lib/minions/functions/trainCommand.ts b/src/lib/minions/functions/trainCommand.ts index 3003ab33077..3991b0a67ed 100644 --- a/src/lib/minions/functions/trainCommand.ts +++ b/src/lib/minions/functions/trainCommand.ts @@ -2,7 +2,7 @@ import { toTitleCase } from '@oldschoolgg/toolkit'; import { uniqueArr } from 'e'; import { SkillsEnum } from 'oldschooljs/dist/constants'; -import { AttackStyles } from '.'; +import type { AttackStyles } from '.'; const validStyles: AttackStyles[] = [ SkillsEnum.Attack, @@ -29,7 +29,7 @@ export const allPossibleStyles: string[] = uniqueArr([ ...validStyles, ...validStyles .map(i => { - let styles = []; + const styles = []; for (const style of validStyles) { if (style === i) continue; if (invalidCombinations.some(t => t.includes(i) && t.includes(style))) continue; @@ -61,8 +61,8 @@ export async function trainCommand(user: MUser, _styles: string | undefined) { _styles === 'shared' ? [SkillsEnum.Attack, SkillsEnum.Strength, SkillsEnum.Defence] : isValidAttackStyle(_styles) - ? [_styles] - : parsed.filter(isValidAttackStyle); + ? [_styles] + : parsed.filter(isValidAttackStyle); for (const comb of invalidCombinations) { if (comb.every(i => styles.includes(i))) { diff --git a/src/lib/minions/functions/unequipAllCommand.ts b/src/lib/minions/functions/unequipAllCommand.ts index 393aa3fea0a..53a49b87f02 100644 --- a/src/lib/minions/functions/unequipAllCommand.ts +++ b/src/lib/minions/functions/unequipAllCommand.ts @@ -1,7 +1,8 @@ import { toTitleCase } from '@oldschoolgg/toolkit'; import { Bank } from 'oldschooljs'; -import { GearSetupType, GearSetupTypes } from '../../gear/types'; +import type { GearSetupType } from '../../gear/types'; +import { GearSetupTypes } from '../../gear/types'; import { defaultGear } from '../../structures/Gear'; export async function unEquipAllCommand( @@ -18,7 +19,7 @@ export async function unEquipAllCommand( } const currentEquippedGear = user.gear[gearType]; - let refund = new Bank(); + const refund = new Bank(); for (const val of Object.values(currentEquippedGear.raw())) { if (!val) continue; refund.add(val.item, val.quantity); diff --git a/src/lib/minions/types.ts b/src/lib/minions/types.ts index dce2ebbdca8..1bffbea1c10 100644 --- a/src/lib/minions/types.ts +++ b/src/lib/minions/types.ts @@ -1,21 +1,23 @@ -import { Image } from '@napi-rs/canvas'; -import { StoreBitfield } from '@oldschoolgg/toolkit'; -import { XpGainSource } from '@prisma/client'; -import { Bank, MonsterKillOptions } from 'oldschooljs'; -import SimpleMonster from 'oldschooljs/dist/structures/SimpleMonster'; - -import { QuestID } from '../../mahoji/lib/abstracted_commands/questCommand'; -import { ClueTier } from '../clues/clueTiers'; -import { BitField, PerkTier } from '../constants'; -import { Diary, DiaryTier } from '../diaries'; -import { GearSetupType, GearStat, OffenceGearStat } from '../gear/types'; -import { POHBoosts } from '../poh'; -import { LevelRequirements, SkillsEnum } from '../skilling/types'; -import { ArrayItemsResolved, ItemBank, Skills } from '../types'; -import { MonsterActivityTaskOptions } from '../types/minions'; -import { calculateSimpleMonsterDeathChance } from '../util'; -import { BSOMonsters } from './data/killableMonsters/custom/customMonsters'; -import { AttackStyles } from './functions'; +import type { Image } from '@napi-rs/canvas'; +import type { PerkTier, StoreBitfield } from '@oldschoolgg/toolkit'; +import type { GearSetupType, XpGainSource } from '@prisma/client'; +import type { Bank, MonsterKillOptions } from 'oldschooljs'; +import type { Item, ItemBank } from 'oldschooljs/dist/meta/types'; +import type SimpleMonster from 'oldschooljs/dist/structures/SimpleMonster'; + +import type { ClueTier } from '../clues/clueTiers'; +import type { BitField } from '../constants'; +import type { GearStat, OffenceGearStat } from '../gear'; +import type { POHBoosts } from '../poh'; +import type { MinigameName } from '../settings/minigames'; +import type { LevelRequirements, SkillsEnum } from '../skilling/types'; +import type { MUserStats } from '../structures/MUserStats'; +import type { ArrayItemsResolved, Skills } from '../types'; +import type { MonsterActivityTaskOptions } from '../types/minions'; +import type { calculateSimpleMonsterDeathChance } from '../util'; +import type { BSOMonsters } from './data/killableMonsters/custom/customMonsters'; +import type { QuestID } from './data/quests'; +import type { AttackStyles } from './functions'; export type BankBackground = { image: Image | null; @@ -71,6 +73,7 @@ export interface KillableMonster { existsInCatacombs?: boolean; qpRequired?: number; difficultyRating?: number; + revsWeaponBoost?: boolean; /** * An array of objects of ([key: itemID]: boostPercentage) boosts that apply to @@ -135,7 +138,8 @@ export interface KillableMonster { }[]; requiredQuests?: QuestID[]; deathProps?: Omit['0'], 'currentKC'>; - diaryRequirement?: [Diary, DiaryTier]; + diaryRequirement?: [DiaryID, DiaryTierName]; + wildySlayerCave?: boolean; requiredBitfield?: BitField; minimumFoodHealAmount?: number; @@ -185,7 +189,7 @@ export interface AddMonsterXpParams { export interface ResolveAttackStylesParams { monsterID: number | undefined; - boostMethod?: string; + boostMethod?: string[]; } export interface BlowpipeData { @@ -212,3 +216,33 @@ export const defaultMegaDuckLocation: Readonly = { export type Flags = Record; export type FlagMap = Map; export type ClueBank = Record; + +export const diaryTiers = ['easy', 'medium', 'hard', 'elite'] as const; +export type DiaryTierName = (typeof diaryTiers)[number]; + +export interface DiaryTier { + name: 'Easy' | 'Medium' | 'Hard' | 'Elite'; + items: Item[]; + skillReqs: Skills; + ownedItems?: number[]; + collectionLogReqs?: number[]; + minigameReqs?: Partial>; + lapsReqs?: Record; + qp?: number; + monsterScores?: Record; + customReq?: (user: MUser, summary: boolean, stats: MUserStats) => [true] | [false, string]; +} +export enum DiaryID { + WesternProvinces = 0, + Ardougne = 1, + Desert = 2, + Falador = 3, + Fremennik = 4, + Kandarin = 5, + Karamja = 6, + KourendKebos = 7, + LumbridgeDraynor = 8, + Morytania = 9, + Varrock = 10, + Wilderness = 11 +} diff --git a/src/lib/modals.ts b/src/lib/modals.ts index 5de83a38d60..6aae7d57d23 100644 --- a/src/lib/modals.ts +++ b/src/lib/modals.ts @@ -1,6 +1,4 @@ -import { ModalSubmitInteraction } from 'discord.js'; - -import { assert } from './util'; +import type { ModalSubmitInteraction } from 'discord.js'; interface ModalListener { customID: string; @@ -10,12 +8,6 @@ interface ModalListener { const modalListeners: ModalListener[] = []; -export function addModalListener(listener: ModalListener) { - modalListeners.push(listener); - modalListeners.sort((a, b) => a.expiration - b.expiration); - assert(modalListeners[0].expiration <= modalListeners[modalListeners.length - 1].expiration); -} - export async function modalInteractionHook(interaction: ModalSubmitInteraction) { const listener = modalListeners.find(i => i.customID === interaction.customId); if (!listener) return interaction.reply('Invalid modal.'); diff --git a/src/lib/monkeyRumble.ts b/src/lib/monkeyRumble.ts index 7984754d6e3..45c4f61307b 100644 --- a/src/lib/monkeyRumble.ts +++ b/src/lib/monkeyRumble.ts @@ -1,10 +1,10 @@ -import { Canvas, Image } from '@napi-rs/canvas'; +import fs from 'node:fs/promises'; +import { Canvas, type Image, loadImage } from '@napi-rs/canvas'; import { toTitleCase } from '@oldschoolgg/toolkit'; import { randArrItem, randInt, roll } from 'e'; -import fs from 'fs/promises'; -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; -import { canvasImageFromBuffer, printWrappedText } from './util/canvasUtil'; +import { printWrappedText } from './util/canvasUtil'; import { textBoxFile } from './util/chatHeadImage'; import getOSItem from './util/getOSItem'; @@ -22,7 +22,7 @@ export const monkeyTiers: MonkeyTier[] = [ id: 1, name: 'Beginner', greegrees: [getOSItem('Beginner rumble greegree')], - image: fs.readFile('./src/lib/resources/images/mmmr/beginnermonkey.png').then(canvasImageFromBuffer), + image: loadImage('./src/lib/resources/images/mmmr/beginnermonkey.png'), strengthLevelReq: 80, gamesReq: 0 }, @@ -30,7 +30,7 @@ export const monkeyTiers: MonkeyTier[] = [ id: 2, name: 'Intermediate', greegrees: [getOSItem('Intermediate rumble greegree')], - image: fs.readFile('./src/lib/resources/images/mmmr/intermediatemonkey.png').then(canvasImageFromBuffer), + image: loadImage('./src/lib/resources/images/mmmr/intermediatemonkey.png'), strengthLevelReq: 90, gamesReq: 50 }, @@ -38,7 +38,7 @@ export const monkeyTiers: MonkeyTier[] = [ id: 3, name: 'Ninja', greegrees: [getOSItem('Ninja rumble greegree')], - image: fs.readFile('./src/lib/resources/images/mmmr/ninjamonkey.png').then(canvasImageFromBuffer), + image: loadImage('./src/lib/resources/images/mmmr/ninjamonkey.png'), strengthLevelReq: 100, gamesReq: 100 }, @@ -46,7 +46,7 @@ export const monkeyTiers: MonkeyTier[] = [ id: 4, name: 'Expert Ninja', greegrees: [getOSItem('Expert ninja rumble greegree')], - image: fs.readFile('./src/lib/resources/images/mmmr/expertninjamonkey.png').then(canvasImageFromBuffer), + image: loadImage('./src/lib/resources/images/mmmr/expertninjamonkey.png'), strengthLevelReq: 110, gamesReq: 200 }, @@ -54,7 +54,7 @@ export const monkeyTiers: MonkeyTier[] = [ id: 5, name: 'Elder', greegrees: [getOSItem('Elder rumble greegree'), getOSItem('Gorilla rumble greegree')], - image: fs.readFile('./src/lib/resources/images/mmmr/eldermonkey.png').then(canvasImageFromBuffer), + image: loadImage('./src/lib/resources/images/mmmr/eldermonkey.png'), strengthLevelReq: 120, gamesReq: 500 } @@ -165,7 +165,7 @@ export const fightingMessages = [ export const monkeyPhrases = ['Ah Ah!', 'Ah Uh Ah!', 'Ah!', 'Ook Ah Ook!', 'Ook Ah Uh!', 'Ook Ook!', 'Ook!', 'Ook.']; export const getMonkeyPhrase = () => { - let arr = []; + const arr = []; for (let i = 0; i < randInt(5, 10); i++) { arr.push(randArrItem(monkeyPhrases)); } @@ -185,9 +185,7 @@ export async function monkeyHeadImage({ monkey, content }: { monkey: Monkey; con const ctx = canvas.getContext('2d'); ctx.imageSmoothingEnabled = false; const bg = await textBoxFile; - const headImage = await canvasImageFromBuffer( - await [...normalHeads, ...specialHeads].find(h => h[1] === monkey.head)![0] - ); + const headImage = await loadImage(await [...normalHeads, ...specialHeads].find(h => h[1] === monkey.head)![0]); ctx.font = '16px RuneScape Quill 8'; ctx.drawImage(bg, 0, 0); ctx.drawImage(headImage, 28, bg.height / 2 - headImage.height / 2); diff --git a/src/lib/musicCape.ts b/src/lib/musicCape.ts index b88a9695f37..da18afda19b 100644 --- a/src/lib/musicCape.ts +++ b/src/lib/musicCape.ts @@ -2,32 +2,26 @@ import { activity_type_enum } from '@prisma/client'; import { objectEntries, partition } from 'e'; import { Bank, Monsters } from 'oldschooljs'; -import { getPOH } from '../mahoji/lib/abstracted_commands/pohCommand'; +import { resolveItems } from 'oldschooljs/dist/util/util'; import { MIMIC_MONSTER_ID, ZALCANO_ID } from './constants'; import { championScrolls } from './data/CollectionsExport'; import { NexMonster } from './nex'; import { RandomEvents } from './randomEvents'; -import { MinigameName, Minigames } from './settings/minigames'; -import { getUsersActivityCounts, prisma } from './settings/prisma'; -import { RequirementFailure, Requirements } from './structures/Requirements'; -import { itemNameFromID } from './util'; -import resolveItems from './util/resolveItems'; +import type { MinigameName } from './settings/minigames'; +import { Minigames } from './settings/minigames'; +import type { RequirementFailure } from './structures/Requirements'; +import { Requirements } from './structures/Requirements'; export const musicCapeRequirements = new Requirements() .add({ - name: 'Do 20 slayer tasks', - has: async ({ user }) => { - const count = await prisma.slayerTask.count({ - where: { - user_id: user.id - } - }); - if (count >= 20) { + name: 'Reach level 50 Slayer', + has: ({ user }) => { + if (user.skillsAsLevels.slayer >= 50) { return []; } return [ { - reason: 'You need to complete 20 slayer tasks.' + reason: 'You need level 50 slayer.' } ]; } @@ -109,14 +103,8 @@ export const musicCapeRequirements = new Requirements() } }) .add({ - name: 'Runecraft all runes atleast once', - has: async ({ user }) => { - const counts = await prisma.$queryRaw<{ rune_id: string }[]>`SELECT DISTINCT(data->>'runeID') AS rune_id -FROM activity -WHERE user_id = ${BigInt(user.id)} -AND type = 'Runecraft' -AND data->>'runeID' IS NOT NULL;`; - + name: 'Runecraft all runes at least once', + has: ({ uniqueRunesCrafted }) => { const runesToCheck = resolveItems([ 'Mind rune', 'Air rune', @@ -131,10 +119,7 @@ AND data->>'runeID' IS NOT NULL;`; 'Astral rune', 'Wrath rune' ]); - const notDoneRunes = runesToCheck - .filter(i => !counts.some(c => c.rune_id === i.toString())) - .map(i => itemNameFromID(i)!) - .map(s => s.split(' ')[0]); + const notDoneRunes = runesToCheck.filter(r => !uniqueRunesCrafted.includes(r)); if (notDoneRunes.length > 0) { return [ { @@ -148,7 +133,7 @@ AND data->>'runeID' IS NOT NULL;`; }) .add({ name: 'One of Every Activity', - has: async ({ user }) => { + has: ({ uniqueActivitiesDone }) => { const typesNotRequiredForMusicCape: activity_type_enum[] = [ activity_type_enum.Easter, activity_type_enum.HalloweenEvent, @@ -165,10 +150,8 @@ AND data->>'runeID' IS NOT NULL;`; activity_type_enum.Mortimer, activity_type_enum.BirthdayCollectIngredients ]; - const activityCounts = await getUsersActivityCounts(user); - const notDoneActivities = Object.values(activity_type_enum).filter( - type => !typesNotRequiredForMusicCape.includes(type) && activityCounts[type] < 1 + type => !typesNotRequiredForMusicCape.includes(type) && !uniqueActivitiesDone.includes(type) ); const [firstLot, secondLot] = partition(notDoneActivities, i => notDoneActivities.indexOf(i) < 5); @@ -188,8 +171,8 @@ AND data->>'runeID' IS NOT NULL;`; }) .add({ name: 'One of Every Minigame', - has: async ({ user }) => { - const results = []; + has: ({ minigames }) => { + const results: RequirementFailure[] = []; const typesNotRequiredForMusicCape: MinigameName[] = [ 'corrupted_gauntlet', 'raids_challenge_mode', @@ -198,9 +181,8 @@ AND data->>'runeID' IS NOT NULL;`; 'champions_challenge' ]; - const minigameScores = await user.fetchMinigames(); const minigamesNotDone = Minigames.filter( - i => !typesNotRequiredForMusicCape.includes(i.column) && minigameScores[i.column] < 1 + i => !typesNotRequiredForMusicCape.includes(i.column) && minigames[i.column] < 1 ).map(i => i.name); if (minigamesNotDone.length > 0) { @@ -214,7 +196,7 @@ AND data->>'runeID' IS NOT NULL;`; }) .add({ name: 'One Random Event with a unique music track', - has: async ({ stats }) => { + has: ({ stats }) => { const results: RequirementFailure[] = []; const eventBank = stats.randomEventCompletionsBank(); const uniqueTracks = RandomEvents.filter(i => i.uniqueMusic); @@ -230,8 +212,7 @@ AND data->>'runeID' IS NOT NULL;`; }) .add({ name: 'Must Build Something in PoH', - has: async ({ user }) => { - const poh = await getPOH(user.id); + has: ({ poh }) => { for (const [key, value] of objectEntries(poh)) { if (['user_id', 'background_id'].includes(key)) continue; if (value !== null) { @@ -243,7 +224,7 @@ AND data->>'runeID' IS NOT NULL;`; }) .add({ name: 'Champions Challenge', - has: async ({ user }) => { + has: ({ user }) => { for (const scroll of championScrolls) { if (user.cl.has(scroll)) return []; } diff --git a/src/lib/mysteryTrail.ts b/src/lib/mysteryTrail.ts index 8757ddf2d66..aa631af1188 100644 --- a/src/lib/mysteryTrail.ts +++ b/src/lib/mysteryTrail.ts @@ -1,8 +1,8 @@ import { Bank, Monsters } from 'oldschooljs'; -import { convertStoredActivityToFlatActivity, prisma } from './settings/prisma'; +import { convertStoredActivityToFlatActivity } from './settings/prisma'; import { getUsersCurrentSlayerInfo } from './slayer/slayerUtil'; -import { ActivityTaskData } from './types/minions'; +import type { ActivityTaskData } from './types/minions'; import getOSItem from './util/getOSItem'; import itemID from './util/itemID'; import resolveItems from './util/resolveItems'; @@ -28,8 +28,7 @@ const firstStep = { hint: `In Lumbridge's dawn, where bovine graze, Lay one to rest in the morning haze, In its yield, your path will blaze.`, - didPass: (data: ActivityTaskData) => - data.type === 'MonsterKilling' && data.monsterID === Monsters.Cow.id && data.quantity === 1 + didPass: (data: ActivityTaskData) => data.type === 'MonsterKilling' && data.mi === Monsters.Cow.id && data.q === 1 }; const finalStep = { @@ -134,7 +133,7 @@ export const mysteriousTrailTracks: Track[] = [ didPass: (data: ActivityTaskData) => { if ( data.type === 'MonsterKilling' && - [Monsters.MaleHamMember.id, Monsters.FemaleHamMember.id].includes(data.monsterID) + [Monsters.MaleHamMember.id, Monsters.FemaleHamMember.id].includes(data.mi) ) { return true; } @@ -239,7 +238,7 @@ export const mysteriousTrailTracks: Track[] = [ if ( task.slayerMaster?.name === 'Turael' && data.type === 'MonsterKilling' && - task.assignedTask?.monsters.includes(data.monsterID) + task.assignedTask?.monsters.includes(data.mi) ) { return true; } @@ -252,7 +251,7 @@ export const mysteriousTrailTracks: Track[] = [ didPass: (data: ActivityTaskData) => { if ( data.type === 'MonsterKilling' && - [Monsters.TrollGeneral.id, Monsters.MountainTroll.id].includes(data.monsterID) + [Monsters.TrollGeneral.id, Monsters.MountainTroll.id].includes(data.mi) ) { return true; } @@ -292,7 +291,7 @@ export const mysteriousTrailTracks: Track[] = [ { hint: 'Hear the echo of a crowned roar, in a place too dangerous to explore.', didPass: (data: ActivityTaskData) => - data.type === 'MonsterKilling' && data.monsterID === Monsters.KingBlackDragon.id + data.type === 'MonsterKilling' && data.mi === Monsters.KingBlackDragon.id }, finalStep ] @@ -338,7 +337,7 @@ export const mysteriousTrailTracks: Track[] = [ didPass: (data: ActivityTaskData) => { if ( data.type === 'MonsterKilling' && - [Monsters.TrollGeneral.id, Monsters.MountainTroll.id].includes(data.monsterID) + [Monsters.TrollGeneral.id, Monsters.MountainTroll.id].includes(data.mi) ) { return true; } diff --git a/src/lib/nex.ts b/src/lib/nex.ts index eefc86ed252..3b7ed75e46e 100644 --- a/src/lib/nex.ts +++ b/src/lib/nex.ts @@ -12,7 +12,7 @@ import { virtusOutfit } from './data/CollectionsExport'; import { GearStat } from './gear'; -import { KillableMonster } from './minions/types'; +import type { KillableMonster } from './minions/types'; import getOSItem from './util/getOSItem'; import itemID from './util/itemID'; import { makeKillTable } from './util/setCustomMonster'; diff --git a/src/lib/openables.ts b/src/lib/openables.ts index 31360b1a80d..c329bbcb037 100644 --- a/src/lib/openables.ts +++ b/src/lib/openables.ts @@ -1,8 +1,8 @@ import { formatOrdinal } from '@oldschoolgg/toolkit'; -import { randInt } from 'e'; +import { percentChance, randInt, roll } from 'e'; import { Bank, LootTable, Openables } from 'oldschooljs'; import { SkillsEnum } from 'oldschooljs/dist/constants'; -import { Item, OpenableOpenOptions } from 'oldschooljs/dist/meta/types'; +import type { Item, ItemBank, OpenableOpenOptions } from 'oldschooljs/dist/meta/types'; import { Mimic } from 'oldschooljs/dist/simulation/misc'; import BrimstoneChest, { BrimstoneChestOpenable } from 'oldschooljs/dist/simulation/openables/BrimstoneChest'; import { HallowedSackTable } from 'oldschooljs/dist/simulation/openables/HallowedSack'; @@ -14,8 +14,9 @@ import { ClueTiers } from './clues/clueTiers'; import { Emoji, Events, MIMIC_MONSTER_ID } from './constants'; import { clueHunterOutfit } from './data/CollectionsExport'; import { defaultFarmingContract } from './minions/farming'; -import { FarmingContract } from './minions/farming/types'; +import type { FarmingContract } from './minions/farming/types'; import { shadeChestOpenables } from './shadesKeys'; +import { nestTable } from './simulation/birdsNest'; import { BagFullOfGemsTable, BuildersSupplyCrateTable, @@ -24,8 +25,7 @@ import { SpoilsOfWarTable } from './simulation/misc'; import { openSeedPack } from './skilling/functions/calcFarmingContracts'; -import { ItemBank } from './types'; -import { itemID, percentChance, roll } from './util'; +import { itemID } from './util'; import getOSItem from './util/getOSItem'; import resolveItems from './util/resolveItems'; @@ -198,7 +198,7 @@ for (const clueTier of ClueTiers) { // and send a notification if they got one. const announcedLoot = loot.filter(i => clueItemsToNotifyOf.includes(i.id), false); if (gotMilestoneReward) { - announcedLoot.add(clueTier.milestoneReward!.itemReward); + announcedLoot.add(clueTier.milestoneReward?.itemReward); } if (announcedLoot.length > 0) { globalClient.emit( @@ -368,6 +368,14 @@ const osjsOpenables: UnifiedOpenable[] = [ output: Openables.NestBoxSeeds.table, allItems: Openables.NestBoxSeeds.table.allItems }, + { + name: 'Bird nest', + id: 5070, + openedItem: getOSItem(5070), + aliases: ['bird nest', 'nest'], + output: nestTable, + allItems: nestTable.allItems + }, { name: 'Ogre coffin', id: 4850, diff --git a/src/lib/paintColors.ts b/src/lib/paintColors.ts index 639f6c3edfe..f801f7a0719 100644 --- a/src/lib/paintColors.ts +++ b/src/lib/paintColors.ts @@ -1,6 +1,6 @@ -import { createCanvas, Image } from '@napi-rs/canvas'; +import { type Image, createCanvas } from '@napi-rs/canvas'; import { LootTable } from 'oldschooljs'; -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; import { setCustomItem } from './customItems/util'; import getOSItem from './util/getOSItem'; diff --git a/src/lib/party.ts b/src/lib/party.ts index 232cb2c6080..f1736a5974f 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -1,22 +1,20 @@ -/* eslint-disable prefer-promise-reject-errors */ -import { UserError } from '@oldschoolgg/toolkit/dist/lib/UserError'; -import { ButtonBuilder, ButtonStyle, ComponentType, InteractionCollector, TextChannel, userMention } from 'discord.js'; -import { debounce, noOp, Time } from 'e'; +import { makeComponents } from '@oldschoolgg/toolkit'; +import { UserError } from '@oldschoolgg/toolkit'; +import { TimerManager } from '@sapphire/timer-manager'; +import type { TextChannel } from 'discord.js'; +import { ButtonBuilder, ButtonStyle, ComponentType, InteractionCollector } from 'discord.js'; +import { Time, debounce, noOp } from 'e'; -import { production } from '../config'; import { BLACKLISTED_USERS } from './blacklists'; -import { SILENT_ERROR, usernameCache } from './constants'; -import { MakePartyOptions } from './types'; -import { formatDuration, makeComponents } from './util'; +import { SILENT_ERROR } from './constants'; +import type { MakePartyOptions } from './types'; +import { getUsername } from './util'; import { CACHED_ACTIVE_USER_IDS } from './util/cachedUserIDs'; const partyLockCache = new Set(); -if (production) { - setInterval(() => { - debugLog('Clearing partylockcache'); - partyLockCache.clear(); - }, Time.Minute * 20); -} +TimerManager.setInterval(() => { + partyLockCache.clear(); +}, Time.Minute * 20); const buttons = [ { @@ -40,32 +38,28 @@ const buttons = [ export async function setupParty(channel: TextChannel, leaderUser: MUser, options: MakePartyOptions): Promise { const usersWhoConfirmed: string[] = [options.leader.id]; let deleted = false; - const massTimeout = options.massTimeout ?? Time.Minute * 2; let massStarted = false; - function getMessageContent() { - const userText = - usersWhoConfirmed.length > 25 - ? `${usersWhoConfirmed.length} users have joined` - : usersWhoConfirmed.map(u => usernameCache.get(u) ?? userMention(u)).join(', '); - const allowedMentions = options.allowedMentions ?? { users: [] }; + async function getMessageContent() { return { - content: `${ - options.message - }\n\n**Users Joined:** ${userText}\n\nThis party will automatically depart in ${formatDuration( - massTimeout - )}, or if the leader clicks the start (start early) or stop button.`, + content: `${options.message}\n\n**Users Joined:** ${( + await Promise.all(usersWhoConfirmed.map(u => getUsername(u))) + ).join( + ', ' + )}\n\nThis party will automatically depart in 2 minutes, or if the leader clicks the start (start early) or stop button.`, components: makeComponents(buttons.map(i => i.button)), - allowedMentions + allowedMentions: { + users: [] + } }; } - const confirmMessage = await channel.send(getMessageContent()); + const confirmMessage = await channel.send(await getMessageContent()); // Debounce message edits to prevent spam. - const updateUsersIn = debounce(() => { + const updateUsersIn = debounce(async () => { if (deleted) return; - confirmMessage.edit(getMessageContent()); + confirmMessage.edit(await getMessageContent()); }, 500); const removeUser = (userID: string) => { @@ -81,7 +75,7 @@ export async function setupParty(channel: TextChannel, leaderUser: MUser, option new Promise(async (resolve, reject) => { let partyCancelled = false; const collector = new InteractionCollector(globalClient, { - time: massTimeout, + time: Time.Minute * 5, maxUsers: options.usersAllowed?.length ?? options.maxSize, dispose: true, channel, @@ -142,7 +136,7 @@ export async function setupParty(channel: TextChannel, leaderUser: MUser, option return; } - resolve(await Promise.all(usersWhoConfirmed.map(mUserFetch))); + resolve(await Promise.all(usersWhoConfirmed.map(id => mUserFetch(id)))); } collector.on('collect', async interaction => { @@ -230,7 +224,7 @@ export async function setupParty(channel: TextChannel, leaderUser: MUser, option for (const user of usersWhoConfirmed) { partyLockCache.delete(user); } - setTimeout(() => startTrip(), 250); + TimerManager.setTimeout(() => startTrip(), 250); }); }); diff --git a/src/lib/patreon.ts b/src/lib/patreon.ts deleted file mode 100644 index 7fe094b0c77..00000000000 --- a/src/lib/patreon.ts +++ /dev/null @@ -1,342 +0,0 @@ -import { Time } from 'e'; -import fetch from 'node-fetch'; - -import { production } from '../config'; -import { cacheBadges } from './badges'; -import { BadgesEnum, BitField, Channel, globalConfig, PatronTierID, PerkTier } from './constants'; -import { fetchSponsors, getUserIdFromGithubID } from './http/util'; -import { mahojiUserSettingsUpdate } from './MUser'; -import { getUsersPerkTier } from './perkTiers'; -import { roboChimpUserFetch } from './roboChimp'; -import { prisma } from './settings/prisma'; -import { Patron } from './types'; -import { logError } from './util/logError'; -import { sendToChannelID } from './util/webhook'; - -const patreonApiURL = new URL(`https://patreon.com/api/oauth2/v2/campaigns/${globalConfig.patreonCampaignID}/members`); - -patreonApiURL.search = new URLSearchParams([ - ['include', ['user', 'currently_entitled_tiers'].join(',')], - [ - 'fields[member]', - [ - 'pledge_relationship_start', - 'last_charge_date', - 'last_charge_status', - 'lifetime_support_cents', - 'patron_status' - ].join(',') - ], - ['fields[user]', ['social_connections'].join(',')], - ['page[count]', '1000'] -]).toString(); - -export const tiers: [PatronTierID, BitField][] = [ - [PatronTierID.Six, BitField.IsPatronTier6], - [PatronTierID.Five, BitField.IsPatronTier5], - [PatronTierID.Four, BitField.IsPatronTier4], - [PatronTierID.Three, BitField.IsPatronTier3], - [PatronTierID.Two, BitField.IsPatronTier2], - [PatronTierID.One, BitField.IsPatronTier1] -]; - -function bitFieldFromPerkTier(tier: PerkTier): BitField { - switch (tier) { - case PerkTier.Two: - return BitField.IsPatronTier1; - case PerkTier.Three: - return BitField.IsPatronTier2; - case PerkTier.Four: - return BitField.IsPatronTier3; - case PerkTier.Five: - return BitField.IsPatronTier4; - case PerkTier.Six: - return BitField.IsPatronTier5; - case PerkTier.Seven: - return BitField.IsPatronTier6; - default: { - throw new Error(`Unmatched bitFieldFromPerkTier ${tier}`); - } - } -} - -function perkTierFromBitfield(bit: BitField): PerkTier { - switch (bit) { - case BitField.IsPatronTier1: - return PerkTier.Two; - case BitField.IsPatronTier2: - return PerkTier.Three; - case BitField.IsPatronTier3: - return PerkTier.Four; - case BitField.IsPatronTier4: - return PerkTier.Five; - case BitField.IsPatronTier5: - return PerkTier.Six; - case BitField.IsPatronTier6: - return PerkTier.Seven; - default: { - throw new Error(`Unmatched perkTierFromBitfield ${bit}`); - } - } -} - -class PatreonTask { - public enabled = production; - - async validatePerks(userID: string, shouldHave: PerkTier): Promise { - const user = await prisma.user.findFirst({ where: { id: userID }, select: { bitfield: true } }); - if (!user) return null; - let perkTier: PerkTier | 0 | null = getUsersPerkTier([...user.bitfield]); - if (perkTier === 0 || perkTier === PerkTier.One) perkTier = null; - - if (!perkTier) { - await this.givePerks(userID, shouldHave); - return `Failed validation, giving ${shouldHave} to ${userID}`; - } - if (perkTier !== shouldHave) { - await this.changeTier(userID, perkTier, shouldHave); - return `Failed validation, wrong tier, changing to ${shouldHave} for ${userID}`; - } - return null; - } - - async changeTier(userID: string, from: PerkTier, to: PerkTier) { - const user = await prisma.user.findFirst({ - where: { id: userID }, - select: { bitfield: true } - }); - if (!user) return null; - - const userBitfield = user.bitfield; - - const bitFieldToRemove = bitFieldFromPerkTier(from); - const bitFieldToAdd = bitFieldFromPerkTier(to); - const newBitfield = [...userBitfield.filter(i => i !== bitFieldToRemove), bitFieldToAdd]; - - // Remove any/all the patron bits from this user. - try { - await mahojiUserSettingsUpdate(userID, { - bitfield: newBitfield - }); - } catch (_) {} - } - - async givePerks(userID: string, perkTier: PerkTier) { - const user = await prisma.user.findFirst({ - where: { id: userID }, - select: { bitfield: true, badges: true } - }); - if (!user) return null; - - const userBadges = user.badges; - - // If they have neither the limited time badge or normal badge, give them the normal one. - if (!userBadges.includes(BadgesEnum.Patron) && !userBadges.includes(BadgesEnum.LimitedPatron)) { - try { - await mahojiUserSettingsUpdate(userID, { - badges: { - push: BadgesEnum.Patron - } - }); - } catch (_) {} - } - - const userBitfield = user.bitfield; - - try { - let newField = [ - ...userBitfield.filter(number => !tiers.map(t => t[1]).includes(number)), - bitFieldFromPerkTier(perkTier) - ]; - - await mahojiUserSettingsUpdate(userID, { - bitfield: newField - }); - } catch (_) {} - } - - async removePerks(userID: string) { - const user = await prisma.user.findFirst({ - where: { id: userID }, - select: { bitfield: true, badges: true } - }); - if (!user) return null; - - const userBitfield = user.bitfield; - const userBadges = user.badges; - - // Remove any/all the patron bits from this user. - - await mahojiUserSettingsUpdate(userID, { - bitfield: userBitfield.filter(number => !tiers.map(t => t[1]).includes(number)) - }); - - // Remove patreon badge(s) - const patronBadges: number[] = [BadgesEnum.Patron, BadgesEnum.LimitedPatron]; - await mahojiUserSettingsUpdate(userID, { - badges: userBadges.filter(number => !patronBadges.includes(number)) - }); - } - - async syncGithub() { - let messages = []; - const sponsors = await fetchSponsors(); - for (const sponsor of sponsors) { - if (!sponsor.tier) continue; - const userID = await getUserIdFromGithubID(sponsor.githubID); - if (!userID) continue; - let res = await this.validatePerks(userID, sponsor.tier); - if (res) { - messages.push(res); - } - } - return messages; - } - - async run() { - debugLog('Starting patreon task...'); - const fetchedPatrons = await this.fetchPatrons(); - let result = []; - - for (const patron of fetchedPatrons) { - if (!patron.discordID) { - continue; - } - - // See if any duplicates are newer than this one: - const duplicateAccounts = fetchedPatrons.filter( - p => p.discordID === patron.discordID && p.patreonID !== patron.patreonID - ); - // We do this in 2 steps to avoid creating new Date objects for every patron entry; only when needed. - const newerAccount = duplicateAccounts.find( - p => new Date(p.lastChargeDate).getTime() > new Date(patron.lastChargeDate).getTime() - ); - - if (newerAccount) { - result.push( - `Discord[${patron.discordID}] Found Patron[${newerAccount.patreonID}] that's newer than the current Patron[${patron.patreonID}], so skipping.` - ); - continue; - } - - const user = await prisma.user.findFirst({ where: { id: patron.discordID }, select: { bitfield: true } }); - if (!user) continue; - - const roboChimpUser = await roboChimpUserFetch(patron.discordID); - - if (roboChimpUser.github_id) continue; - - const username = globalClient.users.cache.get(patron.discordID)?.username ?? ''; - const userIdentifier = `${username}(${patron.discordID}|${patron.patreonID})`; - - if (roboChimpUser.patreon_id !== patron.patreonID) { - try { - await roboChimpClient.user.update({ - where: { - id: BigInt(patron.discordID) - }, - data: { - patreon_id: patron.patreonID - } - }); - } catch (err: any) { - logError( - new Error( - `${err.message} Failed to set patreonID for Discord[${patron.discordID}] Patron[${patron.patreonID}]` - ), - { - id: patron.discordID, - patreon_id: patron.patreonID - } - ); - continue; - } - } - const userBitfield = user.bitfield; - if ( - [BitField.isModerator, BitField.isContributor, BitField.IsWikiContributor].some(bit => - userBitfield.includes(bit) - ) - ) { - continue; - } - - // If their last payment was more than a month ago, remove their status and continue. - if ( - Date.now() - new Date(patron.lastChargeDate).getTime() > Time.Day * 33 && - patron.patronStatus !== 'active_patron' - ) { - const perkTier = getUsersPerkTier([...userBitfield]); - if (perkTier < PerkTier.Two) continue; - result.push(`${userIdentifier} hasn't paid in over 1 month, so removing perks (T${perkTier + 1}).`); - this.removePerks(patron.discordID); - continue; - } - - for (let i = 0; i < tiers.length; i++) { - const [tierID, bitField] = tiers[i]; - - if (!patron.entitledTiers.includes(tierID)) continue; - if (userBitfield.includes(bitField)) break; - - result.push(`${userIdentifier} was given Tier${i + 1}.`); - await this.givePerks(patron.discordID, perkTierFromBitfield(bitField)); - break; - } - } - - const githubResult = await this.syncGithub(); - result.push('------------------ Github ------------------'); - result = result.concat(githubResult); - - if (production) { - sendToChannelID(Channel.PatronLogs, { - files: [{ attachment: Buffer.from(result.join('\n')), name: 'patron.txt' }] - }); - } else { - console.log(result.join('\n')); - } - - cacheBadges(); - debugLog('Finished running patreon task...'); - } - - async fetchPatrons(url?: string): Promise { - const users: Patron[] = []; - const result: any = await fetch(url ?? patreonApiURL.toString(), { - headers: { Authorization: `Bearer ${globalConfig.patreonToken}` } - }).then(res => res.json()); - - if (result.errors) { - logError(result.errors); - throw 'Failed to fetch patrons.'; - } - - for (const user of result.data) { - const socialConnections = result.included.find((i: any) => i.id === user.relationships.user.data.id) - .attributes.social_connections; - - const patron: Patron = { - patreonID: user.relationships.user.data.id, - discordID: socialConnections?.discord?.user_id, - entitledTiers: user.relationships.currently_entitled_tiers.data.map((i: any) => i.id), - lastChargeDate: user.attributes.last_charge_date, - lastChargeStatus: user.attributes.last_charge_status, - lifeTimeSupportCents: user.attributes.lifetime_support_cents, - patronStatus: user.attributes.patron_status, - pledgeRelationshipStart: user.attributes.pledge_relationship_start - }; - - users.push(patron); - } - - // If theres another page, start recursively adding all the pages into the array. - if (result.links?.next) { - users.push(...(await this.fetchPatrons(result.links.next))); - } - - return users; - } -} - -export const patreonTask = new PatreonTask(); diff --git a/src/lib/patreonUtils.ts b/src/lib/patreonUtils.ts new file mode 100644 index 00000000000..bcc964ce449 --- /dev/null +++ b/src/lib/patreonUtils.ts @@ -0,0 +1,54 @@ +import { BadgesEnum } from './constants'; +import { populateRoboChimpCache } from './perkTier'; + +export async function handleDeletedPatron(userID: string[]) { + const users = await prisma.user.findMany({ + where: { + id: { + in: userID + } + } + }); + + for (const user of users) { + if (user.badges.includes(BadgesEnum.Patron) || user.badges.includes(BadgesEnum.LimitedPatron)) { + await prisma.user.update({ + where: { + id: user.id + }, + data: { + badges: user.badges.filter(b => b !== BadgesEnum.Patron && b !== BadgesEnum.LimitedPatron) + } + }); + } + } + + await populateRoboChimpCache(); +} + +export async function handleEditPatron(userID: string[]) { + const users = await prisma.user.findMany({ + where: { + id: { + in: userID + } + } + }); + + for (const user of users) { + if (!user.badges.includes(BadgesEnum.Patron) && !user.badges.includes(BadgesEnum.LimitedPatron)) { + await prisma.user.update({ + where: { + id: user.id + }, + data: { + badges: { + push: BadgesEnum.Patron + } + } + }); + } + } + + await populateRoboChimpCache(); +} diff --git a/src/lib/perkTier.ts b/src/lib/perkTier.ts new file mode 100644 index 00000000000..a4ccaffc4b9 --- /dev/null +++ b/src/lib/perkTier.ts @@ -0,0 +1,44 @@ +import { pick } from 'lodash'; +import type { RobochimpUser } from './roboChimp'; + +const robochimpCachedKeys = [ + 'bits', + 'github_id', + 'patreon_id', + 'perk_tier', + 'user_group_id', + 'premium_balance_expiry_date', + 'premium_balance_tier' +] as const; +type CachedRoboChimpUser = Pick; + +export const roboChimpCache = new Map(); + +export async function populateRoboChimpCache() { + const users = await roboChimpClient.user.findMany({ + select: { + id: true, + bits: true, + github_id: true, + patreon_id: true, + perk_tier: true, + premium_balance_expiry_date: true, + premium_balance_tier: true, + user_group_id: true + }, + where: { + perk_tier: { + not: 0 + } + } + }); + for (const user of users) { + roboChimpCache.set(user.id.toString(), user); + } + debugLog(`Populated RoboChimp cache with ${users.length} users.`); +} + +export function cacheRoboChimpUser(user: RobochimpUser) { + if (user.perk_tier === 0) return; + roboChimpCache.set(user.id.toString(), pick(user, robochimpCachedKeys)); +} diff --git a/src/lib/perkTiers.ts b/src/lib/perkTiers.ts index 1a1b873ddb9..0ba31ddaa64 100644 --- a/src/lib/perkTiers.ts +++ b/src/lib/perkTiers.ts @@ -1,18 +1,6 @@ -import { User } from '@prisma/client'; -import { notEmpty } from 'e'; - import { SupportServer } from '../config'; import { BitField, PerkTier, Roles } from './constants'; -import { logError } from './util/logError'; - -export const perkTierCache = new Map(); - -const tier3ElligibleBits = [ - BitField.IsPatronTier3, - BitField.isContributor, - BitField.isModerator, - BitField.IsWikiContributor -]; +import { roboChimpCache } from './perkTier'; export const allPerkBitfields: BitField[] = [ BitField.IsPatronTier6, @@ -25,82 +13,56 @@ export const allPerkBitfields: BitField[] = [ BitField.BothBotsMaxedFreeTierOnePerks ]; -export function getUsersPerkTier( - userOrBitfield: MUser | User | BitField[], - noCheckOtherAccounts?: boolean -): PerkTier | 0 { - if (userOrBitfield instanceof GlobalMUserClass && userOrBitfield.user.premium_balance_tier !== null) { - const date = userOrBitfield.user.premium_balance_expiry_date; - if (date && Date.now() < date) { - return userOrBitfield.user.premium_balance_tier + 1; - } else if (date && Date.now() > date) { - userOrBitfield - .update({ - premium_balance_tier: null, - premium_balance_expiry_date: null - }) - .catch(e => { - logError(e, { user_id: userOrBitfield.id, message: 'Could not remove premium time' }); - }); - } +export function getUsersPerkTier(user: MUser): PerkTier | 0 { + if ([BitField.isModerator].some(bit => user.bitfield.includes(bit))) { + return PerkTier.Four; } - if (noCheckOtherAccounts !== true && userOrBitfield instanceof GlobalMUserClass) { - let main = userOrBitfield.user.main_account; - const allAccounts: string[] = [...userOrBitfield.user.ironman_alts, userOrBitfield.id]; - if (main) { - allAccounts.push(main); + const elligibleTiers = []; + if ( + user.bitfield.includes(BitField.IsPatronTier1) || + user.bitfield.includes(BitField.HasPermanentTierOne) || + user.bitfield.includes(BitField.BothBotsMaxedFreeTierOnePerks) + ) { + elligibleTiers.push(PerkTier.Two); + } else { + const guild = globalClient.guilds.cache.get(SupportServer); + const member = guild?.members.cache.get(user.id); + if (member && [Roles.Booster].some(roleID => member.roles.cache.has(roleID))) { + elligibleTiers.push(PerkTier.One); } + } - const allAccountTiers = allAccounts.map(id => perkTierCache.get(id)).filter(notEmpty); + if (user.bitfield.includes(BitField.IsPatronTier2) || user.bitfield.includes(BitField.HasPermanentTierOne)) { + elligibleTiers.push(PerkTier.Three); + } - const highestAccountTier = Math.max(0, ...allAccountTiers); - return highestAccountTier; + const roboChimpCached = roboChimpCache.get(user.id); + if (roboChimpCached) { + elligibleTiers.push(roboChimpCached.perk_tier); } - const bitfield = Array.isArray(userOrBitfield) ? userOrBitfield : userOrBitfield.bitfield; + const bitfield = user.bitfield; if (bitfield.includes(BitField.IsPatronTier6)) { - return PerkTier.Seven; + elligibleTiers.push(PerkTier.Seven); } if (bitfield.includes(BitField.IsPatronTier5)) { - return PerkTier.Six; + elligibleTiers.push(PerkTier.Six); } if (bitfield.includes(BitField.IsPatronTier4)) { - return PerkTier.Five; + elligibleTiers.push(PerkTier.Five); } - if (tier3ElligibleBits.some(bit => bitfield.includes(bit))) { - return PerkTier.Four; - } - - if (bitfield.includes(BitField.IsPatronTier2) || bitfield.includes(BitField.HasPermanentTierOne)) { - return PerkTier.Three; - } - - if ( - bitfield.includes(BitField.IsPatronTier1) || - bitfield.includes(BitField.HasPermanentTierOne) || - bitfield.includes(BitField.BothBotsMaxedFreeTierOnePerks) - ) { - return PerkTier.Two; + if (bitfield.includes(BitField.IsPatronTier3)) { + elligibleTiers.push(PerkTier.Four); } - if (userOrBitfield instanceof GlobalMUserClass) { - const guild = globalClient.guilds.cache.get(SupportServer); - const member = guild?.members.cache.get(userOrBitfield.id); - if (member && [Roles.Booster].some(roleID => member.roles.cache.has(roleID))) { - return PerkTier.One; - } + if (bitfield.includes(BitField.IsPatronTier2)) { + elligibleTiers.push(PerkTier.Three); } - return 0; -} - -export function syncPerkTierOfUser(user: MUser) { - const perkTier = getUsersPerkTier(user, true); - perkTierCache.set(user.id, perkTier); - return perkTier; + return Math.max(...elligibleTiers, 0); } diff --git a/src/lib/poh/index.ts b/src/lib/poh/index.ts index d96e5c8c7ba..3771203a461 100644 --- a/src/lib/poh/index.ts +++ b/src/lib/poh/index.ts @@ -1,8 +1,8 @@ -import { PlayerOwnedHouse } from '@prisma/client'; +import type { PlayerOwnedHouse } from '@prisma/client'; import { objectEntries } from 'e'; import { Bank } from 'oldschooljs'; -import { LevelRequirements } from '../skilling/types'; +import type { LevelRequirements } from '../skilling/types'; import { Amulets } from './objects/amulets'; import { DungeonDecorations } from './objects/dungeon_decorations'; import { GardenDecorations } from './objects/garden_decorations'; @@ -46,11 +46,11 @@ export const TOP_FLOOR_Y = 118; export const GROUND_FLOOR_Y = 236; export const DUNGEON_FLOOR_Y = 351; -export const FLOOR_HEIGHT = 112; +const FLOOR_HEIGHT = 112; const GARDEN_X = 587; const GARDEN_Y = 236; -export type PoHSlot = keyof Omit; +type PoHSlot = keyof Omit; export interface PoHObject { id: number; @@ -126,11 +126,11 @@ export const GroupedPohObjects = { GardenDecorations }; -export const PoHObjects = Object.values(GroupedPohObjects).flat(Infinity) as PoHObject[]; +export const PoHObjects = Object.values(GroupedPohObjects).flat(Number.POSITIVE_INFINITY) as PoHObject[]; export const getPOHObject = (idOrName: number | string) => { const key = typeof idOrName === 'string' ? 'name' : 'id'; - let obj = PoHObjects.find(i => i[key] === idOrName); + const obj = PoHObjects.find(i => i[key] === idOrName); if (!obj) throw new Error(`POH Object with id/name ${idOrName} doesn't exist.`); return obj; }; @@ -139,7 +139,7 @@ export type POHBoosts = Partial>>; export function calcPOHBoosts(poh: PlayerOwnedHouse, boosts: POHBoosts): [number, string[]] { let boost = 0; - let messages = []; + const messages = []; for (const [slot, objBoosts] of objectEntries(boosts)) { if (objBoosts === undefined) continue; for (const [name, boostPercent] of objectEntries(objBoosts)) { diff --git a/src/lib/poh/objects/amulets.ts b/src/lib/poh/objects/amulets.ts index 286d6d02806..84d433f07f4 100644 --- a/src/lib/poh/objects/amulets.ts +++ b/src/lib/poh/objects/amulets.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { PoHObject } from '..'; +import type { PoHObject } from '..'; export const Amulets: PoHObject[] = [ { diff --git a/src/lib/poh/objects/dungeon_decorations.ts b/src/lib/poh/objects/dungeon_decorations.ts index 429de1e37f6..16e9a223f8e 100644 --- a/src/lib/poh/objects/dungeon_decorations.ts +++ b/src/lib/poh/objects/dungeon_decorations.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { PoHObject } from '..'; +import type { PoHObject } from '..'; export const DungeonDecorations: PoHObject[] = [ { diff --git a/src/lib/poh/objects/garden_decorations.ts b/src/lib/poh/objects/garden_decorations.ts index 9a51abac11c..e68e9be483b 100644 --- a/src/lib/poh/objects/garden_decorations.ts +++ b/src/lib/poh/objects/garden_decorations.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { PoHObject } from '..'; +import type { PoHObject } from '..'; const cost = new Bank().add('Magic stone', 2); diff --git a/src/lib/poh/objects/guards.ts b/src/lib/poh/objects/guards.ts index 2359e9098e9..a5069759839 100644 --- a/src/lib/poh/objects/guards.ts +++ b/src/lib/poh/objects/guards.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { PoHObject } from '..'; +import type { PoHObject } from '..'; export const Guards: PoHObject[] = [ { diff --git a/src/lib/poh/objects/jewellery_boxes.ts b/src/lib/poh/objects/jewellery_boxes.ts index e50f979c43a..e43a655de85 100644 --- a/src/lib/poh/objects/jewellery_boxes.ts +++ b/src/lib/poh/objects/jewellery_boxes.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { PoHObject } from '..'; +import type { PoHObject } from '..'; export const JewelleryBoxes: PoHObject[] = [ { diff --git a/src/lib/poh/objects/mounted_capes.ts b/src/lib/poh/objects/mounted_capes.ts index 3ab9aad3fe8..30af8c36eba 100644 --- a/src/lib/poh/objects/mounted_capes.ts +++ b/src/lib/poh/objects/mounted_capes.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { PoHObject } from '..'; +import type { PoHObject } from '..'; const baseBank = () => new Bank().add('Marble block').add('Gold leaf'); diff --git a/src/lib/poh/objects/mounted_fish.ts b/src/lib/poh/objects/mounted_fish.ts index 0e13de32ea2..c199e38fbc1 100644 --- a/src/lib/poh/objects/mounted_fish.ts +++ b/src/lib/poh/objects/mounted_fish.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { PoHObject } from '..'; +import type { PoHObject } from '..'; export const MountedFish: PoHObject[] = [ { diff --git a/src/lib/poh/objects/mounted_heads.ts b/src/lib/poh/objects/mounted_heads.ts index 8ff45a1e53b..864c99ba684 100644 --- a/src/lib/poh/objects/mounted_heads.ts +++ b/src/lib/poh/objects/mounted_heads.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { PoHObject } from '..'; +import type { PoHObject } from '..'; const baseBank = () => new Bank().add('Marble block').add('Gold leaf'); diff --git a/src/lib/poh/objects/mounted_items.ts b/src/lib/poh/objects/mounted_items.ts index 3734da11f3e..c54992e787d 100644 --- a/src/lib/poh/objects/mounted_items.ts +++ b/src/lib/poh/objects/mounted_items.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { PoHObject } from '..'; +import type { PoHObject } from '..'; export const MountedItems: PoHObject[] = [ { diff --git a/src/lib/poh/objects/pools.ts b/src/lib/poh/objects/pools.ts index 1f218d035ea..83c7d53dd96 100644 --- a/src/lib/poh/objects/pools.ts +++ b/src/lib/poh/objects/pools.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { PoHObject } from '..'; +import type { PoHObject } from '..'; export const Pools: PoHObject[] = [ { diff --git a/src/lib/poh/objects/prayer_altars.ts b/src/lib/poh/objects/prayer_altars.ts index 822fa8323e6..d5341292651 100644 --- a/src/lib/poh/objects/prayer_altars.ts +++ b/src/lib/poh/objects/prayer_altars.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { PoHObject } from '..'; +import type { PoHObject } from '..'; export const PrayerAltars: PoHObject[] = [ { diff --git a/src/lib/poh/objects/prisons.ts b/src/lib/poh/objects/prisons.ts index cc081fd8063..dd0b0bb48a7 100644 --- a/src/lib/poh/objects/prisons.ts +++ b/src/lib/poh/objects/prisons.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { PoHObject } from '..'; +import type { PoHObject } from '..'; export const Prisons: PoHObject[] = [ { diff --git a/src/lib/poh/objects/spellbook_altars.ts b/src/lib/poh/objects/spellbook_altars.ts index 2615b321d36..f9828ae7830 100644 --- a/src/lib/poh/objects/spellbook_altars.ts +++ b/src/lib/poh/objects/spellbook_altars.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { PoHObject } from '..'; +import type { PoHObject } from '..'; export const SpellbookAltars: PoHObject[] = [ { diff --git a/src/lib/poh/objects/teleports.ts b/src/lib/poh/objects/teleports.ts index 7b5b45c76e6..c8deb952b83 100644 --- a/src/lib/poh/objects/teleports.ts +++ b/src/lib/poh/objects/teleports.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { PoHObject } from '..'; +import type { PoHObject } from '..'; export const Teleports: PoHObject[] = [ { diff --git a/src/lib/poh/objects/thrones.ts b/src/lib/poh/objects/thrones.ts index 7fd6ddbdac3..9bf808fa9bf 100644 --- a/src/lib/poh/objects/thrones.ts +++ b/src/lib/poh/objects/thrones.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { PoHObject } from '..'; +import type { PoHObject } from '..'; export const Thrones: PoHObject[] = [ { diff --git a/src/lib/poh/objects/torches.ts b/src/lib/poh/objects/torches.ts index 6e9a37ed657..3e612d6490b 100644 --- a/src/lib/poh/objects/torches.ts +++ b/src/lib/poh/objects/torches.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { PoHObject } from '..'; +import type { PoHObject } from '..'; export const Torches: PoHObject[] = [ { diff --git a/src/lib/pohImage.ts b/src/lib/pohImage.ts index 8748af5be10..542c98b9f55 100644 --- a/src/lib/pohImage.ts +++ b/src/lib/pohImage.ts @@ -1,12 +1,13 @@ -import { Canvas, Image, SKRSContext2D } from '@napi-rs/canvas'; -import { PlayerOwnedHouse } from '@prisma/client'; +import * as fs from 'node:fs'; +import path from 'node:path'; +import type { Image, SKRSContext2D } from '@napi-rs/canvas'; +import { Canvas, loadImage } from '@napi-rs/canvas'; import { objectEntries, randInt } from 'e'; -import * as fs from 'fs'; -import path from 'path'; import { DUNGEON_FLOOR_Y, GROUND_FLOOR_Y, HOUSE_WIDTH, Placeholders, TOP_FLOOR_Y } from './poh'; -import { canvasImageFromBuffer, loadAndCacheLocalImage } from './util/canvasUtil'; +import { loadAndCacheLocalImage } from './util/canvasUtil'; import { getActivityOfUser } from './util/minionIsBusy'; +import type { PlayerOwnedHouse } from '.prisma/client'; const CONSTRUCTION_IMG_DIR = './src/lib/poh/images'; const FOLDERS = [ @@ -33,7 +34,7 @@ class PoHImage { public imageCache: Map = new Map(); public bgImages: Image[] = []; initPromise: Promise | null = this.init(); - initFinished: boolean = false; + initFinished = false; async init() { this.bgImages.push(await loadAndCacheLocalImage('./src/lib/poh/images/bg_1.jpg')); @@ -42,9 +43,9 @@ class PoHImage { const currentPath = path.join(CONSTRUCTION_IMG_DIR, folder); const filesInDir = await fs.promises.readdir(currentPath); for (const fileName of filesInDir) { - const id = parseInt(path.parse(fileName).name); + const id = Number.parseInt(path.parse(fileName).name); const imageBuffer = await fs.promises.readFile(path.join(currentPath, `${id}.png`)); - const image = await canvasImageFromBuffer(imageBuffer); + const image = await loadImage(imageBuffer); this.imageCache.set(id, image); } @@ -61,7 +62,6 @@ class PoHImage { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(bgImage, 0, 0, bgImage.width, bgImage.height); - debugLog('Generating a POH image'); return [canvas, ctx]; } @@ -93,7 +93,7 @@ class PoHImage { const [placeholder, coordArr] = objects; for (const obj of coordArr) { const [x, y] = obj; - let id = poh[key] ?? placeholder; + const id = poh[key] ?? placeholder; const isMountedItem = key === 'mounted_item' && id !== 1111; if (isMountedItem) { const hasCustomItem = id !== 1112; diff --git a/src/lib/preStartup.ts b/src/lib/preStartup.ts new file mode 100644 index 00000000000..b47aefc1f01 --- /dev/null +++ b/src/lib/preStartup.ts @@ -0,0 +1,29 @@ +import { noOp } from 'e'; +import { syncCustomPrices } from '../mahoji/lib/events'; +import { syncActivityCache } from './Task'; +import { cacheBadges } from './badges'; +import { syncBlacklists } from './blacklists'; +import { GrandExchange } from './grandExchange'; +import { cacheGEPrices } from './marketPrices'; +import { populateRoboChimpCache } from './perkTier'; +import { RawSQL } from './rawSql'; +import { runStartupScripts } from './startupScripts'; +import { logWrapFn } from './util'; +import { syncActiveUserIDs } from './util/cachedUserIDs'; +import { syncDisabledCommands } from './util/syncDisabledCommands'; + +export const preStartup = logWrapFn('PreStartup', async () => { + await Promise.all([ + syncActiveUserIDs(), + syncActivityCache(), + runStartupScripts(), + syncDisabledCommands(), + syncBlacklists(), + syncCustomPrices(), + cacheBadges(), + GrandExchange.init(), + populateRoboChimpCache(), + cacheGEPrices(), + prisma.$queryRawUnsafe(RawSQL.updateAllUsersCLArrays()).then(noOp) + ]); +}); diff --git a/src/lib/premiumPatronTime.ts b/src/lib/premiumPatronTime.ts index 8c66f9d0a61..9780eca8360 100644 --- a/src/lib/premiumPatronTime.ts +++ b/src/lib/premiumPatronTime.ts @@ -1,8 +1,8 @@ -import { ChatInputCommandInteraction } from 'discord.js'; +import type { ChatInputCommandInteraction } from 'discord.js'; import { Time } from 'e'; +import { formatDuration } from '@oldschoolgg/toolkit'; import { handleMahojiConfirmation } from './util/handleMahojiConfirmation'; -import { formatDuration } from './util/smallUtils'; export async function premiumPatronTime( timeMs: number, diff --git a/src/lib/randomEvents.ts b/src/lib/randomEvents.ts index d75e3ced144..7abff77db06 100644 --- a/src/lib/randomEvents.ts +++ b/src/lib/randomEvents.ts @@ -1,14 +1,20 @@ import { activity_type_enum } from '@prisma/client'; -import { randArrItem, roll, Time } from 'e'; -import LRUCache from 'lru-cache'; +import { Time, randArrItem, roll } from 'e'; +import { LRUCache } from 'lru-cache'; import { Bank } from 'oldschooljs'; +import { + beekeeperOutfit, + camoOutfit, + lederhosenOutfit, + mimeOutfit, + zombieOutfit +} from 'oldschooljs/dist/data/itemConstants'; import LootTable from 'oldschooljs/dist/structures/LootTable'; import { userStatsBankUpdate } from '../mahoji/mahojiSettings'; import { BitField } from './constants'; -import resolveItems from './util/resolveItems'; -export interface RandomEvent { +interface RandomEvent { id: number; name: string; outfit?: number[]; @@ -18,28 +24,6 @@ export interface RandomEvent { const baguetteTable = new LootTable().add('Baguette', 1, 63).add('Stale baguette', 1, 1); -export const beekeeperOutfit = resolveItems([ - "Beekeeper's hat", - "Beekeeper's top", - "Beekeeper's legs", - "Beekeeper's gloves", - "Beekeeper's boots" -]); - -export const camoOutfit = resolveItems(['Camo helmet', 'Camo top', 'Camo bottoms']); - -export const lederhosenOutfit = resolveItems(['Lederhosen hat', 'Lederhosen top', 'Lederhosen shorts']); - -export const zombieOutfit = resolveItems([ - 'Zombie mask', - 'Zombie shirt', - 'Zombie trousers', - 'Zombie gloves', - 'Zombie boots' -]); - -export const mimeOutfit = resolveItems(['Mime mask', 'Mime top', 'Mime legs', 'Mime gloves', 'Mime boots']); - // Used by Mysterious Old man, Pillory, Rick Turpentine const randomEventTable = new LootTable() .add('Uncut sapphire', 1, 32) @@ -203,19 +187,19 @@ const cache = new LRUCache({ max: 500 }); const doesntGetRandomEvent: activity_type_enum[] = [activity_type_enum.TombsOfAmascut]; export async function triggerRandomEvent(user: MUser, type: activity_type_enum, duration: number, messages: string[]) { - if (doesntGetRandomEvent.includes(type)) return; + if (doesntGetRandomEvent.includes(type)) return {}; const minutes = Math.min(30, duration / Time.Minute); const randomEventChance = 60 - minutes; - if (!roll(randomEventChance)) return; + if (!roll(randomEventChance)) return {}; if (user.bitfield.includes(BitField.DisabledRandomEvents)) { - return; + return {}; } const prev = cache.get(user.id); // Max 1 event per 3h mins per user if (prev && Date.now() - prev < Time.Hour * 3) { - return; + return {}; } cache.set(user.id, Date.now()); @@ -234,7 +218,9 @@ export async function triggerRandomEvent(user: MUser, type: activity_type_enum, loot.add('Balloon cat'); messages.push('Found a cute Balloon cat!'); } - await transactItems({ userID: user.id, itemsToAdd: loot, collectionLog: true }); - await userStatsBankUpdate(user.id, 'random_event_completions_bank', new Bank().add(event.id)); + await userStatsBankUpdate(user, 'random_event_completions_bank', new Bank().add(event.id)); messages.push(`Did ${event.name} random event and got ${loot}`); + return { + itemsToAddWithCL: loot + }; } diff --git a/src/lib/rawSql.ts b/src/lib/rawSql.ts new file mode 100644 index 00000000000..c1b2cd2d979 --- /dev/null +++ b/src/lib/rawSql.ts @@ -0,0 +1,55 @@ +import { Prisma } from '@prisma/client'; +import type { ItemBank } from './types'; +import { logError } from './util/logError'; + +const u = Prisma.UserScalarFieldEnum; + +const RawBSOSQL = { + leaguesTaskLeaderboard: () => roboChimpClient.$queryRaw<{ id: string; tasks_completed: number }[]>`SELECT id::text, COALESCE(cardinality(leagues_completed_tasks_ids), 0) AS tasks_completed + FROM public.user + ORDER BY tasks_completed DESC + LIMIT 2;`, + openablesLeaderboard: (id: number) => + prisma.$queryRawUnsafe<{ id: string; score: number }[]>( + `SELECT user_id::text AS id, ("openable_scores"->>'${id}')::int AS score +FROM user_stats +WHERE "openable_scores"->>'${id}' IS NOT NULL +AND ("openable_scores"->>'${id}')::int > 50 +ORDER BY ("openable_scores"->>'${id}')::int DESC +LIMIT 50;` + ), + monkeysFoughtLeaderboard: () => + prisma.$queryRawUnsafe<{ id: string }[]>( + 'SELECT id FROM users WHERE monkeys_fought IS NOT NULL ORDER BY cardinality(monkeys_fought) DESC LIMIT 1;' + ), + inventionDisassemblyLeaderboard: () => + prisma.$queryRawUnsafe<{ id: string; uniques: number; disassembled_items_bank: ItemBank }[]>(`SELECT u.id, u.uniques, u.disassembled_items_bank FROM ( + SELECT (SELECT COUNT(*) FROM JSON_OBJECT_KEYS("disassembled_items_bank")) uniques, id, disassembled_items_bank FROM users WHERE "skills.invention" > 0 +) u +ORDER BY u.uniques DESC LIMIT 300;`) +}; + +export const RawSQL = { + updateAllUsersCLArrays: () => `UPDATE users +SET ${u.cl_array} = ( + SELECT (ARRAY(SELECT jsonb_object_keys("${u.collectionLogBank}")::int)) +) +WHERE last_command_date > now() - INTERVAL '1 week';`, + updateCLArray: (userID: string) => `UPDATE users +SET ${u.cl_array} = ( + SELECT (ARRAY(SELECT jsonb_object_keys("${u.collectionLogBank}")::int)) +) +WHERE ${u.id} = '${userID}';`, + ...RawBSOSQL +}; + +export async function loggedRawPrismaQuery(query: string): Promise { + try { + const result = await prisma.$queryRawUnsafe(query); + return result; + } catch (err) { + logError(err, { query: query.slice(0, 100) }); + } + + return null; +} diff --git a/src/lib/reclaimableItems.ts b/src/lib/reclaimableItems.ts index 057c34f9f99..e8c429d26ef 100644 --- a/src/lib/reclaimableItems.ts +++ b/src/lib/reclaimableItems.ts @@ -1,7 +1,5 @@ import { Bank } from 'oldschooljs'; -import { prisma } from './settings/prisma'; - export async function getReclaimableItemsOfUser(user: MUser) { const totalElligible = new Bank(); diff --git a/src/lib/resources/images/bank_backgrounds/508.jpg b/src/lib/resources/images/bank_backgrounds/508.jpg new file mode 100644 index 00000000000..f64ee1a5e89 Binary files /dev/null and b/src/lib/resources/images/bank_backgrounds/508.jpg differ diff --git a/src/lib/resources/images/minimus.png b/src/lib/resources/images/minimus.png new file mode 100644 index 00000000000..c2782c2acc5 Binary files /dev/null and b/src/lib/resources/images/minimus.png differ diff --git a/src/lib/roboChimp.ts b/src/lib/roboChimp.ts index 7f3e54f7a55..1c4670a75ba 100644 --- a/src/lib/roboChimp.ts +++ b/src/lib/roboChimp.ts @@ -1,29 +1,32 @@ import { formatOrdinal } from '@oldschoolgg/toolkit'; -import { PrismaClient, TriviaQuestion, User } from '@prisma/robochimp'; -import deepEqual from 'deep-equal'; +import type { TriviaQuestion, User } from '@prisma/robochimp'; import { calcWhatPercent, round, sumArr } from 'e'; +import deepEqual from 'fast-deep-equal'; +import type { Bank } from 'oldschooljs'; -import { BOT_TYPE, masteryKey } from './constants'; +import { BOT_TYPE, globalConfig, masteryKey } from './constants'; import { getTotalCl } from './data/Collections'; import { calculateMastery } from './mastery'; +import { cacheRoboChimpUser } from './perkTier'; import { MUserStats } from './structures/MUserStats'; -declare global { - const roboChimpClient: PrismaClient; -} -declare global { - namespace NodeJS { - interface Global { - roboChimpClient: PrismaClient; - } - } -} - export type RobochimpUser = User; -global.roboChimpClient = global.roboChimpClient || new PrismaClient(); - export async function getRandomTriviaQuestions(): Promise { + if (!globalConfig.isProduction) { + return [ + { + id: 1, + question: 'What is 1+1?', + answers: ['2'] + }, + { + id: 2, + question: 'What is 2+2?', + answers: ['4'] + } + ]; + } const random: TriviaQuestion[] = await roboChimpClient.$queryRaw`SELECT id, question, answers FROM trivia_question ORDER BY random() @@ -35,8 +38,29 @@ const clKey: keyof User = 'bso_cl_percent'; const levelKey: keyof User = 'bso_total_level'; const totalXPKey: keyof User = BOT_TYPE === 'OSB' ? 'osb_total_xp' : 'bso_total_xp'; -export async function roboChimpSyncData(user: MUser) { - const stats = await MUserStats.fromID(user.id); +export async function roboChimpSyncData(user: MUser, newCL?: Bank) { + const id = BigInt(user.id); + const newCLArray: number[] = Object.keys((newCL ?? user.cl).bank).map(i => Number(i)); + const clArrayUpdateObject = { + cl_array: newCLArray, + cl_array_length: newCLArray.length + } as const; + + const stats = new MUserStats( + await prisma.userStats.upsert({ + where: { + user_id: id + }, + create: { + user_id: id, + ...clArrayUpdateObject + }, + update: { + ...clArrayUpdateObject + } + }) + ); + const [totalClItems, clItems] = await getTotalCl(user, 'collection', stats); const clCompletionPercentage = round(calcWhatPercent(clItems, totalClItems), 2); const totalXP = sumArr(Object.values(user.skillsAsXP)); @@ -50,7 +74,7 @@ export async function roboChimpSyncData(user: MUser) { [masteryKey]: totalMastery } as const; - const newUser = await roboChimpClient.user.upsert({ + const newUser: RobochimpUser = await roboChimpClient.user.upsert({ where: { id: BigInt(user.id) }, @@ -60,6 +84,7 @@ export async function roboChimpSyncData(user: MUser) { ...updateObj } }); + cacheRoboChimpUser(newUser); if (!deepEqual(newUser.store_bitfield, user.user.store_bitfield)) { await user.update({ store_bitfield: newUser.store_bitfield }); @@ -67,8 +92,8 @@ export async function roboChimpSyncData(user: MUser) { return newUser; } -export async function roboChimpUserFetch(userID: string) { - const result = await roboChimpClient.user.upsert({ +export async function roboChimpUserFetch(userID: string): Promise { + const result: RobochimpUser = await roboChimpClient.user.upsert({ where: { id: BigInt(userID) }, @@ -78,6 +103,8 @@ export async function roboChimpUserFetch(userID: string) { update: {} }); + cacheRoboChimpUser(result); + return result; } diff --git a/src/lib/rolesTask.ts b/src/lib/rolesTask.ts index e90013dedb2..c9c328c7b53 100644 --- a/src/lib/rolesTask.ts +++ b/src/lib/rolesTask.ts @@ -1,524 +1,352 @@ -import { Prisma } from '@prisma/client'; -import { noOp, notEmpty } from 'e'; -import { Bank } from 'oldschooljs'; +import { noOp, notEmpty, uniqueArr } from 'e'; -import { production, SupportServer } from '../config'; -import { ClueTiers } from '../lib/clues/clueTiers'; -import { Roles, usernameCache } from '../lib/constants'; +import { SupportServer } from '../config'; +import { BadgesEnum, Roles } from '../lib/constants'; import { getCollectionItems, overallPlusItems } from '../lib/data/Collections'; import { Minigames } from '../lib/settings/minigames'; -import { prisma } from '../lib/settings/prisma'; -import Skills from '../lib/skilling/skills'; -import { assert, convertXPtoLVL, sanitizeBank } from '../lib/util'; -import { logError } from '../lib/util/logError'; -import { TeamLoot } from './simulation/TeamLoot'; -import { ItemBank } from './types'; -import { fetchTameCLLeaderboard } from './util/clLeaderboard'; -import resolveItems from './util/resolveItems'; -function addToUserMap(userMap: Record, id: string, reason: string) { - if (!userMap[id]) userMap[id] = []; - userMap[id].push(reason); -} +import { Prisma } from '@prisma/client'; +import { Bank } from 'oldschooljs'; +import PQueue from 'p-queue'; +import { partition } from 'remeda'; +import z from 'zod'; +import { + type CommandResponse, + Stopwatch, + convertXPtoLVL, + getUsernameSync, + resolveItems, + returnStringOrFile +} from '../lib/util'; +import { ClueTiers } from './clues/clueTiers'; +import { RawSQL, loggedRawPrismaQuery } from './rawSql'; +import { TeamLoot } from './simulation/TeamLoot'; +import { SkillsArray } from './skilling/types'; +import type { ItemBank } from './types'; +import { fetchMultipleCLLeaderboards, fetchTameCLLeaderboard } from './util/clLeaderboard'; +import { logError } from './util/logError'; + +const RoleResultSchema = z.object({ + roleID: z.string().min(17).max(19), + userID: z.string().min(17).max(19), + reason: z.string(), + badge: z.number().int().optional() +}); +type RoleResult = z.infer; const minigames = Minigames.map(game => game.column).filter(i => i !== 'tithe_farm'); -const collections = ['skilling', 'clues', 'minigames', 'raids', 'Dyed Items', 'slayer', 'other']; +const CLS_THAT_GET_ROLE = ['skilling', 'clues', 'minigames', 'other', 'overall']; -for (const cl of collections) { +for (const cl of CLS_THAT_GET_ROLE) { const items = getCollectionItems(cl); if (!items || items.length === 0) { throw new Error(`${cl} isn't a valid CL.`); } } -const mostSlayerPointsQuery = `SELECT id, 'Most Points' as desc -FROM users -WHERE "slayer.points" > 50 -ORDER BY "slayer.points" DESC -LIMIT 1;`; - -const longerSlayerTaskStreakQuery = `SELECT user_id::text as id, 'Longest Task Streak' as desc -FROM user_stats -WHERE "slayer_task_streak" > 20 -ORDER BY "slayer_task_streak" DESC -LIMIT 1;`; - -const mostSlayerTasksDoneQuery = `SELECT user_id::text as id, 'Most Tasks' as desc -FROM slayer_tasks -GROUP BY user_id -ORDER BY count(user_id)::int DESC -LIMIT 1;`; - -async function addRoles({ - users, - role, - badge, - userMap -}: { - users: string[]; - role: string; - badge: number | null; - userMap?: Record; -}): Promise { - if (process.env.TEST) return ''; - const g = globalClient.guilds.cache.get(SupportServer); - if (!g) throw new Error('No support guild'); - let added: string[] = []; - let removed: string[] = []; - let _role = await g.roles.fetch(role).catch(noOp); - if (!_role) return `\nCould not check ${role} role`; - for (const u of users.filter(notEmpty)) { - await g.members.fetch(u).catch(noOp); +async function topSkillers() { + const results: RoleResult[] = []; + + const [top200TotalXPUsers, ...top200ms] = await prisma.$transaction([ + prisma.$queryRawUnsafe( + `SELECT id, ${SkillsArray.map(s => `"skills.${s}"`)}, ${SkillsArray.map(s => `"skills.${s}"::bigint`).join( + ' + ' + )} as totalxp FROM users ORDER BY totalxp DESC LIMIT 200;` + ), + ...SkillsArray.map(s => { + const query = `SELECT id, "skills.${s}" AS xp, '${s}' AS skill FROM users ORDER BY xp DESC LIMIT 1;`; + return prisma.$queryRawUnsafe<{ + id: string; + xp: string; + skill: string; + }>(query); + }) + ]); + + for (const { id, skill } of top200ms.flat()) { + results.push({ + userID: id, + roleID: Roles.TopSkiller, + reason: `Rank 1 ${skill} XP`, + badge: BadgesEnum.TopSkiller + }); } - const roleName = _role.name!; - let noChangeUserDescriptions: string[] = []; - for (const mem of g.members.cache.values()) { - const mUser = await mUserFetch(mem.user.id); - if (mem.roles.cache.has(role) && !users.includes(mem.user.id)) { - if (production) { - await mem.roles.remove(role).catch(noOp); - } - - if (badge && mUser.user.badges.includes(badge)) { - await mUser.update({ - badges: mUser.user.badges.filter(i => i !== badge) - }); - } - removed.push(mem.user.username); - } - if (users.includes(mem.user.id)) { - noChangeUserDescriptions.push(`${mem.user.username}`); - if (production && !mem.roles.cache.has(role)) { - added.push(mem.user.username); - await mem.roles.add(role).catch(noOp); + const rankOneTotal = top200TotalXPUsers + .map((u: any) => { + let totalLevel = 0; + for (const skill of SkillsArray) { + totalLevel += convertXPtoLVL(Number(u[`skills.${skill}` as keyof any]) as any); } - if (badge && !mUser.user.badges.includes(badge)) { - await mUser.update({ - badges: { - push: badge - } - }); - } - } - } - let str = `\n**${roleName}**`; - if (added.length > 0) { - str += `\nAdded to: ${added.join(', ')}.`; - } - if (removed.length > 0) { - str += `\nRemoved from: ${removed.join(', ')}.`; - } - if (userMap) { - let userArr = []; - for (const [id, arr] of Object.entries(userMap)) { - let username = usernameCache.get(id) ?? 'Unknown'; - userArr.push(`${username}(${arr.join(', ')})`); - } - str += `\n${userArr.join(',')}`; - } - if (added.length > 0 || removed.length > 0) { - str += '\n'; - } else { - return `**No Changes:** ${str}`; - } - return str; -} + return { + id: u.id, + totalLevel + }; + }) + .sort((a: any, b: any) => b.totalLevel - a.totalLevel)[0]; -export async function runRolesTask() { - const skillVals = Object.values(Skills); + results.push({ + userID: rankOneTotal.id, + roleID: Roles.TopSkiller, + reason: 'Rank 1 Total Level', + badge: BadgesEnum.TopSkiller + }); - let results: string[] = []; - // eslint-disable-next-line @typescript-eslint/unbound-method - const q = async (str: string) => { - const result = await prisma.$queryRawUnsafe(str).catch(err => { - logError(`This query failed: ${str}`, err); - return []; - }); - return result; - }; - - // Top Skillers - async function topSkillers() { - const topSkillers = ( - await Promise.all([ - ...skillVals.map(s => - q< - { - id: string; - xp: string; - }[] - >(`SELECT id, "skills.${s.id}" as xp FROM users ORDER BY xp DESC LIMIT 1;`) - ), - q< - { - id: string; - }[] - >( - `SELECT id, ${skillVals.map(s => `"skills.${s.id}"`)}, ${skillVals - .map(s => `"skills.${s.id}"::bigint`) - .join(' + ')} as totalxp FROM users ORDER BY totalxp DESC LIMIT 1;` - ) - ]) - ).map(i => i[0]?.id); - - // Rank 1 Total Level - const rankOneTotal = ( - await q( - `SELECT id, ${skillVals.map(s => `"skills.${s.id}"`)}, ${skillVals - .map(s => `"skills.${s.id}"::bigint`) - .join(' + ')} as totalxp FROM users ORDER BY totalxp DESC LIMIT 200;` - ) - ) - .map((u: any) => { - let totalLevel = 0; - for (const skill of skillVals) { - totalLevel += convertXPtoLVL(Number(u[`skills.${skill.id}` as keyof any]) as any); - } - return { - id: u.id, - totalLevel - }; - }) - .sort((a: any, b: any) => b.totalLevel - a.totalLevel)[0]; - topSkillers.push(rankOneTotal.id); - - results.push(await addRoles({ users: topSkillers, role: Roles.TopSkiller, badge: 9 })); - } - - // Top Collectors - async function topCollector() { - const userMap = {}; - - function generateQuery(items: number[], ironmenOnly: boolean, limit: number) { - const t = ` -SELECT id, (cardinality(u.cl_keys) - u.inverse_length) as qty - FROM ( - SELECT ARRAY(SELECT * FROM JSONB_OBJECT_KEYS("collectionLogBank")) "cl_keys", - id, "collectionLogBank", - cardinality(ARRAY(SELECT * FROM JSONB_OBJECT_KEYS("collectionLogBank" - array[${items - .map(i => `'${i}'`) - .join(', ')}]))) "inverse_length" - FROM users - WHERE "collectionLogBank" ?| array[${items.map(i => `'${i}'`).join(', ')}] - ${ironmenOnly ? 'AND "minion.ironman" = true' : ''} - ) u - ORDER BY qty DESC - LIMIT ${limit}; -`; - - return t; - } + return results; +} - const topCollectors = ( - await Promise.all( - collections.map(async clName => { - const items = getCollectionItems(clName); - if (!items || items.length === 0) { - logError(`${clName} collection log doesnt exist`); - return []; - } - - function handleErr(): any[] { - logError(`Failed to select top collectors for ${clName}`); - return []; - } - - const [users, ironUsers] = await Promise.all([ - q(generateQuery(items, false, 1)) - .then(i => i.filter((i: any) => i.qty > 0) as any[]) - .catch(handleErr), - q(generateQuery(items, true, 1)) - .then(i => i.filter((i: any) => i.qty > 0) as any[]) - .catch(handleErr) - ]); - - let result = []; - const userID = users[0]?.id; - const ironmanID = ironUsers[0]?.id; - - if (userID) { - addToUserMap(userMap, userID, `Rank 1 ${clName} CL`); - result.push(userID); - } - if (ironmanID) { - addToUserMap(userMap, ironmanID, `Rank 1 Ironman ${clName} CL`); - result.push(ironmanID); - } - - return result; - }) - ) - ).flat(2); - - const topIronUsers = (await q(generateQuery(getCollectionItems('overall'), true, 3))).filter( - (i: any) => i.qty > 0 - ) as any[]; - for (let i = 0; i < topIronUsers.length; i++) { - const id = topIronUsers[i]?.id; - addToUserMap(userMap, id, `Rank ${i + 1} Ironman Collector`); - topCollectors.push(id); - } - const topNormieUsers = (await q(generateQuery(getCollectionItems('overall'), false, 3))).filter( - (i: any) => i.qty > 0 - ) as any[]; - for (let i = 0; i < topNormieUsers.length; i++) { - const id = topNormieUsers[i]?.id; - addToUserMap(userMap, id, `Rank ${i + 1} Collector`); - topCollectors.push(id); +async function topCollector() { + const results: RoleResult[] = []; + const rankOneInSpecifiedCLs = await fetchMultipleCLLeaderboards( + CLS_THAT_GET_ROLE.map(cl => { + const items = getCollectionItems(cl); + const base = { + items, + clName: cl, + resultLimit: cl === 'overall' ? 3 : 1 + } as const; + return [ + { ...base, ironmenOnly: true }, + { ...base, ironmenOnly: false } + ]; + }).flat(2) + ); + for (const { users, clName, ironmenOnly } of rankOneInSpecifiedCLs) { + for (const user of users) { + results.push({ + userID: user.id, + roleID: Roles.TopCollector, + reason: `Rank 1 ${ironmenOnly ? 'Iron' : 'Main'} ${clName}`, + badge: BadgesEnum.TopCollector + }); } - - results.push(await addRoles({ users: topCollectors, role: Roles.TopCollector, badge: 10, userMap })); } + return results; +} - // Top sacrificers - async function topSacrificers() { - const userMap = {}; - let topSacrificers: string[] = []; - const mostValue = await q('SELECT id FROM users ORDER BY "sacrificedValue" DESC LIMIT 3;'); - for (let i = 0; i < 3; i++) { - if (mostValue[i] !== undefined) { - topSacrificers.push(mostValue[i].id); - addToUserMap(userMap, mostValue[i].id, `Rank ${i + 1} Sacrifice Value`); - } - } - const mostValueIronman = await q( - 'SELECT id FROM users WHERE "minion.ironman" = true ORDER BY "sacrificedValue" DESC LIMIT 1;' - ); - topSacrificers.push(mostValueIronman[0].id); - addToUserMap(userMap, mostValueIronman[0].id, 'Rank 1 Ironman Sacrificed Value'); - - const mostUniques = await q(`SELECT u.id, u.sacbanklength FROM ( +async function topSacrificers() { + const results: RoleResult[] = []; + const users = await prisma.$transaction([ + prisma.$queryRawUnsafe<{ id: string; reason: string }[]>( + `SELECT id, 'Top 3' AS reason FROM users ORDER BY "sacrificedValue" DESC LIMIT 3;` + ), + prisma.$queryRawUnsafe<{ id: string; reason: string }[]>( + `SELECT id, 'Top Ironman' AS reason FROM users WHERE "minion.ironman" = true ORDER BY "sacrificedValue" DESC LIMIT 1;` + ), + prisma.$queryRawUnsafe<{ id: string; reason: string }[]>(`SELECT u.id, 'Top Uniques' AS reason FROM ( SELECT (SELECT COUNT(*)::int FROM JSONB_OBJECT_KEYS("sacrificed_bank")) sacbanklength, user_id::text as id FROM user_stats ) u -ORDER BY u.sacbanklength DESC LIMIT 1;`); - - const mostUniquesIron = await q(`SELECT u.id, u.sacbanklength FROM ( +ORDER BY u.sacbanklength DESC LIMIT 1;`), + prisma.$queryRawUnsafe<{ id: string; reason: string }[]>(`SELECT u.id, 'Top Ironman Uniques' AS reason FROM ( SELECT (SELECT COUNT(*)::int FROM JSONB_OBJECT_KEYS("sacrificed_bank")) sacbanklength, user_id::text as id FROM user_stats INNER JOIN users ON "user_stats"."user_id"::text = "users"."id" WHERE "users"."minion.ironman" = true ) u -ORDER BY u.sacbanklength DESC LIMIT 1;`); - topSacrificers.push(mostUniques[0].id); - addToUserMap(userMap, mostUniques[0].id, 'Most Uniques Sacrificed'); - topSacrificers.push(mostUniquesIron[0].id); - addToUserMap(userMap, mostUniquesIron[0].id, 'Most Ironman Uniques Sacrificed'); - - results.push(await addRoles({ users: topSacrificers, role: Roles.TopSacrificer, badge: 8, userMap })); +ORDER BY u.sacbanklength DESC LIMIT 1;`) + ]); + + for (const res of users.flat()) { + results.push({ + userID: res.id, + reason: res.reason, + roleID: Roles.TopSacrificer, + badge: BadgesEnum.TopSacrifice + }); } - // Top minigamers - async function topMinigamers() { - let topMinigamers = ( - await Promise.all( - minigames.map(m => - q( - `SELECT user_id, '${m}' as m + return results; +} + +async function topMinigamers() { + const results: RoleResult[] = []; + const topMinigamers = await prisma.$transaction( + minigames.map(m => + prisma.$queryRawUnsafe<{ id: string; minigame_name: string; qty: number }[]>( + `SELECT user_id::text AS id, '${m}' AS minigame_name FROM minigames ORDER BY ${m} DESC LIMIT 1;` - ) - ) ) - ).map((i: any) => [i[0].user_id, Minigames.find(m => m.column === i[0].m)!.name]); - - let userMap = {}; - for (const [id, m] of topMinigamers) { - addToUserMap(userMap, id, `Rank 1 ${m}`); - } + ) + ); - results.push( - await addRoles({ - users: topMinigamers.map(i => i[0]), - role: Roles.TopMinigamer, - badge: 11, - userMap - }) - ); + for (const { id, minigame_name } of topMinigamers.flat()) { + results.push({ + userID: id, + roleID: Roles.TopMinigamer, + reason: `Rank 1 ${minigame_name}`, + badge: BadgesEnum.TopMinigame + }); } - // Top clue hunters - async function topClueHunters() { - let topClueHunters = ( - await Promise.all( - ClueTiers.map(t => - q( - `SELECT id, '${t.name}' as n, (openable_scores->>'${t.id}')::int as qty -FROM users -INNER JOIN "user_stats" ON "user_stats"."user_id"::text = "users"."id" + return results; +} + +async function topClueHunters() { + const results: RoleResult[] = []; + const topClueHunters = await prisma.$transaction( + ClueTiers.map(t => + prisma.$queryRawUnsafe<{ user_id: string; tier_name: string; qty: string }>( + ` +SELECT user_id::text, '${t.name}' AS tier_name, (openable_scores->>'${t.id}')::int AS qty +FROM user_stats WHERE "openable_scores"->>'${t.id}' IS NOT NULL ORDER BY qty DESC LIMIT 1;` - ) - ) ) ) - .filter((i: any) => Boolean(i[0]?.id)) - .map((i: any) => [i[0]?.id, i[0]?.n]); - - let userMap = {}; - - for (const [id, n] of topClueHunters) { - addToUserMap(userMap, id, `Rank 1 ${n} Clues`); - } + ); - results.push( - await addRoles({ - users: topClueHunters.map(i => i[0]), - role: Roles.TopClueHunter, - badge: null, - userMap - }) - ); + for (const res of topClueHunters.flat()) { + results.push({ + userID: res.user_id, + roleID: Roles.TopClueHunter, + reason: `Rank 1 ${res.tier_name} Clues`, + badge: BadgesEnum.TopMinigame + }); } + return results; +} - // Top farmers - async function farmers() { - const queries = [ - `SELECT id, 'Top 2 Farming Contracts' as desc +async function topFarmers() { + const results: RoleResult[] = []; + const queries = [ + `SELECT id, 'Top 2 Farming Contracts' as desc FROM users WHERE "minion.farmingContract" IS NOT NULL AND "minion.ironman" = true ORDER BY ("minion.farmingContract"->>'contractsCompleted')::int DESC LIMIT 2;`, - `SELECT id, 'Top 2 Ironman Farming Contracts' as desc + `SELECT id, 'Top 2 Ironman Farming Contracts' as desc FROM users WHERE "minion.farmingContract" IS NOT NULL ORDER BY ("minion.farmingContract"->>'contractsCompleted')::int DESC LIMIT 2;`, - `SELECT user_id::text as id, 'Top 2 Most Farming Trips' as desc + `SELECT user_id::text as id, 'Top 2 Most Farming Trips' as desc FROM activity WHERE type = 'Farming' GROUP BY user_id ORDER BY count(user_id)::int DESC LIMIT 2;`, - `SELECT user_id::text as id, 'Top 2 Tithe Farm' as desc + `SELECT user_id::text as id, 'Top 2 Tithe Farm' as desc FROM user_stats ORDER BY "tithe_farms_completed" DESC LIMIT 2;` - ]; - let res = (await Promise.all(queries.map(q))).map((i: any) => [i[0]?.id, i[0]?.desc]); - let userMap = {}; - for (const [id, desc] of res) { - addToUserMap(userMap, id, desc); - } - - results.push( - await addRoles({ - users: res.map(i => i[0]), - role: '894194027363205150', - badge: null, - userMap - }) - ); + ]; + const res = await prisma.$transaction(queries.map(q => prisma.$queryRawUnsafe<{ id: string; desc: string }[]>(q))); + for (const { id, desc } of res.flat()) { + results.push({ + userID: id, + roleID: '894194027363205150', + reason: desc, + badge: BadgesEnum.Slayer + }); } + return results; +} - // Top slayers - async function slayer() { - let topSlayers = ( - await Promise.all( - [mostSlayerPointsQuery, longerSlayerTaskStreakQuery, mostSlayerTasksDoneQuery].map(query => q(query)) - ) - ) - .filter((i: any) => Boolean(i[0]?.id)) - .map((i: any) => [i[0]?.id, i[0]?.desc]); +async function fetchSlayerResults() { + const results: RoleResult[] = []; + const topSlayers = await prisma.$transaction([ + prisma.$queryRawUnsafe<{ id: string; desc: string }[]>(`SELECT id, 'Most Points' as desc +FROM users +WHERE "slayer.points" > 50 +ORDER BY "slayer.points" DESC +LIMIT 1;`), + prisma.$queryRawUnsafe<{ id: string; desc: string }[]>(`SELECT user_id::text as id, 'Longest Task Streak' as desc +FROM user_stats +WHERE "slayer_task_streak" > 20 +ORDER BY "slayer_task_streak" DESC +LIMIT 1;`), + prisma.$queryRawUnsafe<{ id: string; desc: string }[]>(`SELECT user_id::text as id, 'Most Tasks' as desc +FROM slayer_tasks +GROUP BY user_id +ORDER BY count(user_id)::int DESC +LIMIT 1;`) + ]); + + for (const { id, desc } of topSlayers.flat()) { + results.push({ + userID: id, + roleID: Roles.TopSlayer, + reason: desc, + badge: BadgesEnum.Slayer + }); + } + return results; +} - let userMap = {}; - for (const [id, desc] of topSlayers) { - addToUserMap(userMap, id, desc); +async function giveaways() { + const results: RoleResult[] = []; + const GIVEAWAY_CHANNELS = [ + '792691343284764693', + '732207379818479756', + '342983479501389826', + '982989775399174184', + '346304390858145792' + ]; + const res = await prisma.$queryRaw<{ user_id: string; qty: number }[]>`SELECT user_id, COUNT(user_id)::int AS qty + FROM giveaway + WHERE channel_id IN (${Prisma.join(GIVEAWAY_CHANNELS)}) + AND user_id NOT IN ('157797566833098752') + GROUP BY user_id + ORDER BY qty DESC + LIMIT 50;`; + const userIDsToCheck = res.map(i => i.user_id); + + const giveawayBank = new TeamLoot(); + + const giveaways = await prisma.giveaway.findMany({ + where: { + channel_id: { + in: GIVEAWAY_CHANNELS + }, + user_id: { + in: userIDsToCheck + } } + }); - results.push( - await addRoles({ - users: topSlayers.map(i => i[0]), - role: Roles.TopSlayer, - badge: null, - userMap - }) - ); + if (giveaways.length === 0) return results; + + for (const giveaway of giveaways) { + giveawayBank.add(giveaway.user_id, giveaway.loot as ItemBank); } - // Top giveawayers - async function giveaways() { - const GIVEAWAY_CHANNELS = [ - '792691343284764693', - '732207379818479756', - '342983479501389826', - '982989775399174184', - '346304390858145792' - ]; - const res = await prisma.$queryRaw< - { user_id: string; qty: number }[] - >`SELECT user_id, COUNT(user_id)::int AS qty -FROM giveaway -WHERE channel_id IN (${Prisma.join(GIVEAWAY_CHANNELS)}) -AND user_id NOT IN ('157797566833098752') -GROUP BY user_id -ORDER BY qty DESC -LIMIT 50;`; - const userIDsToCheck = res.map(i => i.user_id); + const [[highestID, loot]] = giveawayBank.entries().sort((a, b) => b[1].value() - a[1].value()); - const giveawayBank = new TeamLoot(); + results.push({ + userID: highestID, + roleID: '1104155653745946746', + reason: `Most Value Given Away (${loot.value()})`, + badge: BadgesEnum.TopGiveawayer + }); + return results; +} - const giveaways = await prisma.giveaway.findMany({ - where: { - channel_id: { - in: GIVEAWAY_CHANNELS - }, - user_id: { - in: userIDsToCheck - } - } +async function globalCL() { + const results: RoleResult[] = []; + const result = await roboChimpClient.$queryRaw<{ id: string; total_cl_percent: number }[]>`SELECT ((osb_cl_percent + bso_cl_percent) / 2) AS total_cl_percent, id::text AS id + FROM public.user + WHERE osb_cl_percent IS NOT NULL AND bso_cl_percent IS NOT NULL + ORDER BY total_cl_percent DESC + LIMIT 10;`; + + for (const user of result) { + results.push({ + userID: user.id, + roleID: Roles.TopGlobalCL, + reason: `Top Global CL ${user.total_cl_percent}%`, + badge: BadgesEnum.TopCollector }); - for (const giveaway of giveaways) { - giveawayBank.add(giveaway.user_id, giveaway.loot as ItemBank); - } - for (const [, bank] of giveawayBank.entries()) { - sanitizeBank(bank); - } - - let userMap = {}; - const [[highestID, loot]] = giveawayBank.entries().sort((a, b) => b[1].value() - a[1].value()); - addToUserMap(userMap, highestID, `Most Value Given Away (${loot.value()})`); - - results.push( - await addRoles({ - users: [highestID], - role: '1104155653745946746', - badge: null, - userMap - }) - ); } + return results; +} - async function monkeyKing() { - const res = await q( - 'SELECT id FROM users WHERE monkeys_fought IS NOT NULL ORDER BY cardinality(monkeys_fought) DESC LIMIT 1;' - ); - results.push(await addRoles({ users: [res[0].id], role: '886180040465870918', badge: null })); - } - async function topInventor() { - const userMap = {}; - let topInventors: string[] = []; - const mostUniques = await q< - { id: string; uniques: number; disassembled_items_bank: ItemBank }[] - >(`SELECT u.id, u.uniques, u.disassembled_items_bank FROM ( - SELECT (SELECT COUNT(*) FROM JSON_OBJECT_KEYS("disassembled_items_bank")) uniques, id, disassembled_items_bank FROM users WHERE "skills.invention" > 0 -) u -ORDER BY u.uniques DESC LIMIT 300;`); - topInventors.push(mostUniques[0].id); - addToUserMap(userMap, mostUniques[0].id, 'Most Uniques Disassembled'); - const parsed = mostUniques - .map(i => ({ ...i, value: new Bank(i.disassembled_items_bank).value() })) - .sort((a, b) => b.value - a.value); - topInventors.push(parsed[0].id); - addToUserMap(userMap, parsed[0].id, 'Most Value Disassembled'); - results.push(await addRoles({ users: topInventors, role: Roles.TopInventor, badge: null, userMap })); - } - async function topLeagues() { - if (process.env.TEST) return; - const topPoints = await roboChimpClient.user.findMany({ +async function topLeagues() { + const [topPoints, topTasks] = await prisma.$transaction([ + roboChimpClient.user.findMany({ where: { leagues_points_total: { gt: 0 @@ -528,127 +356,190 @@ ORDER BY u.uniques DESC LIMIT 300;`); leagues_points_total: 'desc' }, take: 2 + }), + RawSQL.leaguesTaskLeaderboard() + ]); + + const results: RoleResult[] = []; + for (const userID of [topPoints, topTasks].flat().map(i => i.id.toString())) { + results.push({ + userID: userID, + roleID: Roles.TopLeagues, + reason: 'Top 2 leagues points/points' }); - const topTasks: { id: string; tasks_completed: number }[] = - await roboChimpClient.$queryRaw`SELECT id::text, COALESCE(cardinality(leagues_completed_tasks_ids), 0) AS tasks_completed - FROM public.user - ORDER BY tasks_completed DESC - LIMIT 2;`; - const userMap = {}; - addToUserMap(userMap, topPoints[0].id.toString(), 'Rank 1 Leagues Points'); - addToUserMap(userMap, topPoints[1].id.toString(), 'Rank 2 Leagues Points'); - addToUserMap(userMap, topTasks[0].id, 'Rank 1 Leagues Tasks'); - addToUserMap(userMap, topTasks[1].id, 'Rank 2 Leagues Tasks'); - const allLeagues = topPoints.map(i => i.id.toString()).concat(topTasks.map(i => i.id)); - assert(allLeagues.length > 0 && allLeagues.length <= 4); - results.push(await addRoles({ users: allLeagues, role: Roles.TopLeagues, badge: null, userMap })); } + return results; +} + +async function topTamer(): Promise { + const [rankOne] = await fetchTameCLLeaderboard({ items: overallPlusItems, resultLimit: 1 }); + if (rankOne) { + return [ + { + userID: rankOne.user_id, + roleID: Roles.TopTamer, + reason: 'Rank 1 Tames CL' + } + ]; + } + return []; +} - async function topTamer() { - const [rankOne] = await fetchTameCLLeaderboard({ items: overallPlusItems, resultLimit: 1 }); - if (rankOne) { - results.push( - await addRoles({ - users: [rankOne.user_id], - role: '1054356709222666240', - badge: null, - userMap: { [rankOne.user_id]: ['Rank 1 Tames CL'] } - }) - ); +async function topMysterious(): Promise { + const items = resolveItems([ + 'Pet mystery box', + 'Holiday mystery box', + 'Equippable mystery box', + 'Clothing mystery box', + 'Tradeable mystery box', + 'Untradeable mystery box' + ]); + const res = await Promise.all(items.map(id => RawSQL.openablesLeaderboard(id))); + + const userScoreMap: Record = {}; + for (const lb of res) { + const [rankOne] = lb; + for (const user of lb) { + if (!userScoreMap[user.id]) userScoreMap[user.id] = 0; + userScoreMap[user.id] += user.score / rankOne.score; } } - const notes: string[] = []; - - async function mysterious() { - const items = resolveItems([ - 'Pet mystery box', - 'Holiday mystery box', - 'Equippable mystery box', - 'Clothing mystery box', - 'Tradeable mystery box', - 'Untradeable mystery box' - ]); - const res = await Promise.all( - items.map(id => - prisma - .$queryRawUnsafe<{ id: string; score: number }[]>( - `SELECT user_id::text AS id, ("openable_scores"->>'${id}')::int AS score -FROM user_stats -WHERE "openable_scores"->>'${id}' IS NOT NULL -AND ("openable_scores"->>'${id}')::int > 50 -ORDER BY ("openable_scores"->>'${id}')::int DESC -LIMIT 50;` - ) - .then(i => i.map(t => ({ id: t.id, score: Number(t.score) }))) - ) - ); + const entries = Object.entries(userScoreMap).sort((a, b) => b[1] - a[1]); + const [[rankOneID]] = entries; - const userScoreMap: Record = {}; - for (const lb of res) { - const [rankOne] = lb; - for (const user of lb) { - if (!userScoreMap[user.id]) userScoreMap[user.id] = 0; - userScoreMap[user.id] += user.score / rankOne.score; - } + return [ + { + userID: rankOneID, + roleID: Roles.TopMysterious, + reason: 'Rank 1 Mystery Box Opens' } + ]; +} - const entries = Object.entries(userScoreMap).sort((a, b) => b[1] - a[1]); - const [[rankOneID]] = entries; +async function monkeyKing(): Promise { + const [user] = await RawSQL.monkeysFoughtLeaderboard(); + return [{ userID: user.id, roleID: '886180040465870918', reason: 'Most Monkeys Fought' }]; +} - let note = '**Top Mystery Box Openers**\n\n'; - for (const [id, score] of entries.slice(0, 10)) { - note += `${usernameCache.get(id) ?? id} - ${score}\n`; - } +async function topInventor(): Promise { + const mostUniquesLb = await RawSQL.inventionDisassemblyLeaderboard(); + const topInventors: string[] = [mostUniquesLb[0].id]; + const parsed = mostUniquesLb + .map(i => ({ ...i, value: new Bank(i.disassembled_items_bank).value() })) + .sort((a, b) => b.value - a.value); + topInventors.push(parsed[0].id); + return topInventors.map(i => ({ userID: i, roleID: Roles.TopInventor, reason: 'Most Uniques/Value Disassembled' })); +} - notes.push(note); +export async function runRolesTask(dryRun: boolean): Promise { + const results: RoleResult[] = []; - results.push( - await addRoles({ - users: [rankOneID], - role: '1074592096968785960', - badge: null, - userMap: { [rankOneID]: ['Rank 1 Mystery Box Opens'] } - }) - ); - } + const promiseQueue = new PQueue({ concurrency: 2 }); const tup = [ - ['Top Slayer', slayer], + ['Top Slayer', fetchSlayerResults], ['Top Clue Hunters', topClueHunters], ['Top Minigamers', topMinigamers], ['Top Sacrificers', topSacrificers], ['Top Collectors', topCollector], ['Top Skillers', topSkillers], - ['Top Farmers', farmers], + ['Top Farmers', topFarmers], ['Top Giveawayers', giveaways], - ['Monkey King', monkeyKing], - ['Top Farmers', farmers], - ['Top Inventor', topInventor], + ['Global CL', globalCL], + + // BSO Only ['Top Leagues', topLeagues], ['Top Tamer', topTamer], - ['Mysterious', mysterious] + ['Top Mysterious', topMysterious], + ['Monkey King', monkeyKing], + ['Top Inventor', topInventor] ] as const; - let failed: string[] = []; - await Promise.all( - tup.map(async ([name, fn]) => { + for (const [name, fn] of tup) { + promiseQueue.add(async () => { + const stopwatch = new Stopwatch(); try { - await fn(); - } catch (err: any) { - if (process.env.TEST) { - throw err; + const res = await fn(); + const [validResults, invalidResults] = partition(res, i => RoleResultSchema.safeParse(i).success); + results.push(...validResults); + if (invalidResults.length > 0) { + logError(`[RolesTask] Invalid results for ${name}: ${JSON.stringify(invalidResults)}`); } - failed.push(`${name} (${err.message})`); - logError(err); + } catch (err) { + logError(`[RolesTask] Error in ${name}: ${err}`); + } finally { + debugLog(`[RolesTask] Ran ${name} in ${stopwatch.stop()}`); + } + }); + } + + await promiseQueue.onIdle(); + + debugLog(`Finished role functions, ${results.length} results`); + + const allBadgeIDs = uniqueArr(results.map(i => i.badge)).filter(notEmpty); + const allRoleIDs = uniqueArr(results.map(i => i.roleID)).filter(notEmpty); + + if (!dryRun) { + const roleNames = new Map(); + const supportServerGuild = globalClient.guilds.cache.get(SupportServer)!; + if (!supportServerGuild) throw new Error('No support guild'); + + // Remove all top badges from all users (and add back later) + debugLog('Removing badges...'); + const badgeIDs = `ARRAY[${allBadgeIDs.join(',')}]`; + await loggedRawPrismaQuery(` +UPDATE users +SET badges = badges - ${badgeIDs} +WHERE badges && ${badgeIDs} +`); + + // Remove roles from ineligible users + debugLog('Remove roles from ineligible users...'); + for (const member of supportServerGuild.members.cache.values()) { + const rolesToRemove = member.roles.cache.filter(r => allRoleIDs.includes(r.id)); + if (rolesToRemove.size > 0) { + await member.roles.remove(rolesToRemove.map(r => r.id)).catch(console.error); + } + } + + // Add roles to users + debugLog('Add roles to users...'); + for (const { userID, roleID, badge } of results) { + if (!userID) continue; + const role = await supportServerGuild.roles.fetch(roleID).catch(console.error); + const member = await supportServerGuild.members.fetch(userID).catch(noOp); + if (!member) { + debugLog(`Failed to find member ${userID}`); + continue; + } + if (!role) { + debugLog(`Failed to find role ${roleID}`); + continue; + } + roleNames.set(roleID, role.name); + + if (!member.roles.cache.has(roleID)) { + await member.roles.add(roleID).catch(console.error); } - }) - ); - let res = `**Roles** -${results.join('\n')} -${failed.length > 0 ? `Failed: ${failed.join(', ')}` : ''} -${notes.join('\n')}`; + if (badge) { + const user = await mUserFetch(userID); + if (!user.user.badges.includes(badge)) { + await user.update({ + badges: { + push: badge + } + }); + } + } + } + + return returnStringOrFile( + `**Roles**\n${results.map(r => `${getUsernameSync(r.userID)} got ${roleNames.get(r.roleID)} because ${r.reason}`).join('\n')}` + ); + } - return res; + return 'Dry run'; } diff --git a/src/lib/safeglobals.ts b/src/lib/safeglobals.ts new file mode 100644 index 00000000000..05e92e577fa --- /dev/null +++ b/src/lib/safeglobals.ts @@ -0,0 +1,3 @@ +import './customItems/customItems'; +import './data/itemAliases'; +import './util/logger'; diff --git a/src/lib/settings/minigames.ts b/src/lib/settings/minigames.ts index 90ae382a5b8..0bb33597356 100644 --- a/src/lib/settings/minigames.ts +++ b/src/lib/settings/minigames.ts @@ -1,10 +1,8 @@ -import { Minigame } from '@prisma/client'; - -import { prisma } from './prisma'; +import type { Minigame } from '@prisma/client'; export type MinigameName = keyof Omit; -export interface BotMinigame { +interface BotMinigame { name: string; aliases: string[]; column: MinigameName; @@ -255,6 +253,11 @@ export const Minigames: readonly BotMinigame[] = [ name: 'Turaels Trials', aliases: ['turaels trials', 'trials'], column: 'turaels_trials' + }, + { + name: 'Fortis Colosseum', + aliases: ['colo'], + column: 'colosseum' } ]; diff --git a/src/lib/settings/prisma.ts b/src/lib/settings/prisma.ts index e8c60966145..4e719a2bf13 100644 --- a/src/lib/settings/prisma.ts +++ b/src/lib/settings/prisma.ts @@ -1,47 +1,9 @@ -import { isMainThread } from 'node:worker_threads'; +import type { Activity, Prisma } from '@prisma/client'; +import type { activity_type_enum } from '@prisma/client'; -import { Activity, activity_type_enum, Prisma, PrismaClient } from '@prisma/client'; +import type { ActivityTaskData } from '../types/minions'; -import { production } from '../../config'; -import { ActivityTaskData } from '../types/minions'; -import { sqlLog } from '../util/logger'; - -declare global { - namespace NodeJS { - interface Global { - prisma: PrismaClient | undefined; - } - } -} - -function makePrismaClient(): PrismaClient { - if (!isMainThread && !process.env.TEST) return null as any; - if (!production && !process.env.TEST) console.log('Making prisma client...'); - return new PrismaClient({ - log: [ - { - emit: 'event', - level: 'query' - } - ] - }); -} - -export const prisma = global.prisma || makePrismaClient(); -global.prisma = prisma; - -export let queryCountStore = { value: 0 }; - -if (isMainThread) { - // @ts-ignore ignore - prisma.$on('query' as any, (_query: any) => { - const query = _query as Prisma.QueryEvent; - if (!production) { - sqlLog(query.query); - } - queryCountStore.value++; - }); -} +export const queryCountStore = { value: 0 }; export function convertStoredActivityToFlatActivity(activity: Activity): ActivityTaskData { return { @@ -64,25 +26,9 @@ export async function countUsersWithItemInCl(itemID: number, ironmenOnly: boolea WHERE ("collectionLogBank"->>'${itemID}') IS NOT NULL AND ("collectionLogBank"->>'${itemID}')::int >= 1 ${ironmenOnly ? 'AND "minion.ironman" = true' : ''};`; - const result = parseInt(((await prisma.$queryRawUnsafe(query)) as any)[0].count); - if (isNaN(result)) { + const result = Number.parseInt(((await prisma.$queryRawUnsafe(query)) as any)[0].count); + if (Number.isNaN(result)) { throw new Error(`countUsersWithItemInCl produced invalid number '${result}' for ${itemID}`); } return result; } - -export async function getUsersActivityCounts(user: MUser) { - const counts = await prisma.$queryRaw<{ type: activity_type_enum; count: bigint }[]>`SELECT type, COUNT(type) -FROM activity -WHERE user_id = ${BigInt(user.id)} -GROUP BY type;`; - - let result: Record = {} as Record; - for (const type of Object.values(activity_type_enum)) { - result[type] = 0; - } - for (const { count, type } of counts) { - result[type] = Number(count); - } - return result; -} diff --git a/src/lib/settings/settings.ts b/src/lib/settings/settings.ts index d2625915bb4..5a818214420 100644 --- a/src/lib/settings/settings.ts +++ b/src/lib/settings/settings.ts @@ -1,23 +1,24 @@ -import { Activity, NewUser, Prisma } from '@prisma/client'; -import { +import type { CommandResponse } from '@oldschoolgg/toolkit'; +import type { CommandOptions } from '@oldschoolgg/toolkit'; +import type { Activity, NewUser, Prisma } from '@prisma/client'; +import type { APIInteractionGuildMember, ButtonInteraction, ChatInputCommandInteraction, GuildMember, User } from 'discord.js'; -import { Time } from 'e'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; -import { CommandOptions } from 'mahoji/dist/lib/types'; +import { Time } from 'e'; +import { isEmpty } from 'lodash'; import { postCommand } from '../../mahoji/lib/postCommand'; import { preCommand } from '../../mahoji/lib/preCommand'; import { convertMahojiCommandToAbstractCommand } from '../../mahoji/lib/util'; -import { minionActivityCache, PerkTier } from '../constants'; +import { PerkTier, minionActivityCache } from '../constants'; import { channelIsSendable, isGroupActivity } from '../util'; -import { handleInteractionError, interactionReply } from '../util/interactionReply'; +import { deferInteraction, handleInteractionError, interactionReply } from '../util/interactionReply'; import { logError } from '../util/logError'; -import { convertStoredActivityToFlatActivity, prisma } from './prisma'; +import { convertStoredActivityToFlatActivity } from './prisma'; export * from './minigames'; @@ -49,7 +50,7 @@ export async function cancelTask(userID: string) { minionActivityCache.delete(userID); } -export async function runMahojiCommand({ +async function runMahojiCommand({ channelID, userID, guildID, @@ -67,7 +68,7 @@ export async function runMahojiCommand({ user: User | MUser; member: APIInteractionGuildMember | GuildMember | null; }) { - const mahojiCommand = globalClient.mahojiClient.commands.values.find(c => c.name === commandName); + const mahojiCommand = Array.from(globalClient.mahojiClient.commands.values()).find(c => c.name === commandName); if (!mahojiCommand) { throw new Error(`No mahoji command found for ${commandName}`); } @@ -80,11 +81,12 @@ export async function runMahojiCommand({ user: globalClient.users.cache.get(user.id)!, member: guildID ? globalClient.guilds.cache.get(guildID)?.members.cache.get(user.id) : undefined, client: globalClient.mahojiClient, - interaction: interaction as ChatInputCommandInteraction + interaction: interaction as ChatInputCommandInteraction, + djsClient: globalClient }); } -export interface RunCommandArgs { +interface RunCommandArgs { commandName: string; args: CommandOptions; user: User | MUser; @@ -95,6 +97,7 @@ export interface RunCommandArgs { guildID: string | undefined | null; interaction: ButtonInteraction | ChatInputCommandInteraction; continueDeltaMillis: number | null; + ephemeral?: boolean; } export async function runCommand({ commandName, @@ -106,15 +109,22 @@ export async function runCommand({ user, member, interaction, - continueDeltaMillis + continueDeltaMillis, + ephemeral }: RunCommandArgs): Promise { - const channel = globalClient.channels.cache.get(channelID.toString()); - if (!channel || !channelIsSendable(channel)) return null; - const mahojiCommand = globalClient.mahojiClient.commands.values.find(c => c.name === commandName); - if (!mahojiCommand) throw new Error('No command found'); + await deferInteraction(interaction); + const channel = globalClient.channels.cache.get(channelID); + const mahojiCommand = Array.from(globalClient.mahojiClient.commands.values()).find(c => c.name === commandName); + if (!mahojiCommand || !channelIsSendable(channel)) { + await interactionReply(interaction, { + content: 'There was an error repeating your trip, I cannot find the channel you used the command in.', + ephemeral: true + }); + return null; + } const abstractCommand = convertMahojiCommandToAbstractCommand(mahojiCommand); - let error: Error | null = null; + const error: Error | null = null; let inhibited = false; try { const inhibitedReason = await preCommand({ @@ -129,19 +139,19 @@ export async function runCommand({ if (inhibitedReason) { inhibited = true; - if (inhibitedReason.silent) return null; + let response = + typeof inhibitedReason.reason! === 'string' ? inhibitedReason.reason : inhibitedReason.reason?.content!; + if (isEmpty(response)) { + response = 'You cannot use this command right now.'; + } await interactionReply(interaction, { - content: - typeof inhibitedReason.reason! === 'string' - ? inhibitedReason.reason - : inhibitedReason.reason!.content!, + content: response, ephemeral: true }); return null; } - if (Array.isArray(args)) throw new Error(`Had array of args for mahoji command called ${commandName}`); const result = await runMahojiCommand({ options: args, commandName, @@ -152,7 +162,14 @@ export async function runCommand({ user, interaction }); - if (result && !interaction.replied) await interactionReply(interaction, result); + if (result && !interaction.replied) { + await interactionReply( + interaction, + typeof result === 'string' + ? { content: result, ephemeral: ephemeral } + : { ...result, ephemeral: ephemeral } + ); + } return result; } catch (err: any) { await handleInteractionError(err, interaction); diff --git a/src/lib/shadesKeys.ts b/src/lib/shadesKeys.ts index 8be5bb6bb67..9222134df3e 100644 --- a/src/lib/shadesKeys.ts +++ b/src/lib/shadesKeys.ts @@ -1,10 +1,10 @@ import { roll } from 'e'; import { Bank, LootTable } from 'oldschooljs'; -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; -import { UnifiedOpenable } from './openables'; +import { resolveItems } from 'oldschooljs/dist/util/util'; +import type { UnifiedOpenable } from './openables'; import getOSItem from './util/getOSItem'; -import resolveItems from './util/resolveItems'; const BronzeChest = new LootTable({ limit: 99 }) .every('Swamp paste', [10, 20]) diff --git a/src/lib/simulation/birdsNest.ts b/src/lib/simulation/birdsNest.ts index 43d0aa6d4be..4ddcf80c039 100644 --- a/src/lib/simulation/birdsNest.ts +++ b/src/lib/simulation/birdsNest.ts @@ -23,34 +23,6 @@ export const treeSeedsNest = new LootTable() .add('Celastrus seed', 1, 3) .add('Redwood tree seed', 1, 2); -export const wysonSeedsNest = new LootTable() - .add('Sweetcorn seed', 6, 102) - .add('Strawberry seed', 6, 100) - .add('Acorn', 1, 80) - .add('Limpwurt seed', 2, 80) - .add('Watermelon seed', 2, 70) - .add('Snape grass seed', 2, 40) - .add('Lantadyme seed', 1, 30) - .add('Dwarf weed seed', 1, 30) - .add('Cadantine seed', 1, 24) - .add('Teak seed', 1, 20) - .add('Mahogany seed', 1, 20) - .add('Willow seed', 1, 16) - .add('Pineapple seed', 1, 16) - .add('Calquat tree seed', 1, 12) - .add('Papaya tree seed', 1, 10) - .add('Maple seed', 1, 6) - .add('Torstol seed', 1, 4) - .add('Ranarr seed', 1, 4) - .add('Snapdragon seed', 1, 4) - .add('Yew seed', 1, 4) - .add('Spirit seed', 1, 4) - .add('Palm tree seed', 1, 2) - .add('Dragonfruit tree seed', 1, 2) - .add('Magic seed', 1, 2) - .add('Celastrus seed', 1, 1) - .add('Redwood tree seed', 1, 1); - export const ringNests = new LootTable() .add('Sapphire ring', 1, 40) .add('Gold ring', 1, 35) diff --git a/src/lib/simulation/fishingTrawler.ts b/src/lib/simulation/fishingTrawler.ts index 61526b96c79..f6b5528382f 100644 --- a/src/lib/simulation/fishingTrawler.ts +++ b/src/lib/simulation/fishingTrawler.ts @@ -2,8 +2,8 @@ import { Bank } from 'oldschooljs'; import LootTable from 'oldschooljs/dist/structures/LootTable'; import { itemID } from 'oldschooljs/dist/util'; +import { resolveItems } from 'oldschooljs/dist/util/util'; import { randomVariation, roll } from '../util'; -import resolveItems from '../util/resolveItems'; const trawlerFish = [ { diff --git a/src/lib/simulation/grandmasterClue.ts b/src/lib/simulation/grandmasterClue.ts index 86197c0a4d1..46f00cd52f7 100644 --- a/src/lib/simulation/grandmasterClue.ts +++ b/src/lib/simulation/grandmasterClue.ts @@ -7,7 +7,7 @@ import Clue from 'oldschooljs/dist/structures/Clue'; import LootTable from 'oldschooljs/dist/structures/LootTable'; import { LampTable } from '../xpLamps'; -import { AllBarrows, BattlestaffTable, CosmeticsTable, runeAlchablesTable, StaffOrbTable } from './sharedTables'; +import { AllBarrows, BattlestaffTable, CosmeticsTable, StaffOrbTable, runeAlchablesTable } from './sharedTables'; const ClueHunterTable = new LootTable() .add('Helm of raedwald') diff --git a/src/lib/simulation/misc.ts b/src/lib/simulation/misc.ts index 1430dffc802..cce3dd29fea 100644 --- a/src/lib/simulation/misc.ts +++ b/src/lib/simulation/misc.ts @@ -160,7 +160,7 @@ export const NestBoxesTable = new LootTable() .add('Nest box (ring)', 1, 5) .add('Nest box (empty)', 1, 3); -export const BaseGemBagTable = new LootTable() +const BaseGemBagTable = new LootTable() .add('Uncut sapphire', 1, 4993) .add('Uncut emerald', 1, 3468) .add('Uncut ruby', 1, 1180) @@ -178,3 +178,29 @@ export const BuildersSupplyCrateTable = new LootTable() .add('Soft clay', [45, 48]) .add('Bolt of cloth', 15) .add('Limestone brick', 9); + +const NexNonUniqueBaseTable = new LootTable() + .add('Blood rune', [84, 325], 3) + .add('Death rune', [85, 170], 3) + .add('Soul rune', [86, 227], 3) + .add('Dragon bolts (unf)', [12, 90], 3) + .add('Cannonball', [42, 298], 3) + .add('Air rune', [123, 1365]) + .add('Fire rune', [210, 1655]) + .add('Water rune', [193, 1599]) + .add('Onyx bolts (e)', [11, 29]) + .add('Air orb', [6, 20], 3) + .add('Uncut ruby', [3, 26], 3) + .add('Wine of zamorak', [4, 14], 3) + .add('Coal', [23, 95]) + .add('Runite ore', [2, 28]) + .add(new LootTable().every('Shark', 3).every('Prayer potion(4)', 1), 1, 1) + .add(new LootTable().every('Saradomin brew(4)', 2).every('Super restore(4)', 1), 1, 1) + .add('Ecumenical key shard', [6, 39]) + .add('Blood essence', [1, 2]) + .add('Coins', [8539, 26_748]); + +export const NexNonUniqueTable = new LootTable() + .every(NexNonUniqueBaseTable, 2) + .oneIn(25, 'Nihil shard', [1, 20]) + .oneIn(100, 'Rune sword'); diff --git a/src/lib/simulation/simulatedKillables.ts b/src/lib/simulation/simulatedKillables.ts index 032e039394f..a8e31df9011 100644 --- a/src/lib/simulation/simulatedKillables.ts +++ b/src/lib/simulation/simulatedKillables.ts @@ -1,18 +1,17 @@ -import { randArrItem, randInt } from 'e'; +import { randArrItem, randInt, roll } from 'e'; import { Bank, Misc } from 'oldschooljs'; -import { DOANonUniqueTable } from '../../tasks/minions/bso/doaActivity'; +import { DOANonUniqueTable } from '../bso/doa/doaLootTable'; import { nexUniqueDrops } from '../data/CollectionsExport'; import { chanceOfDOAUnique, pickUniqueToGiveUser } from '../depthsOfAtlantis'; import { MoktangLootTable } from '../minions/data/killableMonsters/custom/bosses/Moktang'; import { NEX_UNIQUE_DROPRATE, nexLootTable } from '../nex'; import { zygomiteFarmingSource } from '../skilling/skills/farming/zygomites'; -import { roll } from '../util'; import { WintertodtCrate } from './wintertodt'; interface SimulatedKillable { name: string; - isCustom: Boolean; + isCustom: boolean; loot: (quantity: number) => Bank; } @@ -49,7 +48,7 @@ export const simulatedKillables: SimulatedKillable[] = [ name: 'The Nightmare', isCustom: false, loot: (quantity: number) => { - let bank = new Bank(); + const bank = new Bank(); for (let i = 0; i < quantity; i++) { bank.add(Misc.Nightmare.kill({ team: [{ damageDone: 2400, id: 'id' }], isPhosani: false }).id); } @@ -60,7 +59,7 @@ export const simulatedKillables: SimulatedKillable[] = [ name: "Phosani's Nightmare", isCustom: false, loot: (quantity: number) => { - let bank = new Bank(); + const bank = new Bank(); for (let i = 0; i < quantity; i++) { bank.add(Misc.Nightmare.kill({ team: [{ damageDone: 2400, id: 'id' }], isPhosani: true }).id); } @@ -87,7 +86,7 @@ export const simulatedKillables: SimulatedKillable[] = [ name: 'Nex', isCustom: true, loot: (quantity: number) => { - let loot = new Bank(); + const loot = new Bank(); for (let i = 0; i < quantity; i++) { if (roll(NEX_UNIQUE_DROPRATE(1))) { loot.add(randArrItem(nexUniqueDrops), 1); @@ -108,7 +107,7 @@ export const simulatedKillables: SimulatedKillable[] = [ name: src.name, isCustom: true, loot: (quantity: number) => { - let loot = new Bank(); + const loot = new Bank(); for (let i = 0; i < quantity; i++) { loot.add(src.lootTable?.roll()); } diff --git a/src/lib/simulation/tempoross.ts b/src/lib/simulation/tempoross.ts index 9b281fb6f51..3b3f162e1d4 100644 --- a/src/lib/simulation/tempoross.ts +++ b/src/lib/simulation/tempoross.ts @@ -129,7 +129,7 @@ const fishTables = [ export function getTemporossLoot(quantity: number, fishingLevel: number, userBank: Bank) { const loot = new Bank(); - let lootTable = new LootTable() + const lootTable = new LootTable() .add('Spirit flakes', [32, 64], 2000) .add(PoolCasketTable, 1, 400) .add('Plank', [20, 30], 350) diff --git a/src/lib/simulation/toa.ts b/src/lib/simulation/toa.ts index 2be1d4463a5..e38fab51b26 100644 --- a/src/lib/simulation/toa.ts +++ b/src/lib/simulation/toa.ts @@ -1,7 +1,10 @@ -import { mentionCommand, SimpleTable } from '@oldschoolgg/toolkit'; -import { Minigame, XpGainSource } from '@prisma/client'; +import { SimpleTable, exponentialPercentScale, mentionCommand } from '@oldschoolgg/toolkit'; +import type { CommandResponse } from '@oldschoolgg/toolkit'; +import type { Minigame } from '@prisma/client'; +import { XpGainSource } from '@prisma/client'; import { bold } from 'discord.js'; import { + Time, calcPercentOfNum, calcWhatPercent, clamp, @@ -15,25 +18,24 @@ import { round, scaleNumber, sumArr, - Time, uniqueArr } from 'e'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; import { Bank, LootTable } from 'oldschooljs'; +import { resolveItems } from 'oldschooljs/dist/util/util'; import { mahojiParseNumber, userStatsBankUpdate } from '../../mahoji/mahojiSettings'; import { Emoji } from '../constants'; import { getSimilarItems } from '../data/similarItems'; import { degradeItem } from '../degradeableItems'; -import { GearStats, UserFullGearSetup } from '../gear/types'; -import { canAffordInventionBoost, inventionBoosts, InventionID, inventionItemBoost } from '../invention/inventions'; +import type { GearStats, UserFullGearSetup } from '../gear/types'; +import { InventionID, canAffordInventionBoost, inventionBoosts, inventionItemBoost } from '../invention/inventions'; import { trackLoot } from '../lootTrack'; import { setupParty } from '../party'; import { getMinigameScore } from '../settings/minigames'; import { SkillsEnum } from '../skilling/types'; -import { constructGearSetup, Gear } from '../structures/Gear'; -import { MakePartyOptions, Skills } from '../types'; -import { TOAOptions } from '../types/minions'; +import { Gear, constructGearSetup } from '../structures/Gear'; +import type { MakePartyOptions, Skills } from '../types'; +import type { TOAOptions } from '../types/minions'; import { assert, channelIsSendable, @@ -46,8 +48,7 @@ import addSubTaskToActivityTask from '../util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../util/calcMaxTripLength'; import getOSItem from '../util/getOSItem'; import itemID from '../util/itemID'; -import resolveItems from '../util/resolveItems'; -import { bankToStrShortNames, exponentialPercentScale, getToaKCs } from '../util/smallUtils'; +import { bankToStrShortNames, getToaKCs } from '../util/smallUtils'; import { updateBankSetting } from '../util/updateBankSetting'; import { TeamLoot } from './TeamLoot'; @@ -254,7 +255,7 @@ const toaRequirements: { return true; }, desc: () => - `atleast ${BP_DARTS_NEEDED}x darts per raid, and using one of: ${ALLOWED_DARTS.map(i => i.name).join( + `at least ${BP_DARTS_NEEDED}x darts per raid, and using one of: ${ALLOWED_DARTS.map(i => i.name).join( ', ' )}, loaded in Blowpipe` }, @@ -292,7 +293,7 @@ const toaRequirements: { return true; }, desc: () => - `decent range gear (BiS is ${maxRangeGear.toString()}), atleast ${BOW_ARROWS_NEEDED}x arrows equipped, and one of these bows: ${REQUIRED_RANGE_WEAPONS.map( + `decent range gear (BiS is ${maxRangeGear.toString()}), at least ${BOW_ARROWS_NEEDED}x arrows equipped, and one of these bows: ${REQUIRED_RANGE_WEAPONS.map( itemNameFromID ).join(', ')}` }, @@ -349,11 +350,11 @@ const toaRequirements: { minimumSuppliesNeeded = minSuppliesWithAtkStr; } if (!user.owns(minimumSuppliesNeeded.clone().multiply(quantity))) { - return `You need atleast this much supplies: ${minimumSuppliesNeeded}.`; + return `You need at least this much supplies: ${minimumSuppliesNeeded}.`; } const bfCharges = BLOOD_FURY_CHARGES_PER_RAID * quantity; if (user.gear.melee.hasEquipped('Amulet of blood fury') && user.user.blood_fury_charges < bfCharges) { - return `You need atleast ${bfCharges} Blood fury charges to use it, otherwise it has to be unequipped: ${mentionCommand( + return `You need at least ${bfCharges} Blood fury charges to use it, otherwise it has to be unequipped: ${mentionCommand( globalClient, 'minion', 'charge' @@ -362,7 +363,7 @@ const toaRequirements: { const tumCharges = TUMEKEN_SHADOW_PER_RAID * quantity; if (user.gear.mage.hasEquipped("Tumeken's shadow") && user.user.tum_shadow_charges < tumCharges) { - return `You need atleast ${tumCharges} Tumeken's shadow charges to use it, otherwise it has to be unequipped: ${mentionCommand( + return `You need at least ${tumCharges} Tumeken's shadow charges to use it, otherwise it has to be unequipped: ${mentionCommand( globalClient, 'minion', 'charge' @@ -379,7 +380,7 @@ const toaRequirements: { return true; }, - desc: () => `Need atleast ${minimumSuppliesNeeded}` + desc: () => `Need at least ${minimumSuppliesNeeded}` }, { name: 'Rune Pouch', @@ -390,7 +391,7 @@ const toaRequirements: { } return true; }, - desc: () => `Need atleast ${minimumSuppliesNeeded}` + desc: () => `Need at least ${minimumSuppliesNeeded}` }, { name: 'Poison Protection', @@ -444,7 +445,7 @@ export function calculateXPFromRaid({ let totalCombatXP = 0; for (const style of meleeStyles) { - let amount = calcPercentOfNum(percentOfRaid, xpPerStyle); + const amount = calcPercentOfNum(percentOfRaid, xpPerStyle); totalCombatXP += amount; promises.push( user.addXP({ @@ -513,7 +514,7 @@ const untradeables = [ ]; function untradeableRoll(kc: number, cl: Bank) { - let loot = new Bank(); + const loot = new Bank(); for (const { item, dropRate } of untradeables) { let rolls = 1; if (!cl.has(item.id) && kc > 5) { @@ -529,7 +530,7 @@ function untradeableRoll(kc: number, cl: Bank) { return loot; } -let TOAUniqueTable = new LootTable() +const TOAUniqueTable = new LootTable() .add('Lightbearer', 1, 7) .add("Osmumten's fang", 1, 7) .add("Elidinis' ward", 1, 3) @@ -595,7 +596,7 @@ const nonUniqueTable = [ function nonUniqueLoot({ points }: { points: number }) { assert(typeof points === 'number', `Points must be a number, received ${typeof points} ${points}.`); assert(points >= 1 && points <= 64_000, `Points (${points.toLocaleString()}) must be between 1-64,000`); - let loot = new Bank(); + const loot = new Bank(); for (let i = 0; i < 3; i++) { const [item, divisor] = randArrItem(nonUniqueTable); @@ -634,14 +635,13 @@ export function calcTOALoot({ users, raidLevel }: { users: TOALootUser[]; raidLe const totalTeamPoints = sumArr(users.map(i => i.points)); // The number of raid levels less than or equal to 400 - let x = Math.min(raidLevel, 400); + const x = Math.min(raidLevel, 400); // The number of raid levels from 400 to 550 - let y = Math.min(150, raidLevel - 400); + const y = Math.min(150, raidLevel - 400); - // prettier-ignore - let pointsForOnePercentUniqueChance = 10_500 - 20 * (x + (y / 3)); - let chanceOfUnique = Math.min(totalTeamPoints / pointsForOnePercentUniqueChance, 55); - let didGetUnique = percentChance(chanceOfUnique); + const pointsForOnePercentUniqueChance = 10_500 - 20 * (x + y / 3); + const chanceOfUnique = Math.min(totalTeamPoints / pointsForOnePercentUniqueChance, 55); + const didGetUnique = percentChance(chanceOfUnique); const uniqueRecipient = didGetUnique ? uniqueDeciderTable.roll() : null; const messages: string[] = [ @@ -662,9 +662,9 @@ export function calcTOALoot({ users, raidLevel }: { users: TOALootUser[]; raidLe } loot.add(user.id, untradeableRoll(user.kc, user.cl)); - let pointsForOnePercentPetChance = 350_000 - 700 * (x + y / 3); - let chanceOfPet = Math.min(user.points / pointsForOnePercentPetChance, 55); - let didGetPet = percentChance(chanceOfPet); + const pointsForOnePercentPetChance = 350_000 - 700 * (x + y / 3); + const chanceOfPet = Math.min(user.points / pointsForOnePercentPetChance, 55); + const didGetPet = percentChance(chanceOfPet); if (didGetPet) { loot.add(user.id, "Tumeken's guardian"); } @@ -796,7 +796,7 @@ function calcDeathChance(totalAttempts: number, raidLevel: RaidLevel, tobAndCoxK } const match = map.find(i => totalAttempts <= i.attemptsMax)! ?? map[map.length - 1]; - const nextMatch = map[map.indexOf(match) + 1] ?? { attemptsMax: Infinity, increase: 0 }; + const nextMatch = map[map.indexOf(match) + 1] ?? { attemptsMax: Number.POSITIVE_INFINITY, increase: 0 }; const increase = scaleNumber( totalAttempts, @@ -833,7 +833,7 @@ function calculateTotalEffectiveness({ skillsAsLevels: Skills; randomNess: boolean; }) { - let percents = []; + const percents = []; percents.push(clamp(calcWhatPercent(totalKC, 20), 0, 100)); percents.push(clamp(calcWhatPercent(totalAttempts, 20), 0, 100)); @@ -852,7 +852,7 @@ function calculateTotalEffectiveness({ } function calculateAdditionalDeathChance(raidLevel: RaidLevel, attempts: number) { - const minDeathChance = mileStoneBaseDeathChances.find(i => i.level === raidLevel)!.minChance; + const minDeathChance = mileStoneBaseDeathChances.find(i => i.level === raidLevel)?.minChance; if (!minDeathChance) return 0; let divisor = 1; for (const number of [50, 100, 200, 300]) { @@ -871,8 +871,8 @@ function calculatePointsAndDeaths( coxAndTobKC: number, teamSize: number ) { - let deaths: number[] = []; - let deathChance = calcDeathChance(totalAttempts, raidLevel, coxAndTobKC); + const deaths: number[] = []; + const deathChance = calcDeathChance(totalAttempts, raidLevel, coxAndTobKC); const harshEffectivenessScale = exponentialPercentScale(effectiveness, 0.05); let points = estimatePoints(raidLevel, teamSize) / teamSize; @@ -916,14 +916,8 @@ function calcSetupPercent( } // For melee compare the highest melee attack stat of max setup with the highest melee attack stat of the user if (melee) { - let maxMeleeStat = Math.max( - maxStats['attack_stab'], - Math.max(maxStats['attack_slash'], maxStats['attack_crush']) - ); - let userMeleeStat = Math.max( - userStats['attack_stab'], - Math.max(userStats['attack_slash'], userStats['attack_crush']) - ); + const maxMeleeStat = Math.max(maxStats.attack_stab, Math.max(maxStats.attack_slash, maxStats.attack_crush)); + const userMeleeStat = Math.max(userStats.attack_stab, Math.max(userStats.attack_slash, userStats.attack_crush)); totalPercent += Math.min(100, calcWhatPercent(userMeleeStat, maxMeleeStat)); numKeys++; } @@ -941,7 +935,7 @@ interface GearSetupPercents { mage: number; total: number; } -export function calculateUserGearPercents(gear: UserFullGearSetup, raidLevel: number): GearSetupPercents { +function calculateUserGearPercents(gear: UserFullGearSetup, raidLevel: number): GearSetupPercents { const maxMelee = raidLevel < 300 ? maxMeleeLessThan300Gear : maxMeleeOver300Gear; const melee = calcSetupPercent( maxMelee.stats, @@ -1006,7 +1000,7 @@ async function calcTOAInput({ } // Between 8-1 brews - let brewsNeeded = Math.max(1, 9 - Math.max(1, Math.ceil((kc + 1) / 12))); + const brewsNeeded = Math.max(1, 9 - Math.max(1, Math.ceil((kc + 1) / 12))); let restoresNeeded = Math.max(2, Math.floor(brewsNeeded / 3)); if (kc < 2) restoresNeeded += 3; else if (kc < 5) restoresNeeded += 2; @@ -1019,7 +1013,7 @@ async function calcTOAInput({ throw new Error(`${user.logName} had no range weapon for TOA`); } if (rangeWeapon.id !== itemID('Bow of faerdhinen (c)')) { - cost.add(user.gear.range.ammo!.item, BOW_ARROWS_NEEDED * quantity); + cost.add(user.gear.range.ammo?.item, BOW_ARROWS_NEEDED * quantity); } if (user.gear.melee.hasEquipped('Amulet of blood fury')) { cost.remove('Saradomin brew(4)', quantity); @@ -1038,7 +1032,7 @@ async function calcTOAInput({ }; } -export async function checkTOAUser( +async function checkTOAUser( user: MUser, kc: number, raidLevel: number, @@ -1073,7 +1067,7 @@ export async function checkTOAUser( true, `${ user.usernameOrMention - } doesn't have enough Serpentine helm charges. You need atleast ${serpHelmCharges} charges to do a ${formatDuration( + } doesn't have enough Serpentine helm charges. You need at least ${serpHelmCharges} charges to do a ${formatDuration( duration )} TOA raid.` ]; @@ -1086,7 +1080,7 @@ export async function checkTOAUser( if (kc < dividedRaidLevel) { return [ true, - `${user.usernameOrMention}, you need atleast ${dividedRaidLevel} TOA KC to ${ + `${user.usernameOrMention}, you need at least ${dividedRaidLevel} TOA KC to ${ teamSize === 2 ? 'duo' : 'solo' } a level ${raidLevel} TOA raid.` ]; @@ -1096,7 +1090,7 @@ export async function checkTOAUser( return [false]; } -export async function checkTOATeam(users: MUser[], raidLevel: number, quantity: number): Promise { +async function checkTOATeam(users: MUser[], raidLevel: number, quantity: number): Promise { const userWithoutSupplies = users.find(u => !u.bank.has(minimumSuppliesNeeded)); if (userWithoutSupplies) { return `${userWithoutSupplies.usernameOrMention} doesn't have enough supplies`; @@ -1115,9 +1109,7 @@ export async function checkTOATeam(users: MUser[], raidLevel: number, quantity: Time.Hour, quantity ); - if (!checkResult[0]) { - continue; - } else { + if (checkResult[1]) { return checkResult[1]; } } @@ -1153,7 +1145,7 @@ export async function toaStartCommand( return "Your minion is busy, so you can't start a raid."; } - let maxSize = mahojiParseNumber({ input: teamSize, min: 2, max: 8 }) ?? 8; + const maxSize = mahojiParseNumber({ input: teamSize, min: 2, max: 8 }) ?? 8; const partyOptions: MakePartyOptions = { leader: user, @@ -1289,7 +1281,7 @@ export async function toaStartCommand( debugStr += ` ${inventionBoosts.chincannon.toaPercentReduction}% speed increase from the Chincannon (${res.messages})`; } - await userStatsBankUpdate(u.id, 'toa_cost', realCost); + await userStatsBankUpdate(u, 'toa_cost', realCost); const effectiveCost = realCost.clone(); totalCost.add(effectiveCost); @@ -1322,7 +1314,7 @@ export async function toaStartCommand( })) }); - let userArr: [string, number, number[]][][] = []; + const userArr: [string, number, number[]][][] = []; for (let i = 0; i < quantity; i++) { const thisQtyArr: [string, number, number[]][] = []; for (const user of toaSimResults[i].parsedTeam) { @@ -1370,7 +1362,7 @@ const totalSpeedReductions = sumArr(miscBoosts.map(i => i[1])) + 15; // Os fang; -let baseTOADurations: Record = { +const baseTOADurations: Record = { 1: Time.Minute * 50, 100: Time.Minute * 50, 150: Time.Minute * 60, @@ -1396,7 +1388,7 @@ interface ParsedTeamMember { attempts: number; } -export function createTOATeam({ +function createTOATeam({ team, disableVariation, raidLevel, @@ -1407,11 +1399,11 @@ export function createTOATeam({ disableVariation?: true; quantity: number; }) { - let arr = []; + const arr = []; const messages: string[] = []; for (const { user, toaAttempts, minigameScores } of team) { - let gearStats = calculateUserGearPercents(user.gear, raidLevel); + const gearStats = calculateUserGearPercents(user.gear, raidLevel); const totalAttempts = toaAttempts; const totalKC = minigameScores.tombs_of_amascut; const effectiveness = calculateTotalEffectiveness({ @@ -1445,8 +1437,8 @@ export function createTOATeam({ const teamSize = team.length; const maxScaling = 350; assert(teamSize >= 1 && teamSize < 9, 'TOA team must be 1-8 users'); - let individualReductions: { id: string; reduction: number }[] = []; - let reductions: Record = {}; + const individualReductions: { id: string; reduction: number }[] = []; + const reductions: Record = {}; const results: { duration: number; @@ -1459,7 +1451,7 @@ export function createTOATeam({ chincannonUser: string | null; }[] = []; - let parsedTeam = []; + const parsedTeam = []; for (const u of arr) { let userPercentChange = 0; @@ -1471,16 +1463,16 @@ export function createTOATeam({ // Reduce time for KC const kcPercent = clamp(calcWhatPercent(u.totalAttempts, maxScaling), 1, 100); - let kcPercentBoost = calcPerc(kcPercent, speedReductionForKC); + const kcPercentBoost = calcPerc(kcPercent, speedReductionForKC); userPercentChange += kcPercentBoost; let weaponBoosts: string[] = ["Osmumten's fang", 'Ghrazi rapier']; - let boostAmounts = [15, 6] as const; + const boostAmounts = [15, 6] as const; // If the raid level is less than 300, Ghrazi rapier is the BIS instead. if (raidLevel < 300) weaponBoosts = [weaponBoosts[1], weaponBoosts[0]]; for (let i = 0; i < weaponBoosts.length; i++) { - let amount = boostAmounts[i]; + const amount = boostAmounts[i]; if (u.gear.melee.hasEquipped(weaponBoosts[i])) { userPercentChange += amount; break; @@ -1501,7 +1493,7 @@ export function createTOATeam({ } userPercentChange = calcWhatPercent(userPercentChange, totalSpeedReductions); - let reduction = round(userPercentChange / teamSize, 1); + const reduction = round(userPercentChange / teamSize, 1); individualReductions.push({ id: u.id, reduction: userPercentChange }); reductions[u.user.id] = reduction; @@ -1530,7 +1522,7 @@ export function createTOATeam({ totalReduction = round(totalReduction / (teamSize - 1), 2); messages.push( `${ - arr.find(i => i.id === worstTeamMember.id)!.user.badgedUsername + arr.find(i => i.id === worstTeamMember.id)?.user.badgedUsername } is being carried by the rest of the team so they don't affect the raid time!` ); } else { @@ -1575,7 +1567,7 @@ export function createTOATeam({ } for (let i = 0; i < TOARooms.length; i++) { - let room = TOARooms[i]; + const room = TOARooms[i]; if (usersWithPointsAndDeaths.every(member => member.deaths.includes(i))) { wipedRoom = room.id; @@ -1611,7 +1603,7 @@ export function createTOATeam({ return results; } -export async function toaCheckCommand(user: MUser) { +async function toaCheckCommand(user: MUser) { const result = await checkTOAUser(user, await getMinigameScore(user.id, 'tombs_of_amascut'), 200, 5, Time.Hour, 1); if (result[0]) { return `🔴 You aren't able to join a Tombs of Amascut raid, address these issues first: ${result[1]}`; @@ -1676,7 +1668,7 @@ export async function toaHelpCommand(user: MUser, channelID: string) { totalUniques += user.cl.amount(item); } - let str = `**Tombs of Amascut** + const str = `**Tombs of Amascut** **Attempts:** ${stats.toa_attempts} **Entry Mode:** ${entryKC} KC @@ -1686,18 +1678,18 @@ export async function toaHelpCommand(user: MUser, channelID: string) { totalUniques > 0 ? `(1 unique per ${Math.floor( stats.total_toa_points / totalUniques - ).toLocaleString()} pts, one unique every ${Math.floor( + ).toLocaleString()} pts, one unique every ${Math.floor( totalKC / totalUniques - )} raids, one unique every ${formatDuration( + )} raids, one unique every ${formatDuration( (stats.total_toa_duration_minutes * Time.Minute) / totalUniques - )})` + )})` : '' } **Requirements** ${toaRequirements .map(i => { - let res = i.doesMeet({ user, gearStats, quantity: 1, allItemsOwned: user.allItemsOwned }); + const res = i.doesMeet({ user, gearStats, quantity: 1, allItemsOwned: user.allItemsOwned }); if (typeof res === 'string') { return `- ❌ ${bold(i.name)} ${res}`; } @@ -1713,21 +1705,3 @@ ${calculateBoostString(user)} return channelID === '1069176960523190292' ? { content: str, ephemeral: true } : str; } - -export function normalizeTOAUsers(data: TOAOptions) { - const _detailedUsers = data.detailedUsers; - const detailedUsers = ( - (Array.isArray(_detailedUsers[0]) ? _detailedUsers : [_detailedUsers]) as [string, number, number[]][][] - ).map(userArr => - userArr.map(user => ({ - id: user[0], - points: user[1], - deaths: user[2] - })) - ); - return detailedUsers; -} - -export function anyoneDiedInTOARaid(data: TOAOptions) { - return normalizeTOAUsers(data).some(userArr => userArr.some(user => user.deaths.length > 0)); -} diff --git a/src/lib/simulation/tob.ts b/src/lib/simulation/tob.ts index 3148333da4f..f47453de628 100644 --- a/src/lib/simulation/tob.ts +++ b/src/lib/simulation/tob.ts @@ -1,13 +1,13 @@ import { SimpleTable } from '@oldschoolgg/toolkit'; import { percentChance, roll, sumArr } from 'e'; import { Bank, LootTable } from 'oldschooljs'; -import { LootBank } from 'oldschooljs/dist/meta/types'; -import { convertLootBanksToItemBanks, JSONClone } from 'oldschooljs/dist/util'; +import type { LootBank } from 'oldschooljs/dist/meta/types'; +import { JSONClone, convertLootBanksToItemBanks } from 'oldschooljs/dist/util'; import { TOBRooms } from '../data/tob'; import { assert } from '../util/logError'; -export interface TeamMember { +interface TeamMember { id: string; /** * The rooms they died in. @@ -15,7 +15,7 @@ export interface TeamMember { deaths: number[]; } -export interface TheatreOfBloodOptions { +interface TheatreOfBloodOptions { /** * Whether or not this raid is in Challenge Mode or not. */ @@ -87,7 +87,7 @@ const HardModeExtraTable = new LootTable() .tertiary(150, 'Sanguine ornament kit') .tertiary(100, 'Holy ornament kit'); -export class TheatreOfBloodClass { +class TheatreOfBloodClass { nonUniqueLoot(member: ParsedMember, isHardMode: boolean, deaths: number[]) { if (deaths.length === TOBRooms.length) { return new Bank().add('Cabbage'); @@ -101,7 +101,7 @@ export class TheatreOfBloodClass { if (isHardMode) { // Add 15% extra regular loot for hard mode: for (const [itemID] of Object.entries(loot.bank)) { - loot.bank[parseInt(itemID)] = Math.ceil(loot.bank[parseInt(itemID)] * 1.15); + loot.bank[Number.parseInt(itemID)] = Math.ceil(loot.bank[Number.parseInt(itemID)] * 1.15); } // Add HM Tertiary drops: dust / kits loot.add(HardModeExtraTable.roll()); @@ -151,7 +151,7 @@ export class TheatreOfBloodClass { const totalDeaths = sumArr(parsedTeam.map(i => i.numDeaths)); - let percentBaseChanceOfUnique = (options.hardMode ? 13 : 11) * (teamPoints / maxPointsTeamCanGet); + const percentBaseChanceOfUnique = (options.hardMode ? 13 : 11) * (teamPoints / maxPointsTeamCanGet); const purpleReceived = percentChance(percentBaseChanceOfUnique); const purpleRecipient = purpleReceived ? this.uniqueDecide(parsedTeam) : null; diff --git a/src/lib/simulation/wintertodt.ts b/src/lib/simulation/wintertodt.ts index 2e6f7671fd5..fb7c42ae58a 100644 --- a/src/lib/simulation/wintertodt.ts +++ b/src/lib/simulation/wintertodt.ts @@ -1,16 +1,15 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { SimpleTable } from '@oldschoolgg/toolkit'; +import { SimpleTable, normal } from '@oldschoolgg/toolkit'; import { calcPercentOfNum, randInt, roll } from 'e'; import { Bank } from 'oldschooljs'; import LootTable from 'oldschooljs/dist/structures/LootTable'; import { convertXPtoLVL } from 'oldschooljs/dist/util/util'; +import { resolveItems } from 'oldschooljs/dist/util/util'; import { MAX_XP } from '../constants'; -import { LevelRequirements, SkillsEnum } from '../skilling/types'; -import { ItemBank } from '../types'; +import type { LevelRequirements } from '../skilling/types'; +import { SkillsEnum } from '../skilling/types'; +import type { ItemBank } from '../types'; import itemID from '../util/itemID'; -import resolveItems from '../util/resolveItems'; -import { normal } from '../util/smallUtils'; interface WintertodtCrateOptions { points: number; @@ -138,7 +137,7 @@ const pyroPieces = resolveItems([ 'Pyromancer boots' ]) as number[]; -export class WintertodtCrateClass { +class WintertodtCrateClass { public pickWeightedLootItem(lvl: number, array: T[]): T { const maxIndex = Math.max(Math.floor(calcPercentOfNum(Math.min(lvl + 15, 99), array.length)), 1); const minIndex = Math.floor(calcPercentOfNum(Math.max(lvl - 70, 1), array.length)); @@ -258,7 +257,7 @@ export class WintertodtCrateClass { for (let i = 0; i < rolls; i++) { const rolledUnique = this.rollUnique(new Bank().add(itemsOwned).add(loot), firemakingXP); - if (rolledUnique instanceof Array) { + if (Array.isArray(rolledUnique)) { const [itemID, qty] = rolledUnique; loot.add(itemID, qty); continue; diff --git a/src/lib/skilling/functions/calcFarmingContracts.ts b/src/lib/skilling/functions/calcFarmingContracts.ts index 89f35bb1a63..db22a98c5db 100644 --- a/src/lib/skilling/functions/calcFarmingContracts.ts +++ b/src/lib/skilling/functions/calcFarmingContracts.ts @@ -3,7 +3,7 @@ import { randArrItem, randInt, roll } from 'e'; import { Bank, LootTable } from 'oldschooljs'; import { HighSeedPackTable, LowSeedPackTable, MediumSeedPackTable } from '../../data/seedPackTables'; -import { PlantTier } from '../../minions/farming/types'; +import type { PlantTier } from '../../minions/farming/types'; export function openSeedPack(seedTier: number): Bank { const loot = new Bank(); diff --git a/src/lib/skilling/functions/calcsFarming.ts b/src/lib/skilling/functions/calcsFarming.ts index b1b049f8528..d316429382e 100644 --- a/src/lib/skilling/functions/calcsFarming.ts +++ b/src/lib/skilling/functions/calcsFarming.ts @@ -1,10 +1,10 @@ import { randInt } from 'e'; - import { userHasMasterFarmerOutfit } from '../../../mahoji/mahojiSettings'; import { BitField } from '../../constants'; +import { QuestID } from '../../minions/data/quests'; import { hasUnlockedAtlantis } from '../../util'; -import { FarmingPatchName } from '../../util/farmingHelpers'; -import { Plant, SkillsEnum } from '../types'; +import type { FarmingPatchName } from '../../util/farmingHelpers'; +import { type Plant, SkillsEnum } from '../types'; export function calcNumOfPatches(plant: Plant, user: MUser, qp: number): [number] { let numOfPatches = plant.defaultNumOfPatches; @@ -47,6 +47,18 @@ export function calcNumOfPatches(plant: Plant, user: MUser, qp: number): [number } } + if (user.user.finished_quest_ids.includes(QuestID.ChildrenOfTheSun)) { + switch (plant.seedType) { + case 'allotment': + numOfPatches += 2; + break; + case 'herb': + case 'flower': + numOfPatches += 1; + break; + } + } + return [numOfPatches]; } diff --git a/src/lib/skilling/functions/calcsHunter.ts b/src/lib/skilling/functions/calcsHunter.ts index 4d48a0501ed..a0bc1ecd3a5 100644 --- a/src/lib/skilling/functions/calcsHunter.ts +++ b/src/lib/skilling/functions/calcsHunter.ts @@ -1,8 +1,8 @@ -import { calcPercentOfNum, Time } from 'e'; +import { Time, calcPercentOfNum } from 'e'; import LootTable from 'oldschooljs/dist/structures/LootTable'; import { percentChance } from '../../util'; -import { Creature } from '../types'; +import type { Creature } from '../types'; export function calcLootXPHunting( currentLevel: number, @@ -31,12 +31,12 @@ export function calcLootXPHunting( const experienceFactor = experienceScore / (Time.Hour / timeInSeconds); const maxPercentIncrease = 10; - let percentIncrease = Math.min(Math.floor(experienceFactor), maxPercentIncrease); + const percentIncrease = Math.min(Math.floor(experienceFactor), maxPercentIncrease); chanceOfSuccess += chanceOfSuccess * (percentIncrease / 100); } - let xpToAdd = creature.hunterXP + (creature.name === 'Herbiboar' ? 27 * (currentLevel - 80) : 0); + const xpToAdd = creature.hunterXP + (creature.name === 'Herbiboar' ? 27 * (currentLevel - 80) : 0); if (noRandomness) { const successes = Math.floor(calcPercentOfNum(chanceOfSuccess, quantity)); @@ -56,10 +56,10 @@ export function calcLootXPHunting( } export function generateHerbiTable(currentHerbLvl: number, gotMagicSec: boolean): LootTable { - let herbiTable = new LootTable(); + const herbiTable = new LootTable(); if (currentHerbLvl < 31) return herbiTable; herbiTable.tertiary(6500, 'Herbi'); - let baseYield = gotMagicSec ? 2 : 1; + const baseYield = gotMagicSec ? 2 : 1; herbiTable .add('Grimy guam leaf', [baseYield, 4]) .add('Grimy irit leaf', [baseYield, 4]) diff --git a/src/lib/skilling/functions/calcsRunecrafting.ts b/src/lib/skilling/functions/calcsRunecrafting.ts index c3d5d3227a3..42fc4c2a54f 100644 --- a/src/lib/skilling/functions/calcsRunecrafting.ts +++ b/src/lib/skilling/functions/calcsRunecrafting.ts @@ -30,7 +30,7 @@ export function raimentBonus(user: MUser, quantity: number): number { let bonusQuantity = 0; if ( user.hasEquippedOrInBank( - Object.keys(Runecraft.raimentsOfTheEyeItems).map(i => parseInt(i)), + Object.keys(Runecraft.raimentsOfTheEyeItems).map(i => Number.parseInt(i)), 'every' ) ) { @@ -39,7 +39,7 @@ export function raimentBonus(user: MUser, quantity: number): number { } else { // For each Raiments of the Eye item, check if they have it, give its' quantity boost if so (NO bonus XP). for (const [itemID, bonus] of Object.entries(Runecraft.raimentsOfTheEyeItems)) { - if (user.hasEquippedOrInBank(parseInt(itemID))) { + if (user.hasEquippedOrInBank(Number.parseInt(itemID))) { const amountToAdd = Math.floor(quantity * (bonus / 100)); bonusQuantity += amountToAdd; } diff --git a/src/lib/skilling/functions/determineMiningTime.ts b/src/lib/skilling/functions/determineMiningTime.ts index 2af461aa636..4db33950f5c 100644 --- a/src/lib/skilling/functions/determineMiningTime.ts +++ b/src/lib/skilling/functions/determineMiningTime.ts @@ -1,6 +1,5 @@ -import { percentChance, Time } from 'e'; - -import { Ore } from './../types'; +import { Time, percentChance } from 'e'; +import type { Ore } from '../types'; interface MiningTimeOptions { hasGlory: boolean; diff --git a/src/lib/skilling/functions/determineWoodcuttingTime.ts b/src/lib/skilling/functions/determineWoodcuttingTime.ts index 46ef2cb8cdf..952097b50fa 100644 --- a/src/lib/skilling/functions/determineWoodcuttingTime.ts +++ b/src/lib/skilling/functions/determineWoodcuttingTime.ts @@ -1,10 +1,10 @@ -import { percentChance, Time } from 'e'; +import { Time, percentChance } from 'e'; import { IVY_MAX_TRIP_LENGTH_BOOST } from '../../constants'; import { calcMaxTripLength } from '../../util/calcMaxTripLength'; import resolveItems from '../../util/resolveItems'; -import { MUserClass } from './../../MUser'; -import { Log } from './../types'; +import type { MUserClass } from './../../MUser'; +import type { Log } from './../types'; interface WoodcuttingTimeOptions { quantity: number | undefined; diff --git a/src/lib/skilling/functions/getFarmingInfo.ts b/src/lib/skilling/functions/getFarmingInfo.ts index 20125b37308..2440ff648d1 100644 --- a/src/lib/skilling/functions/getFarmingInfo.ts +++ b/src/lib/skilling/functions/getFarmingInfo.ts @@ -1,12 +1,13 @@ import { toTitleCase } from '@oldschoolgg/toolkit'; -import { User } from '@prisma/client'; +import type { User } from '@prisma/client'; import { Time } from 'e'; import { mahojiUsersSettingsFetch } from '../../../mahoji/mahojiSettings'; import { defaultPatches } from '../../minions/farming'; -import { IPatchData, IPatchDataDetailed } from '../../minions/farming/types'; +import type { IPatchData, IPatchDataDetailed } from '../../minions/farming/types'; import { assert, formatDuration } from '../../util'; -import { farmingKeys, FarmingPatchName, farmingPatchNames, findPlant } from '../../util/farmingHelpers'; +import type { FarmingPatchName } from '../../util/farmingHelpers'; +import { farmingKeys, farmingPatchNames, findPlant } from '../../util/farmingHelpers'; export function getFarmingInfoFromUser(user: User) { const patches: Record = {} as Record; @@ -62,7 +63,7 @@ export function getFarmingInfoFromUser(user: User) { } export async function getFarmingInfo(userID: string) { - let keys: Partial> = {}; + const keys: Partial> = {}; for (const key of farmingKeys) keys[key] = true; const userData = await mahojiUsersSettingsFetch(userID, keys); return getFarmingInfoFromUser(userData as User); diff --git a/src/lib/skilling/functions/miningBoosts.ts b/src/lib/skilling/functions/miningBoosts.ts index cb3ab9df189..4fd89bb0569 100644 --- a/src/lib/skilling/functions/miningBoosts.ts +++ b/src/lib/skilling/functions/miningBoosts.ts @@ -1,5 +1,3 @@ -import { Bank } from 'oldschooljs'; - import itemID from '../../util/itemID'; export const pickaxes = [ @@ -65,10 +63,10 @@ export const pickaxes = [ } ]; -export const miningGloves = [ +export const miningGloves: { id: number; Percentages: Record }[] = [ { id: itemID('Expert mining gloves'), - Percentages: new Bank({ + Percentages: { 'Silver ore': 50, Coal: 40, 'Gold ore': 33.33, @@ -76,11 +74,11 @@ export const miningGloves = [ 'Adamantite ore': 16.66, 'Runite ore': 12.5, Amethyst: 25 - }) + } }, { id: itemID('Superior mining gloves'), - Percentages: new Bank({ + Percentages: { 'Silver ore': 0, Coal: 0, 'Gold ore': 0, @@ -88,11 +86,11 @@ export const miningGloves = [ 'Adamantite ore': 16.66, 'Runite ore': 12.5, Amethyst: 0 - }) + } }, { id: itemID('Mining gloves'), - Percentages: new Bank({ + Percentages: { 'Silver ore': 50, Coal: 40, 'Gold ore': 33.33, @@ -100,14 +98,14 @@ export const miningGloves = [ 'Adamantite ore': 0, 'Runite ore': 0, Amethyst: 0 - }) + } } ]; -export const varrockArmours = [ +export const varrockArmours: { id: number; Percentages: Record }[] = [ { id: itemID('Varrock armour 4'), - Percentages: new Bank({ + Percentages: { Clay: 10, 'Copper ore': 10, 'Tin ore': 10, @@ -121,11 +119,11 @@ export const varrockArmours = [ 'Adamantite ore': 10, 'Runite ore': 10, Amethyst: 10 - }) + } }, { id: itemID('Varrock armour 3'), - Percentages: new Bank({ + Percentages: { Clay: 10, 'Copper ore': 10, 'Tin ore': 10, @@ -139,11 +137,11 @@ export const varrockArmours = [ 'Adamantite ore': 10, 'Runite ore': 0, Amethyst: 0 - }) + } }, { id: itemID('Varrock armour 2'), - Percentages: new Bank({ + Percentages: { Clay: 10, 'Copper ore': 10, 'Tin ore': 10, @@ -157,11 +155,11 @@ export const varrockArmours = [ 'Adamantite ore': 0, 'Runite ore': 0, Amethyst: 0 - }) + } }, { id: itemID('Varrock armour 1'), - Percentages: new Bank({ + Percentages: { Clay: 10, 'Copper ore': 10, 'Tin ore': 10, @@ -175,11 +173,11 @@ export const varrockArmours = [ 'Adamantite ore': 0, 'Runite ore': 0, Amethyst: 0 - }) + } } ]; -export const miningCapeOreEffect: Bank = new Bank({ +export const miningCapeOreEffect: Record = { Clay: 5, 'Copper ore': 5, 'Tin ore': 5, @@ -193,4 +191,4 @@ export const miningCapeOreEffect: Bank = new Bank({ 'Adamantite ore': 5, 'Runite ore': 0, Amethyst: 0 -}); +}; diff --git a/src/lib/skilling/functions/questRequirements.ts b/src/lib/skilling/functions/questRequirements.ts index 7f78d0b08d2..1b571e30288 100644 --- a/src/lib/skilling/functions/questRequirements.ts +++ b/src/lib/skilling/functions/questRequirements.ts @@ -1,4 +1,4 @@ -import { Skills } from '../../types'; +import type { Skills } from '../../types'; export const sinsOfTheFatherSkillRequirements: Skills = { woodcutting: 62, diff --git a/src/lib/skilling/skillcapes.ts b/src/lib/skilling/skillcapes.ts index aae075db52b..d26009d29fc 100644 --- a/src/lib/skilling/skillcapes.ts +++ b/src/lib/skilling/skillcapes.ts @@ -1,5 +1,5 @@ import { Bank } from 'oldschooljs'; -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; import getOSItem from '../util/getOSItem'; import itemID from '../util/itemID'; diff --git a/src/lib/skilling/skills/agility.ts b/src/lib/skilling/skills/agility.ts index 197f9dcadd6..caa22ae4018 100644 --- a/src/lib/skilling/skills/agility.ts +++ b/src/lib/skilling/skills/agility.ts @@ -1,5 +1,6 @@ import { Emoji } from '../../constants'; -import { Course, SkillsEnum } from '../types'; +import type { Course } from '../types'; +import { SkillsEnum } from '../types'; export const courses: Course[] = [ { @@ -195,7 +196,7 @@ export const gracefulItems = [ 'Agility cape(t)' ]; -export const MonkeyBackpacks = [ +const MonkeyBackpacks = [ { id: 24_862, name: 'Karamjan monkey', diff --git a/src/lib/skilling/skills/construction/constructables.ts b/src/lib/skilling/skills/construction/constructables.ts index 87d0dedd7cb..b54f2feb1df 100644 --- a/src/lib/skilling/skills/construction/constructables.ts +++ b/src/lib/skilling/skills/construction/constructables.ts @@ -3,20 +3,20 @@ import { itemID } from 'oldschooljs/dist/util'; interface Constructable { id: number; name: string; - input: [Plank, number]; + input: [number, number]; xp: number; level: number; nails?: number; ticks: number; } -export enum Plank { - Plank = itemID('Plank'), - OakPlank = itemID('Oak plank'), - TeakPlank = itemID('Teak plank'), - MahoganyPlank = itemID('Mahogany plank'), - ElderPlank = itemID('Elder plank') -} +export const Plank = { + Plank: itemID('Plank'), + OakPlank: itemID('Oak plank'), + TeakPlank: itemID('Teak plank'), + MahoganyPlank: itemID('Mahogany plank'), + ElderPlank: itemID('Elder plank') +} as const; const Constructables: Constructable[] = [ { diff --git a/src/lib/skilling/skills/cooking/cooking.ts b/src/lib/skilling/skills/cooking/cooking.ts index f77127cdade..9a2faa7555c 100644 --- a/src/lib/skilling/skills/cooking/cooking.ts +++ b/src/lib/skilling/skills/cooking/cooking.ts @@ -3,7 +3,8 @@ import { Bank } from 'oldschooljs'; import { removeDiscontinuedItems } from '../../../bso/bsoUtil'; import { Emoji } from '../../../constants'; import itemID from '../../../util/itemID'; -import { Cookable, SkillsEnum } from '../../types'; +import type { Cookable } from '../../types'; +import { SkillsEnum } from '../../types'; export const Cookables: Cookable[] = [ { diff --git a/src/lib/skilling/skills/cooking/leapingFish.ts b/src/lib/skilling/skills/cooking/leapingFish.ts index 1cc67c89d2b..8053ee9e742 100644 --- a/src/lib/skilling/skills/cooking/leapingFish.ts +++ b/src/lib/skilling/skills/cooking/leapingFish.ts @@ -1,5 +1,5 @@ import getOSItem from '../../../util/getOSItem'; -import { CutLeapingFish } from '../../types'; +import type { CutLeapingFish } from '../../types'; const LeapingFish: CutLeapingFish[] = [ { diff --git a/src/lib/skilling/skills/crafting/craftables/birdhouse.ts b/src/lib/skilling/skills/crafting/craftables/birdhouse.ts index 6e87cbbfa67..9bf3107b379 100644 --- a/src/lib/skilling/skills/crafting/craftables/birdhouse.ts +++ b/src/lib/skilling/skills/crafting/craftables/birdhouse.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import itemID from '../../../../util/itemID'; -import { Craftable } from '../../../types'; +import type { Craftable } from '../../../types'; const Birdhouse: Craftable[] = [ { diff --git a/src/lib/skilling/skills/crafting/craftables/built.ts b/src/lib/skilling/skills/crafting/craftables/built.ts index 3d310ee617d..0ebc0e00ad6 100644 --- a/src/lib/skilling/skills/crafting/craftables/built.ts +++ b/src/lib/skilling/skills/crafting/craftables/built.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import itemID from '../../../../util/itemID'; -import { Craftable } from '../../../types'; +import type { Craftable } from '../../../types'; const Built: Craftable[] = [ { diff --git a/src/lib/skilling/skills/crafting/craftables/carapace.ts b/src/lib/skilling/skills/crafting/craftables/carapace.ts index 0d021da9bed..29561410c46 100644 --- a/src/lib/skilling/skills/crafting/craftables/carapace.ts +++ b/src/lib/skilling/skills/crafting/craftables/carapace.ts @@ -1,54 +1,54 @@ import { Bank } from 'oldschooljs'; import itemID from '../../../../util/itemID'; -import { Craftable } from '../../../types'; +import type { Craftable } from '../../../types'; export const carapaceCraftables: Craftable[] = [ { - name: 'Carapace gloves', - id: itemID('Carapace gloves'), - level: 11, - xp: 23.8, - inputItems: new Bank({ Carapace: 1 }), + name: 'Carapace helm', + id: itemID('Carapace helm'), + level: 33, + xp: 24, + inputItems: new Bank({ Carapace: 2 }), tickRate: 3 }, { - name: 'Carapace boots', - id: itemID('Carapace boots'), - level: 15, - xp: 26.2, - inputItems: new Bank({ Carapace: 1 }), + name: 'Carapace torso', + id: itemID('Carapace torso'), + level: 35, + xp: 36, + inputItems: new Bank({ Carapace: 3 }), tickRate: 3 }, { - name: 'Carapace helm', - id: itemID('Carapace helm'), - level: 22, - xp: 28.5, - inputItems: new Bank({ Carapace: 1 }), + name: 'Carapace legs', + id: itemID('Carapace legs'), + level: 34, + xp: 24, + inputItems: new Bank({ Carapace: 2 }), tickRate: 3 }, { - name: 'Carapace gloves', - id: itemID('Carapace gloves'), - level: 32, - xp: 32, + name: 'Carapace boots', + id: itemID('Carapace boots'), + level: 31, + xp: 12, inputItems: new Bank({ Carapace: 1 }), tickRate: 3 }, { - name: 'Carapace torso', - id: itemID('Carapace torso'), - level: 35, - xp: 35, - inputItems: new Bank({ Carapace: 3 }), + name: 'Carapace gloves', + id: itemID('Carapace gloves'), + level: 30, + xp: 12, + inputItems: new Bank({ Carapace: 1 }), tickRate: 3 }, { - name: 'Carapace legs', - id: itemID('Carapace legs'), - level: 44, - xp: 37, + name: 'Carapace shield', + id: itemID('Carapace shield'), + level: 36, + xp: 36, inputItems: new Bank({ Carapace: 3 }), tickRate: 3 } diff --git a/src/lib/skilling/skills/crafting/craftables/custom.ts b/src/lib/skilling/skills/crafting/craftables/custom.ts index bcfd39d0079..c1887644b98 100644 --- a/src/lib/skilling/skills/crafting/craftables/custom.ts +++ b/src/lib/skilling/skills/crafting/craftables/custom.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import itemID from '../../../../util/itemID'; -import { Craftable } from '../../../types'; +import type { Craftable } from '../../../types'; import { carapaceCraftables } from './carapace'; export const customCraftables: Craftable[] = [ diff --git a/src/lib/skilling/skills/crafting/craftables/dragonhide.ts b/src/lib/skilling/skills/crafting/craftables/dragonhide.ts index 8c3baa40602..345a2dd933b 100644 --- a/src/lib/skilling/skills/crafting/craftables/dragonhide.ts +++ b/src/lib/skilling/skills/crafting/craftables/dragonhide.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import itemID from '../../../../util/itemID'; -import { Craftable } from '../../../types'; +import type { Craftable } from '../../../types'; const Dragonhide: Craftable[] = [ { diff --git a/src/lib/skilling/skills/crafting/craftables/gems.ts b/src/lib/skilling/skills/crafting/craftables/gems.ts index af49d99725a..b54f3e20e52 100644 --- a/src/lib/skilling/skills/crafting/craftables/gems.ts +++ b/src/lib/skilling/skills/crafting/craftables/gems.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import itemID from '../../../../util/itemID'; -import { Craftable } from '../../../types'; +import type { Craftable } from '../../../types'; const Gems: Craftable[] = [ { diff --git a/src/lib/skilling/skills/crafting/craftables/glassblowing.ts b/src/lib/skilling/skills/crafting/craftables/glassblowing.ts index 2eb881e78f8..9fd9027ad92 100644 --- a/src/lib/skilling/skills/crafting/craftables/glassblowing.ts +++ b/src/lib/skilling/skills/crafting/craftables/glassblowing.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import itemID from '../../../../util/itemID'; -import { Craftable } from '../../../types'; +import type { Craftable } from '../../../types'; const Glassblowing: Craftable[] = [ { diff --git a/src/lib/skilling/skills/crafting/craftables/gold.ts b/src/lib/skilling/skills/crafting/craftables/gold.ts index 0baa6981163..df1e0219b70 100644 --- a/src/lib/skilling/skills/crafting/craftables/gold.ts +++ b/src/lib/skilling/skills/crafting/craftables/gold.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import itemID from '../../../../util/itemID'; -import { Craftable } from '../../../types'; +import type { Craftable } from '../../../types'; const Gold: Craftable[] = [ { diff --git a/src/lib/skilling/skills/crafting/craftables/index.ts b/src/lib/skilling/skills/crafting/craftables/index.ts index b01c85b1a0c..a347188a77c 100644 --- a/src/lib/skilling/skills/crafting/craftables/index.ts +++ b/src/lib/skilling/skills/crafting/craftables/index.ts @@ -1,4 +1,4 @@ -import { Craftable } from '../../../types'; +import type { Craftable } from '../../../types'; import Birdhouse from './birdhouse'; import Built from './built'; import { customCraftables } from './custom'; diff --git a/src/lib/skilling/skills/crafting/craftables/leather.ts b/src/lib/skilling/skills/crafting/craftables/leather.ts index 93e8bce071f..6146b4861ac 100644 --- a/src/lib/skilling/skills/crafting/craftables/leather.ts +++ b/src/lib/skilling/skills/crafting/craftables/leather.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import itemID from '../../../../util/itemID'; -import { Craftable } from '../../../types'; +import type { Craftable } from '../../../types'; const Leather: Craftable[] = [ { diff --git a/src/lib/skilling/skills/crafting/craftables/misc.ts b/src/lib/skilling/skills/crafting/craftables/misc.ts index 485b4e0afa8..4647f4e2469 100644 --- a/src/lib/skilling/skills/crafting/craftables/misc.ts +++ b/src/lib/skilling/skills/crafting/craftables/misc.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import itemID from '../../../../util/itemID'; -import { Craftable } from '../../../types'; +import type { Craftable } from '../../../types'; const Misc: Craftable[] = [ { diff --git a/src/lib/skilling/skills/crafting/craftables/silver.ts b/src/lib/skilling/skills/crafting/craftables/silver.ts index bd370cba987..a7e6321cc9f 100644 --- a/src/lib/skilling/skills/crafting/craftables/silver.ts +++ b/src/lib/skilling/skills/crafting/craftables/silver.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import itemID from '../../../../util/itemID'; -import { Craftable } from '../../../types'; +import type { Craftable } from '../../../types'; const Silver: Craftable[] = [ { diff --git a/src/lib/skilling/skills/crafting/craftables/tanning.ts b/src/lib/skilling/skills/crafting/craftables/tanning.ts index 5e0cb8efd3e..55be88b211c 100644 --- a/src/lib/skilling/skills/crafting/craftables/tanning.ts +++ b/src/lib/skilling/skills/crafting/craftables/tanning.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import itemID from '../../../../util/itemID'; -import { Craftable } from '../../../types'; +import type { Craftable } from '../../../types'; const Tanning: Craftable[] = [ { diff --git a/src/lib/skilling/skills/dung/dungDbFunctions.ts b/src/lib/skilling/skills/dung/dungDbFunctions.ts index 134206edbf6..b317e4708fd 100644 --- a/src/lib/skilling/skills/dung/dungDbFunctions.ts +++ b/src/lib/skilling/skills/dung/dungDbFunctions.ts @@ -30,7 +30,7 @@ export function calcGorajanShardChance({ dungLevel: number; hasRingOfLuck: boolean; }) { - let goraShardBoosts = []; + const goraShardBoosts = []; let baseRate = 2000; if (hasMasterCape) { baseRate /= 2; diff --git a/src/lib/skilling/skills/farming/allotments.ts b/src/lib/skilling/skills/farming/allotments.ts index f682a92157f..9fa02fcad8d 100644 --- a/src/lib/skilling/skills/farming/allotments.ts +++ b/src/lib/skilling/skills/farming/allotments.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import itemID from '../../../util/itemID'; -import { Plant } from '../../types'; +import type { Plant } from '../../types'; const allotmentPlants: Plant[] = [ { diff --git a/src/lib/skilling/skills/farming/bushes.ts b/src/lib/skilling/skills/farming/bushes.ts index a81cc7d7ac9..5e7e8eaeb2e 100644 --- a/src/lib/skilling/skills/farming/bushes.ts +++ b/src/lib/skilling/skills/farming/bushes.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import { itemID } from 'oldschooljs/dist/util'; -import { Plant } from '../../types'; +import type { Plant } from '../../types'; export const bushes: Plant[] = [ { diff --git a/src/lib/skilling/skills/farming/fruitTrees.ts b/src/lib/skilling/skills/farming/fruitTrees.ts index 5e0590df1de..ff713342449 100644 --- a/src/lib/skilling/skills/farming/fruitTrees.ts +++ b/src/lib/skilling/skills/farming/fruitTrees.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import itemID from '../../../util/itemID'; -import { Plant } from '../../types'; +import type { Plant } from '../../types'; const fruitTrees: Plant[] = [ { diff --git a/src/lib/skilling/skills/farming/herbPlants.ts b/src/lib/skilling/skills/farming/herbPlants.ts index 43a6b7ab66f..bef210195ec 100644 --- a/src/lib/skilling/skills/farming/herbPlants.ts +++ b/src/lib/skilling/skills/farming/herbPlants.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import itemID from '../../../util/itemID'; -import { Plant } from '../../types'; +import type { Plant } from '../../types'; const herbPlants: Plant[] = [ { diff --git a/src/lib/skilling/skills/farming/hops.ts b/src/lib/skilling/skills/farming/hops.ts index fbac0155745..4bc64786b47 100644 --- a/src/lib/skilling/skills/farming/hops.ts +++ b/src/lib/skilling/skills/farming/hops.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import itemID from '../../../util/itemID'; -import { Plant } from '../../types'; +import type { Plant } from '../../types'; const hopsPlants: Plant[] = [ { diff --git a/src/lib/skilling/skills/farming/index.ts b/src/lib/skilling/skills/farming/index.ts index a69dbdea773..3d7324c6f7f 100644 --- a/src/lib/skilling/skills/farming/index.ts +++ b/src/lib/skilling/skills/farming/index.ts @@ -4,7 +4,7 @@ import { Emoji } from '../../../constants'; import getOSItem from '../../../util/getOSItem'; import itemID from '../../../util/itemID'; import resolveItems from '../../../util/resolveItems'; -import { Plant, SkillsEnum } from '../../types'; +import { type Plant, SkillsEnum } from '../../types'; import allotmentPlants from './allotments'; import { bushes } from './bushes'; import fruitTrees from './fruitTrees'; diff --git a/src/lib/skilling/skills/farming/specialPlants.ts b/src/lib/skilling/skills/farming/specialPlants.ts index 712abb0d336..10fdd839b6e 100644 --- a/src/lib/skilling/skills/farming/specialPlants.ts +++ b/src/lib/skilling/skills/farming/specialPlants.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import itemID from '../../../util/itemID'; -import { Plant } from '../../types'; +import type { Plant } from '../../types'; const specialPlants: Plant[] = [ { diff --git a/src/lib/skilling/skills/farming/trees.ts b/src/lib/skilling/skills/farming/trees.ts index 667c567c805..73343923ea5 100644 --- a/src/lib/skilling/skills/farming/trees.ts +++ b/src/lib/skilling/skills/farming/trees.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import itemID from '../../../util/itemID'; -import { Plant } from '../../types'; +import type { Plant } from '../../types'; const trees: Plant[] = [ { diff --git a/src/lib/skilling/skills/farming/zygomites.ts b/src/lib/skilling/skills/farming/zygomites.ts index 256d019a351..4d1d5aef40a 100644 --- a/src/lib/skilling/skills/farming/zygomites.ts +++ b/src/lib/skilling/skills/farming/zygomites.ts @@ -1,14 +1,14 @@ import { SimpleTable } from '@oldschoolgg/toolkit'; import { randArrItem, roll } from 'e'; import { Bank, LootTable } from 'oldschooljs'; -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; import { MysteryBoxes } from '../../../bsoOpenables'; import { BitField } from '../../../constants'; import { globalDroprates } from '../../../data/globalDroprates'; import { clAdjustedDroprate } from '../../../util'; import getOSItem from '../../../util/getOSItem'; -import { Plant } from '../../types'; +import type { Plant } from '../../types'; export const zygomiteSeedMutChance = 15; diff --git a/src/lib/skilling/skills/firemaking.ts b/src/lib/skilling/skills/firemaking.ts index bb1d485dd42..9a940f8b7ae 100644 --- a/src/lib/skilling/skills/firemaking.ts +++ b/src/lib/skilling/skills/firemaking.ts @@ -1,6 +1,7 @@ import { Emoji } from '../../constants'; import itemID from '../../util/itemID'; -import { Burnable, SkillsEnum } from '../types'; +import type { Burnable } from '../types'; +import { SkillsEnum } from '../types'; const burnables: Burnable[] = [ { diff --git a/src/lib/skilling/skills/fishing.ts b/src/lib/skilling/skills/fishing.ts index da59b269951..e38cd01ca8e 100644 --- a/src/lib/skilling/skills/fishing.ts +++ b/src/lib/skilling/skills/fishing.ts @@ -1,6 +1,7 @@ import { Emoji } from '../../constants'; import itemID from '../../util/itemID'; -import { Fish, SkillsEnum } from '../types'; +import type { Fish } from '../types'; +import { SkillsEnum } from '../types'; const fishes: Fish[] = [ { diff --git a/src/lib/skilling/skills/fletching/fletchables/arrows.ts b/src/lib/skilling/skills/fletching/fletchables/arrows.ts index e5f85edcc36..41637a0540e 100644 --- a/src/lib/skilling/skills/fletching/fletchables/arrows.ts +++ b/src/lib/skilling/skills/fletching/fletchables/arrows.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import itemID from '../../../../util/itemID'; -import { Fletchable } from '../../../types'; +import type { Fletchable } from '../../../types'; const Arrows: Fletchable[] = [ { diff --git a/src/lib/skilling/skills/fletching/fletchables/bolts.ts b/src/lib/skilling/skills/fletching/fletchables/bolts.ts index e80949e2bcd..9391fc4ca27 100644 --- a/src/lib/skilling/skills/fletching/fletchables/bolts.ts +++ b/src/lib/skilling/skills/fletching/fletchables/bolts.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import itemID from '../../../../util/itemID'; -import { Fletchable } from '../../../types'; +import type { Fletchable } from '../../../types'; const Bolts: Fletchable[] = [ { diff --git a/src/lib/skilling/skills/fletching/fletchables/bows.ts b/src/lib/skilling/skills/fletching/fletchables/bows.ts index d8b6e8da4ee..9fd8173e8f0 100644 --- a/src/lib/skilling/skills/fletching/fletchables/bows.ts +++ b/src/lib/skilling/skills/fletching/fletchables/bows.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import itemID from '../../../../util/itemID'; -import { Fletchable } from '../../../types'; +import type { Fletchable } from '../../../types'; const Bows: Fletchable[] = [ { diff --git a/src/lib/skilling/skills/fletching/fletchables/crossbows.ts b/src/lib/skilling/skills/fletching/fletchables/crossbows.ts index b9015f22490..f8997e28562 100644 --- a/src/lib/skilling/skills/fletching/fletchables/crossbows.ts +++ b/src/lib/skilling/skills/fletching/fletchables/crossbows.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import itemID from '../../../../util/itemID'; -import { Fletchable } from '../../../types'; +import type { Fletchable } from '../../../types'; const Crossbows: Fletchable[] = [ // stocks diff --git a/src/lib/skilling/skills/fletching/fletchables/index.ts b/src/lib/skilling/skills/fletching/fletchables/index.ts index fd1c7b71afb..097573438c2 100644 --- a/src/lib/skilling/skills/fletching/fletchables/index.ts +++ b/src/lib/skilling/skills/fletching/fletchables/index.ts @@ -1,4 +1,4 @@ -import { Fletchable } from '../../../types'; +import type { Fletchable } from '../../../types'; import Arrows from './arrows'; import Bolts from './bolts'; import Bows from './bows'; diff --git a/src/lib/skilling/skills/fletching/fletchables/javelins.ts b/src/lib/skilling/skills/fletching/fletchables/javelins.ts index 6322a6438c9..d27132cb042 100644 --- a/src/lib/skilling/skills/fletching/fletchables/javelins.ts +++ b/src/lib/skilling/skills/fletching/fletchables/javelins.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import itemID from '../../../../util/itemID'; -import { Fletchable } from '../../../types'; +import type { Fletchable } from '../../../types'; const Javelins: Fletchable[] = [ { diff --git a/src/lib/skilling/skills/fletching/fletchables/shafts.ts b/src/lib/skilling/skills/fletching/fletchables/shafts.ts index 7967678f910..9aaef20455a 100644 --- a/src/lib/skilling/skills/fletching/fletchables/shafts.ts +++ b/src/lib/skilling/skills/fletching/fletchables/shafts.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import itemID from '../../../../util/itemID'; -import { Fletchable } from '../../../types'; +import type { Fletchable } from '../../../types'; const Shafts: Fletchable[] = [ { diff --git a/src/lib/skilling/skills/fletching/fletchables/shields.ts b/src/lib/skilling/skills/fletching/fletchables/shields.ts index 451faad272c..91a5903cf86 100644 --- a/src/lib/skilling/skills/fletching/fletchables/shields.ts +++ b/src/lib/skilling/skills/fletching/fletchables/shields.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import itemID from '../../../../util/itemID'; -import { Fletchable } from '../../../types'; +import type { Fletchable } from '../../../types'; const Shields: Fletchable[] = [ { diff --git a/src/lib/skilling/skills/fletching/fletchables/slayer.ts b/src/lib/skilling/skills/fletching/fletchables/slayer.ts index 6490026b32f..f97f0285ed9 100644 --- a/src/lib/skilling/skills/fletching/fletchables/slayer.ts +++ b/src/lib/skilling/skills/fletching/fletchables/slayer.ts @@ -2,7 +2,7 @@ import { Bank } from 'oldschooljs'; import { SlayerTaskUnlocksEnum } from '../../../../slayer/slayerUnlocks'; import itemID from '../../../../util/itemID'; -import { Fletchable } from '../../../types'; +import type { Fletchable } from '../../../types'; const Slayer: Fletchable[] = [ { diff --git a/src/lib/skilling/skills/fletching/fletchables/tippedBolts.ts b/src/lib/skilling/skills/fletching/fletchables/tippedBolts.ts index 21b26db611f..53441c42fb4 100644 --- a/src/lib/skilling/skills/fletching/fletchables/tippedBolts.ts +++ b/src/lib/skilling/skills/fletching/fletchables/tippedBolts.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import itemID from '../../../../util/itemID'; -import { Fletchable } from '../../../types'; +import type { Fletchable } from '../../../types'; const TippedBolts: Fletchable[] = [ { diff --git a/src/lib/skilling/skills/fletching/fletchables/tippedDragonBolts.ts b/src/lib/skilling/skills/fletching/fletchables/tippedDragonBolts.ts index 2a39f95991c..44ad4546665 100644 --- a/src/lib/skilling/skills/fletching/fletchables/tippedDragonBolts.ts +++ b/src/lib/skilling/skills/fletching/fletchables/tippedDragonBolts.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import itemID from '../../../../util/itemID'; -import { Fletchable } from '../../../types'; +import type { Fletchable } from '../../../types'; const TippedDragonBolts: Fletchable[] = [ { diff --git a/src/lib/skilling/skills/fletching/fletchables/tips.ts b/src/lib/skilling/skills/fletching/fletchables/tips.ts index 8742a7a791a..54d991b13e8 100644 --- a/src/lib/skilling/skills/fletching/fletchables/tips.ts +++ b/src/lib/skilling/skills/fletching/fletchables/tips.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import itemID from '../../../../util/itemID'; -import { Fletchable } from '../../../types'; +import type { Fletchable } from '../../../types'; const Tips: Fletchable[] = [ { diff --git a/src/lib/skilling/skills/herblore/mixables/barbMixes.ts b/src/lib/skilling/skills/herblore/mixables/barbMixes.ts index adb2cecbf04..6437dd144ed 100644 --- a/src/lib/skilling/skills/herblore/mixables/barbMixes.ts +++ b/src/lib/skilling/skills/herblore/mixables/barbMixes.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import getOSItem from '../../../../util/getOSItem'; -import { Mixable } from '../../../types'; +import type { Mixable } from '../../../types'; export const barbMixes: Mixable[] = [ { @@ -33,10 +33,7 @@ export const barbMixes: Mixable[] = [ aliases: ['Relicyms mix roe', 'Relicyms mix(2)', 'Relicyms mix 2 roe'], level: 9, xp: 14, - inputItems: new Bank({ - 4846: 1, - Roe: 1 - }), + inputItems: new Bank().add("Relicym's balm(2)").add('Roe'), tickRate: 1, bankTimePerPotion: 0.088 }, diff --git a/src/lib/skilling/skills/herblore/mixables/bsoMixables.ts b/src/lib/skilling/skills/herblore/mixables/bsoMixables.ts index 7fa851d4633..05ddb5739c9 100644 --- a/src/lib/skilling/skills/herblore/mixables/bsoMixables.ts +++ b/src/lib/skilling/skills/herblore/mixables/bsoMixables.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import getOSItem from '../../../../util/getOSItem'; -import { Mixable } from '../../../types'; +import type { Mixable } from '../../../types'; export const bsoMixables: Mixable[] = [ { diff --git a/src/lib/skilling/skills/herblore/mixables/crush.ts b/src/lib/skilling/skills/herblore/mixables/crush.ts index 7133ea37009..cb18c6e5201 100644 --- a/src/lib/skilling/skills/herblore/mixables/crush.ts +++ b/src/lib/skilling/skills/herblore/mixables/crush.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import getOSItem from '../../../../util/getOSItem'; -import { Mixable } from '../../../types'; +import type { Mixable } from '../../../types'; const Crush: Mixable[] = [ { diff --git a/src/lib/skilling/skills/herblore/mixables/grimy.ts b/src/lib/skilling/skills/herblore/mixables/grimy.ts index de52c82f15c..ca4e7d24d72 100644 --- a/src/lib/skilling/skills/herblore/mixables/grimy.ts +++ b/src/lib/skilling/skills/herblore/mixables/grimy.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import getOSItem from '../../../../util/getOSItem'; -import { Mixable } from '../../../types'; +import type { Mixable } from '../../../types'; const Grimy: Mixable[] = [ { diff --git a/src/lib/skilling/skills/herblore/mixables/index.ts b/src/lib/skilling/skills/herblore/mixables/index.ts index 00b51ed4eb9..31c9b6ff3c8 100644 --- a/src/lib/skilling/skills/herblore/mixables/index.ts +++ b/src/lib/skilling/skills/herblore/mixables/index.ts @@ -1,4 +1,4 @@ -import { Mixable } from '../../../types'; +import type { Mixable } from '../../../types'; import { barbMixes } from './barbMixes'; import { bsoMixables } from './bsoMixables'; import Crush from './crush'; diff --git a/src/lib/skilling/skills/herblore/mixables/potions.ts b/src/lib/skilling/skills/herblore/mixables/potions.ts index 7f3323f834d..1335916faf3 100644 --- a/src/lib/skilling/skills/herblore/mixables/potions.ts +++ b/src/lib/skilling/skills/herblore/mixables/potions.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import getOSItem from '../../../../util/getOSItem'; -import { Mixable } from '../../../types'; +import type { Mixable } from '../../../types'; const Potions: Mixable[] = [ { diff --git a/src/lib/skilling/skills/herblore/mixables/tar.ts b/src/lib/skilling/skills/herblore/mixables/tar.ts index 299c43786ad..c662cb92819 100644 --- a/src/lib/skilling/skills/herblore/mixables/tar.ts +++ b/src/lib/skilling/skills/herblore/mixables/tar.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import getOSItem from '../../../../util/getOSItem'; -import { Mixable } from '../../../types'; +import type { Mixable } from '../../../types'; const Tar: Mixable[] = [ { diff --git a/src/lib/skilling/skills/herblore/mixables/unfinishedPotions.ts b/src/lib/skilling/skills/herblore/mixables/unfinishedPotions.ts index 91b448ca057..cd587e02691 100644 --- a/src/lib/skilling/skills/herblore/mixables/unfinishedPotions.ts +++ b/src/lib/skilling/skills/herblore/mixables/unfinishedPotions.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import getOSItem from '../../../../util/getOSItem'; -import { Mixable } from '../../../types'; +import type { Mixable } from '../../../types'; const unfinishedPotions: Mixable[] = [ { diff --git a/src/lib/skilling/skills/hunter/aerialFishing.ts b/src/lib/skilling/skills/hunter/aerialFishing.ts index cbeaae8fedb..702baa49a93 100644 --- a/src/lib/skilling/skills/hunter/aerialFishing.ts +++ b/src/lib/skilling/skills/hunter/aerialFishing.ts @@ -1,6 +1,7 @@ import LootTable from 'oldschooljs/dist/structures/LootTable'; -import { Creature, HunterTechniqueEnum } from '../../types'; +import type { Creature } from '../../types'; +import { HunterTechniqueEnum } from '../../types'; const aerialFishingCreatures: Creature[] = [ { diff --git a/src/lib/skilling/skills/hunter/creatures/birdSnaring.ts b/src/lib/skilling/skills/hunter/creatures/birdSnaring.ts index c2468c41340..5abb41dd8b8 100644 --- a/src/lib/skilling/skills/hunter/creatures/birdSnaring.ts +++ b/src/lib/skilling/skills/hunter/creatures/birdSnaring.ts @@ -1,6 +1,7 @@ import LootTable from 'oldschooljs/dist/structures/LootTable'; -import { Creature, HunterTechniqueEnum } from '../../../types'; +import type { Creature } from '../../../types'; +import { HunterTechniqueEnum } from '../../../types'; const birdSnaringCreatures: Creature[] = [ { diff --git a/src/lib/skilling/skills/hunter/creatures/boxTrapping.ts b/src/lib/skilling/skills/hunter/creatures/boxTrapping.ts index 1ddeae318de..1ed564d1cde 100644 --- a/src/lib/skilling/skills/hunter/creatures/boxTrapping.ts +++ b/src/lib/skilling/skills/hunter/creatures/boxTrapping.ts @@ -1,6 +1,7 @@ import LootTable from 'oldschooljs/dist/structures/LootTable'; -import { Creature, HunterTechniqueEnum } from '../../../types'; +import type { Creature } from '../../../types'; +import { HunterTechniqueEnum } from '../../../types'; const boxTrappingCreatures: Creature[] = [ { diff --git a/src/lib/skilling/skills/hunter/creatures/bso.ts b/src/lib/skilling/skills/hunter/creatures/bso.ts index 7a88cdfe9b7..b183ca5cf24 100644 --- a/src/lib/skilling/skills/hunter/creatures/bso.ts +++ b/src/lib/skilling/skills/hunter/creatures/bso.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import LootTable from 'oldschooljs/dist/structures/LootTable'; -import { Creature, HunterTechniqueEnum } from '../../../types'; +import { type Creature, HunterTechniqueEnum } from '../../../types'; const customBSOCreatures: Creature[] = [ { @@ -28,7 +28,7 @@ const customBSOCreatures: Creature[] = [ slope: 0, intercept: 99, bait: qty => { - let req = new Bank(); + const req = new Bank(); const kibbleRequired = Math.ceil(qty / 8); req.add('Simple kibble', kibbleRequired); return req; @@ -47,7 +47,7 @@ const customBSOCreatures: Creature[] = [ slope: 0, intercept: 99, bait: qty => { - let req = new Bank(); + const req = new Bank(); const kibbleRequired = Math.ceil(qty / 2); req.add('Delicious kibble', kibbleRequired); return req; diff --git a/src/lib/skilling/skills/hunter/creatures/butterflyNetting.ts b/src/lib/skilling/skills/hunter/creatures/butterflyNetting.ts index 722e3afde5f..03cf5a8967b 100644 --- a/src/lib/skilling/skills/hunter/creatures/butterflyNetting.ts +++ b/src/lib/skilling/skills/hunter/creatures/butterflyNetting.ts @@ -1,7 +1,8 @@ import { Bank } from 'oldschooljs'; import LootTable from 'oldschooljs/dist/structures/LootTable'; -import { Creature, HunterTechniqueEnum } from '../../../types'; +import type { Creature } from '../../../types'; +import { HunterTechniqueEnum } from '../../../types'; const butterflyNettingCreatures: Creature[] = [ { diff --git a/src/lib/skilling/skills/hunter/creatures/deadfallTrapping.ts b/src/lib/skilling/skills/hunter/creatures/deadfallTrapping.ts index 66540496e8a..afc390ec5a2 100644 --- a/src/lib/skilling/skills/hunter/creatures/deadfallTrapping.ts +++ b/src/lib/skilling/skills/hunter/creatures/deadfallTrapping.ts @@ -1,7 +1,8 @@ import { Bank } from 'oldschooljs'; import LootTable from 'oldschooljs/dist/structures/LootTable'; -import { Creature, HunterTechniqueEnum } from '../../../types'; +import type { Creature } from '../../../types'; +import { HunterTechniqueEnum } from '../../../types'; const deadfallTrappingCreatures: Creature[] = [ { diff --git a/src/lib/skilling/skills/hunter/creatures/falconry.ts b/src/lib/skilling/skills/hunter/creatures/falconry.ts index 911a51ed0c7..b539454b902 100644 --- a/src/lib/skilling/skills/hunter/creatures/falconry.ts +++ b/src/lib/skilling/skills/hunter/creatures/falconry.ts @@ -1,6 +1,7 @@ import LootTable from 'oldschooljs/dist/structures/LootTable'; -import { Creature, HunterTechniqueEnum } from '../../../types'; +import type { Creature } from '../../../types'; +import { HunterTechniqueEnum } from '../../../types'; const falconryCreatures: Creature[] = [ { diff --git a/src/lib/skilling/skills/hunter/creatures/index.ts b/src/lib/skilling/skills/hunter/creatures/index.ts index f7cdb5a302b..acb3aab20e1 100644 --- a/src/lib/skilling/skills/hunter/creatures/index.ts +++ b/src/lib/skilling/skills/hunter/creatures/index.ts @@ -1,4 +1,4 @@ -import { Creature } from '../../../types'; +import type { Creature } from '../../../types'; import birdSnaringCreatures from './birdSnaring'; import boxTrappingCreatures from './boxTrapping'; import customBSOCreatures from './bso'; diff --git a/src/lib/skilling/skills/hunter/creatures/magicBoxTrapping.ts b/src/lib/skilling/skills/hunter/creatures/magicBoxTrapping.ts index fed6d5aaf03..5c6ba991581 100644 --- a/src/lib/skilling/skills/hunter/creatures/magicBoxTrapping.ts +++ b/src/lib/skilling/skills/hunter/creatures/magicBoxTrapping.ts @@ -1,7 +1,8 @@ import { Bank } from 'oldschooljs'; import LootTable from 'oldschooljs/dist/structures/LootTable'; -import { Creature, HunterTechniqueEnum } from '../../../types'; +import type { Creature } from '../../../types'; +import { HunterTechniqueEnum } from '../../../types'; const magicBoxTrappingCreatures: Creature[] = [ { diff --git a/src/lib/skilling/skills/hunter/creatures/netTrapping.ts b/src/lib/skilling/skills/hunter/creatures/netTrapping.ts index d255b77d58d..923a2493517 100644 --- a/src/lib/skilling/skills/hunter/creatures/netTrapping.ts +++ b/src/lib/skilling/skills/hunter/creatures/netTrapping.ts @@ -1,6 +1,7 @@ import LootTable from 'oldschooljs/dist/structures/LootTable'; -import { Creature, HunterTechniqueEnum } from '../../../types'; +import type { Creature } from '../../../types'; +import { HunterTechniqueEnum } from '../../../types'; const netTrappingCreatures: Creature[] = [ { diff --git a/src/lib/skilling/skills/hunter/creatures/pitfallTrapping.ts b/src/lib/skilling/skills/hunter/creatures/pitfallTrapping.ts index dca987aa457..78202bdf99d 100644 --- a/src/lib/skilling/skills/hunter/creatures/pitfallTrapping.ts +++ b/src/lib/skilling/skills/hunter/creatures/pitfallTrapping.ts @@ -1,6 +1,7 @@ import LootTable from 'oldschooljs/dist/structures/LootTable'; -import { Creature, HunterTechniqueEnum } from '../../../types'; +import type { Creature } from '../../../types'; +import { HunterTechniqueEnum } from '../../../types'; const pitfallTrappingCreatures: Creature[] = [ { diff --git a/src/lib/skilling/skills/hunter/creatures/rabbitSnaring.ts b/src/lib/skilling/skills/hunter/creatures/rabbitSnaring.ts index 9e94bffac2c..cc81a48c34a 100644 --- a/src/lib/skilling/skills/hunter/creatures/rabbitSnaring.ts +++ b/src/lib/skilling/skills/hunter/creatures/rabbitSnaring.ts @@ -1,7 +1,8 @@ import { Bank } from 'oldschooljs'; import LootTable from 'oldschooljs/dist/structures/LootTable'; -import { Creature, HunterTechniqueEnum } from '../../../types'; +import type { Creature } from '../../../types'; +import { HunterTechniqueEnum } from '../../../types'; const rabbitSnaringCreatures: Creature[] = [ { diff --git a/src/lib/skilling/skills/hunter/creatures/tracking.ts b/src/lib/skilling/skills/hunter/creatures/tracking.ts index 8eac1ff2b17..fd1067f2f3c 100644 --- a/src/lib/skilling/skills/hunter/creatures/tracking.ts +++ b/src/lib/skilling/skills/hunter/creatures/tracking.ts @@ -1,6 +1,7 @@ import LootTable from 'oldschooljs/dist/structures/LootTable'; -import { Creature, HunterTechniqueEnum } from '../../../types'; +import type { Creature } from '../../../types'; +import { HunterTechniqueEnum } from '../../../types'; const trackingCreatures: Creature[] = [ { diff --git a/src/lib/skilling/skills/hunter/driftNet.ts b/src/lib/skilling/skills/hunter/driftNet.ts index d6d2cfcc4f3..80afdc867e0 100644 --- a/src/lib/skilling/skills/hunter/driftNet.ts +++ b/src/lib/skilling/skills/hunter/driftNet.ts @@ -1,6 +1,7 @@ import LootTable from 'oldschooljs/dist/structures/LootTable'; -import { Creature, HunterTechniqueEnum } from '../../types'; +import type { Creature } from '../../types'; +import { HunterTechniqueEnum } from '../../types'; const driftNetCreatures: Creature[] = [ { diff --git a/src/lib/skilling/skills/index.ts b/src/lib/skilling/skills/index.ts index 8fdde858645..287fe8429de 100644 --- a/src/lib/skilling/skills/index.ts +++ b/src/lib/skilling/skills/index.ts @@ -1,6 +1,6 @@ import { Emoji } from '../../constants'; import { skillEmoji } from '../../data/emojis'; -import { Skill, SkillsEnum } from '../types'; +import { type Skill, SkillsEnum } from '../types'; import Agility from './agility'; import Construction from './construction'; import Cooking from './cooking/cooking'; diff --git a/src/lib/skilling/skills/mining.ts b/src/lib/skilling/skills/mining.ts index a0f4c0ba117..35d5a7a8ac9 100644 --- a/src/lib/skilling/skills/mining.ts +++ b/src/lib/skilling/skills/mining.ts @@ -3,7 +3,7 @@ import LootTable from 'oldschooljs/dist/structures/LootTable'; import { Emoji } from '../../constants'; import itemID from '../../util/itemID'; import resolveItems from '../../util/resolveItems'; -import { Ore, SkillsEnum } from '../types'; +import { type Ore, SkillsEnum } from '../types'; export const GemRockTable = new LootTable() .add('Uncut opal', 1, 60) diff --git a/src/lib/skilling/skills/prayer.ts b/src/lib/skilling/skills/prayer.ts index e7fdcf39020..c63d8ceb426 100644 --- a/src/lib/skilling/skills/prayer.ts +++ b/src/lib/skilling/skills/prayer.ts @@ -1,6 +1,7 @@ import { Emoji } from '../../constants'; import itemID from '../../util/itemID'; -import { Ash, Bone, SkillsEnum } from '../types'; +import type { Ash, Bone } from '../types'; +import { SkillsEnum } from '../types'; export const bones: Bone[] = [ { diff --git a/src/lib/skilling/skills/runecraft.ts b/src/lib/skilling/skills/runecraft.ts index 297b590b276..0bbea6cdebe 100644 --- a/src/lib/skilling/skills/runecraft.ts +++ b/src/lib/skilling/skills/runecraft.ts @@ -17,7 +17,7 @@ export interface Rune { stams?: boolean; } -export interface Tiara { +interface Tiara { xp: number; id: number; name: string; diff --git a/src/lib/skilling/skills/smithing/blastables.ts b/src/lib/skilling/skills/smithing/blastables.ts index 6c88afafb97..c7570433d11 100644 --- a/src/lib/skilling/skills/smithing/blastables.ts +++ b/src/lib/skilling/skills/smithing/blastables.ts @@ -2,7 +2,7 @@ import { Time } from 'e'; import { Bank } from 'oldschooljs'; import itemID from '../../../util/itemID'; -import { BlastableBar } from '../../types'; +import type { BlastableBar } from '../../types'; const BlastableBars: BlastableBar[] = [ { diff --git a/src/lib/skilling/skills/smithing/smeltables.ts b/src/lib/skilling/skills/smithing/smeltables.ts index 645e84d93be..c599ee3794f 100644 --- a/src/lib/skilling/skills/smithing/smeltables.ts +++ b/src/lib/skilling/skills/smithing/smeltables.ts @@ -2,7 +2,7 @@ import { Time } from 'e'; import { Bank } from 'oldschooljs'; import itemID from '../../../util/itemID'; -import { Bar } from '../../types'; +import type { Bar } from '../../types'; const Bars: Bar[] = [ { diff --git a/src/lib/skilling/skills/smithing/smithables/adamant.ts b/src/lib/skilling/skills/smithing/smithables/adamant.ts index 0d33acba648..354b0e1e4fe 100644 --- a/src/lib/skilling/skills/smithing/smithables/adamant.ts +++ b/src/lib/skilling/skills/smithing/smithables/adamant.ts @@ -1,7 +1,7 @@ import { Time } from 'e'; import itemID from '../../../../util/itemID'; -import { SmithedItem } from '../../../types'; +import type { SmithedItem } from '../../../types'; const Adamant: SmithedItem[] = [ { diff --git a/src/lib/skilling/skills/smithing/smithables/bronze.ts b/src/lib/skilling/skills/smithing/smithables/bronze.ts index 214fa97e578..ef24d81b756 100644 --- a/src/lib/skilling/skills/smithing/smithables/bronze.ts +++ b/src/lib/skilling/skills/smithing/smithables/bronze.ts @@ -1,7 +1,7 @@ import { Time } from 'e'; import itemID from '../../../../util/itemID'; -import { SmithedItem } from '../../../types'; +import type { SmithedItem } from '../../../types'; const Bronze: SmithedItem[] = [ { diff --git a/src/lib/skilling/skills/smithing/smithables/bsoSmithables.ts b/src/lib/skilling/skills/smithing/smithables/bsoSmithables.ts index 366ea7d4ce5..5417f95b323 100644 --- a/src/lib/skilling/skills/smithing/smithables/bsoSmithables.ts +++ b/src/lib/skilling/skills/smithing/smithables/bsoSmithables.ts @@ -1,7 +1,7 @@ import { Time } from 'e'; import itemID from '../../../../util/itemID'; -import { SmithedItem } from '../../../types'; +import type { SmithedItem } from '../../../types'; const BSOSmithables: SmithedItem[] = [ { diff --git a/src/lib/skilling/skills/smithing/smithables/dwarven.ts b/src/lib/skilling/skills/smithing/smithables/dwarven.ts index 2e247e651a4..09158cd94b6 100644 --- a/src/lib/skilling/skills/smithing/smithables/dwarven.ts +++ b/src/lib/skilling/skills/smithing/smithables/dwarven.ts @@ -1,7 +1,7 @@ import { Time } from 'e'; import itemID from '../../../../util/itemID'; -import { SmithedItem } from '../../../types'; +import type { SmithedItem } from '../../../types'; const Dwarven: SmithedItem[] = [ { diff --git a/src/lib/skilling/skills/smithing/smithables/gold.ts b/src/lib/skilling/skills/smithing/smithables/gold.ts index 71853cc9071..a4c952fd299 100644 --- a/src/lib/skilling/skills/smithing/smithables/gold.ts +++ b/src/lib/skilling/skills/smithing/smithables/gold.ts @@ -1,7 +1,7 @@ import { Time } from 'e'; import itemID from '../../../../util/itemID'; -import { SmithedItem } from '../../../types'; +import type { SmithedItem } from '../../../types'; const Gold: SmithedItem[] = [ { diff --git a/src/lib/skilling/skills/smithing/smithables/gorajan.ts b/src/lib/skilling/skills/smithing/smithables/gorajan.ts index d8405c4e55e..03ded0eb1b5 100644 --- a/src/lib/skilling/skills/smithing/smithables/gorajan.ts +++ b/src/lib/skilling/skills/smithing/smithables/gorajan.ts @@ -1,7 +1,7 @@ import { Time } from 'e'; import itemID from '../../../../util/itemID'; -import { SmithedItem } from '../../../types'; +import type { SmithedItem } from '../../../types'; const items = [ ['Gorajan warrior helmet', 'Torva full helm'], diff --git a/src/lib/skilling/skills/smithing/smithables/iron.ts b/src/lib/skilling/skills/smithing/smithables/iron.ts index ef01aa7fc42..3813741c0b9 100644 --- a/src/lib/skilling/skills/smithing/smithables/iron.ts +++ b/src/lib/skilling/skills/smithing/smithables/iron.ts @@ -1,7 +1,7 @@ import { Time } from 'e'; import itemID from '../../../../util/itemID'; -import { SmithedItem } from '../../../types'; +import type { SmithedItem } from '../../../types'; const Iron: SmithedItem[] = [ { diff --git a/src/lib/skilling/skills/smithing/smithables/mithril.ts b/src/lib/skilling/skills/smithing/smithables/mithril.ts index 63cde7f64d9..b20cfe69bf2 100644 --- a/src/lib/skilling/skills/smithing/smithables/mithril.ts +++ b/src/lib/skilling/skills/smithing/smithables/mithril.ts @@ -1,7 +1,7 @@ import { Time } from 'e'; import itemID from '../../../../util/itemID'; -import { SmithedItem } from '../../../types'; +import type { SmithedItem } from '../../../types'; const Mithril: SmithedItem[] = [ { diff --git a/src/lib/skilling/skills/smithing/smithables/rune.ts b/src/lib/skilling/skills/smithing/smithables/rune.ts index 695e62c3260..139adbed85b 100644 --- a/src/lib/skilling/skills/smithing/smithables/rune.ts +++ b/src/lib/skilling/skills/smithing/smithables/rune.ts @@ -1,7 +1,7 @@ import { Time } from 'e'; import itemID from '../../../../util/itemID'; -import { SmithedItem } from '../../../types'; +import type { SmithedItem } from '../../../types'; const Rune: SmithedItem[] = [ { diff --git a/src/lib/skilling/skills/smithing/smithables/silver.ts b/src/lib/skilling/skills/smithing/smithables/silver.ts index 75b029ccf98..7d7581cbe9f 100644 --- a/src/lib/skilling/skills/smithing/smithables/silver.ts +++ b/src/lib/skilling/skills/smithing/smithables/silver.ts @@ -1,7 +1,7 @@ import { Time } from 'e'; import itemID from '../../../../util/itemID'; -import { SmithedItem } from '../../../types'; +import type { SmithedItem } from '../../../types'; const Silver: SmithedItem[] = [ { diff --git a/src/lib/skilling/skills/smithing/smithables/steel.ts b/src/lib/skilling/skills/smithing/smithables/steel.ts index 64d2e39bbbc..9b1accb4df8 100644 --- a/src/lib/skilling/skills/smithing/smithables/steel.ts +++ b/src/lib/skilling/skills/smithing/smithables/steel.ts @@ -1,7 +1,7 @@ import { Time } from 'e'; import itemID from '../../../../util/itemID'; -import { SmithedItem } from '../../../types'; +import type { SmithedItem } from '../../../types'; const Steel: SmithedItem[] = [ { diff --git a/src/lib/skilling/skills/woodcutting/forestry.ts b/src/lib/skilling/skills/woodcutting/forestry.ts index 49bde528c09..4ab4a99fb41 100644 --- a/src/lib/skilling/skills/woodcutting/forestry.ts +++ b/src/lib/skilling/skills/woodcutting/forestry.ts @@ -9,7 +9,7 @@ export const LeafTable = new LootTable() .add('Yew leaves', 20) .add('Magic leaves', 20); -export interface ForestryEvent { +interface ForestryEvent { id: number; name: string; uniqueXP: SkillsEnum; diff --git a/src/lib/skilling/skills/woodcutting/woodcutting.ts b/src/lib/skilling/skills/woodcutting/woodcutting.ts index 0223d09882f..bf2dffbafca 100644 --- a/src/lib/skilling/skills/woodcutting/woodcutting.ts +++ b/src/lib/skilling/skills/woodcutting/woodcutting.ts @@ -2,7 +2,8 @@ import { LootTable } from 'oldschooljs'; import { BitField, Emoji } from '../../../constants'; import itemID from '../../../util/itemID'; -import { Log, SkillsEnum } from '../../types'; +import type { Log } from '../../types'; +import { SkillsEnum } from '../../types'; const sulliuscepTable = new LootTable() .add('Numulite', [4, 8], 34) diff --git a/src/lib/skilling/types.ts b/src/lib/skilling/types.ts index e941edc960a..77bd5dd420a 100644 --- a/src/lib/skilling/types.ts +++ b/src/lib/skilling/types.ts @@ -1,10 +1,9 @@ -import { Bank } from 'oldschooljs'; -import { Item } from 'oldschooljs/dist/meta/types'; -import LootTable from 'oldschooljs/dist/structures/LootTable'; - -import { SlayerTaskUnlocksEnum } from '../slayer/slayerUnlocks'; -import { ItemBank, Skills } from '../types'; -import { FarmingPatchName } from '../util/farmingHelpers'; +import type { Bank } from 'oldschooljs'; +import type { Item, ItemBank } from 'oldschooljs/dist/meta/types'; +import type LootTable from 'oldschooljs/dist/structures/LootTable'; +import type { SlayerTaskUnlocksEnum } from '../slayer/slayerUnlocks'; +import type { Skills } from '../types'; +import type { FarmingPatchName } from '../util/farmingHelpers'; export enum SkillsEnum { Agility = 'agility', @@ -328,7 +327,7 @@ export enum HunterTechniqueEnum { BoxTrapping = 'box trapping', ButterflyNetting = 'butterfly netting', DeadfallTrapping = 'deadfall trapping', - Falconry = 'falconry', + Falconry = 'hawking', MagicBoxTrapping = 'magic box trapping', NetTrapping = 'net trapping', PitfallTrapping = 'pitfall trapping', diff --git a/src/lib/slayer/constants.ts b/src/lib/slayer/constants.ts index 512f87350fc..2f75daae39f 100644 --- a/src/lib/slayer/constants.ts +++ b/src/lib/slayer/constants.ts @@ -1,8 +1,8 @@ export enum AutoslayOptionsEnum { - Default, - HighestUnlocked, - MaxEfficiency, - LowestCombat + Default = 0, + HighestUnlocked = 1, + MaxEfficiency = 2, + LowestCombat = 3 } export const slayerMasterChoices = [ @@ -12,7 +12,8 @@ export const slayerMasterChoices = [ 'Nieve', 'Chaeldar', 'Mazchna', - 'Turael' + 'Turael', + 'Krystilia' ].map(smc => { return { name: smc, value: smc }; }); diff --git a/src/lib/slayer/slayerMasters.ts b/src/lib/slayer/slayerMasters.ts index 91244d9c254..3e87831f050 100644 --- a/src/lib/slayer/slayerMasters.ts +++ b/src/lib/slayer/slayerMasters.ts @@ -3,11 +3,12 @@ import { MonsterSlayerMaster } from 'oldschooljs'; import { chaeldarTasks } from './tasks/chaeldarTasks'; import { duradelTasks } from './tasks/duradelTasks'; import { konarTasks } from './tasks/konarTasks'; +import { krystiliaTasks } from './tasks/krystiliaTasks'; import { mazchnaTasks } from './tasks/mazchnaTasks'; import { nieveTasks } from './tasks/nieveTasks'; import { turaelTasks } from './tasks/turaelTasks'; import { vannakaTasks } from './tasks/vannakaTasks'; -import { SlayerMaster } from './types'; +import type { SlayerMaster } from './types'; export const slayerMasters: SlayerMaster[] = [ { @@ -77,5 +78,13 @@ export const slayerMasters: SlayerMaster[] = [ combatLvl: 100, slayerLvl: 50, osjsEnum: MonsterSlayerMaster.Duradel + }, + { + id: 8, + name: 'Krystilia', + aliases: ['krystilia', 'wildy slayer', 'wilderness slayer'], + tasks: krystiliaTasks, + basePoints: 25, + osjsEnum: MonsterSlayerMaster.Krystilia } ]; diff --git a/src/lib/slayer/slayerShop.ts b/src/lib/slayer/slayerShop.ts index 5784e64b565..67ff5d6d4bc 100644 --- a/src/lib/slayer/slayerShop.ts +++ b/src/lib/slayer/slayerShop.ts @@ -1,5 +1,5 @@ import getOSItem from '../util/getOSItem'; -import { SlayerShopItem } from './types'; +import type { SlayerShopItem } from './types'; export const slayerShopBuy: readonly SlayerShopItem[] = [ { diff --git a/src/lib/slayer/slayerUnlocks.ts b/src/lib/slayer/slayerUnlocks.ts index 0799b9c88bb..2c1c248e499 100644 --- a/src/lib/slayer/slayerUnlocks.ts +++ b/src/lib/slayer/slayerUnlocks.ts @@ -2,7 +2,7 @@ import { Monsters } from 'oldschooljs'; import itemID from '../util/itemID'; -export interface SlayerTaskUnlocks { +interface SlayerTaskUnlocks { id: SlayerTaskUnlocksEnum; name: string; desc?: string; @@ -18,62 +18,65 @@ export interface SlayerTaskUnlocks { export enum SlayerTaskUnlocksEnum { // Unlockables MalevolentMasquerade = 2, - RingBling, - SeeingRed, - IHopeYouMithMe, - WatchTheBirdie, - HotStuff, - ReptileGotRipped, - LikeABoss, - BiggerAndBadder, - KingBlackBonnet, - KalphiteKhat, - UnholyHelmet, - DarkMantle, - UndeadHead, - UseMoreHead, - TwistedVision, - StopTheWyvern, - Basilocked, - ActualVampyreSlayer, + RingBling = 3, + SeeingRed = 4, + IHopeYouMithMe = 5, + WatchTheBirdie = 6, + HotStuff = 7, + ReptileGotRipped = 8, + LikeABoss = 9, + BiggerAndBadder = 10, + KingBlackBonnet = 11, + KalphiteKhat = 12, + UnholyHelmet = 13, + DarkMantle = 14, + UndeadHead = 15, + UseMoreHead = 16, + TwistedVision = 17, + StopTheWyvern = 18, + Basilocked = 19, + ActualVampyreSlayer = 20, // Extension Unlocks - NeedMoreDarkness, - AnkouVeryMuch, - SuqANotherOne, - FireAndDarkness, - PedalToTheMetals, - IReallyMithYou, - AdamindSomeMore, - RUUUUUNE, - SpiritualFervour, - BirdsOfAFeather, - GreaterChallenge, - ItsDarkInHere, - BleedMeDry, - SmellYaLater, - Horrorific, - ToDustYouShallReturn, - WyverNotherOne, - GetSmashed, - NechsPlease, - AugmentMyAbbies, - KrackOn, - GetScabarightOnIt, - WyverNotherTwo, - Basilonger, - MoreAtStake, + NeedMoreDarkness = 22, + AnkouVeryMuch = 23, + SuqANotherOne = 24, + FireAndDarkness = 25, + PedalToTheMetals = 26, + IReallyMithYou = 27, + AdamindSomeMore = 28, + RUUUUUNE = 29, + SpiritualFervour = 30, + BirdsOfAFeather = 31, + GreaterChallenge = 32, + ItsDarkInHere = 33, + BleedMeDry = 34, + SmellYaLater = 35, + Horrorific = 36, + ToDustYouShallReturn = 37, + WyverNotherOne = 38, + GetSmashed = 39, + NechsPlease = 40, + AugmentMyAbbies = 41, + KrackOn = 42, + GetScabarightOnIt = 43, + WyverNotherTwo = 44, + Basilonger = 45, + MoreAtStake = 46, // Item Purchases: - SlayerRing, - HerbSack, - RunePouch, - DoubleTrouble, - BroaderFletching, + SlayerRing = 48, + HerbSack = 49, + RunePouch = 50, + DoubleTrouble = 51, + BroaderFletching = 52, // Custom - SizeMatters, - BlockAndRoll, - PoreDecisions, - Maskuerade + SizeMatters = 53, + BlockAndRoll = 54, + PoreDecisions = 55, + Maskuerade = 56, + IWildyMoreSlayer = 200, + Revenenenenenants = 201 } + export const SlayerRewardsShop: SlayerTaskUnlocks[] = [ { id: SlayerTaskUnlocksEnum.MalevolentMasquerade, @@ -227,6 +230,14 @@ export const SlayerRewardsShop: SlayerTaskUnlocks[] = [ canBeRemoved: true, aliases: ['vampyre slayer', 'vampire slayer', 'actual vampire slayer', 'vampyres', 'vampires'] }, + { + id: SlayerTaskUnlocksEnum.IWildyMoreSlayer, + name: 'I Wildy More Slayer', + desc: 'Krystilia will be able to assign Jellies, Dust Devils, Nechryaels and Abyssal Demons as your task.', + slayerPointCost: 0, + canBeRemoved: true, + aliases: ['wildy slayer'] + }, { id: SlayerTaskUnlocksEnum.SlayerRing, name: 'Slayer ring', @@ -520,6 +531,16 @@ export const SlayerRewardsShop: SlayerTaskUnlocks[] = [ canBeRemoved: false, aliases: ['broad bolts', 'broads', 'broad arrows', 'fletching', 'broad fletching'] }, + { + id: SlayerTaskUnlocksEnum.Revenenenenenants, + name: 'Revenenenenenants', + desc: 'Extends Revenants tasks', + slayerPointCost: 100, + extendID: [Monsters.RevenantImp.id], + extendMult: 1.5, + canBeRemoved: true, + aliases: ['extend revenants', 'extend revs'] + }, { id: SlayerTaskUnlocksEnum.SizeMatters, name: 'Size Matters', diff --git a/src/lib/slayer/slayerUtil.ts b/src/lib/slayer/slayerUtil.ts index af5d0a481cb..97ef9b2fcf6 100644 --- a/src/lib/slayer/slayerUtil.ts +++ b/src/lib/slayer/slayerUtil.ts @@ -1,74 +1,89 @@ import { notEmpty, objectKeys, randFloat, randInt } from 'e'; -import { Bank, Monsters, MonsterSlayerMaster } from 'oldschooljs'; -import Monster from 'oldschooljs/dist/structures/Monster'; +import { Bank, Monsters } from 'oldschooljs'; +import type Monster from 'oldschooljs/dist/structures/Monster'; import { KourendKebosDiary, LumbridgeDraynorDiary, userhasDiaryTier } from '../../lib/diaries'; import { CombatAchievements } from '../combat_achievements/combatAchievements'; -import { BitField, PvMMethod } from '../constants'; +import { BitField, type PvMMethod } from '../constants'; import { CombatOptionsEnum } from '../minions/data/combatConstants'; import { BSOMonsters } from '../minions/data/killableMonsters/custom/customMonsters'; -import { KillableMonster } from '../minions/types'; -import { prisma } from '../settings/prisma'; -import { getNewUser } from '../settings/settings'; +import type { KillableMonster } from '../minions/types'; + import { SkillsEnum } from '../skilling/types'; -import { bankHasItem, roll, stringMatches } from '../util'; -import itemID from '../util/itemID'; +import { roll, stringMatches } from '../util'; import { logError } from '../util/logError'; import resolveItems from '../util/resolveItems'; import { autoslayModes } from './constants'; import { slayerMasters } from './slayerMasters'; import { SlayerRewardsShop, SlayerTaskUnlocksEnum } from './slayerUnlocks'; import { allSlayerTasks } from './tasks'; -import { bossTasks } from './tasks/bossTasks'; -import { AssignableSlayerTask, SlayerMaster } from './types'; +import { bossTasks, wildernessBossTasks } from './tasks/bossTasks'; +import type { AssignableSlayerTask, SlayerMaster } from './types'; export enum SlayerMasterEnum { - Reserved, - Turael, - Mazchna, - Vannaka, - Chaeldar, - Konar, - Nieve, - Duradel + Reserved = 0, + Turael = 1, + Mazchna = 2, + Vannaka = 3, + Chaeldar = 4, + Konar = 5, + Nieve = 6, + Duradel = 7 } -export interface DetermineBoostParams { +interface DetermineBoostParams { cbOpts: CombatOptionsEnum[]; user: MUser; monster: KillableMonster; - method?: PvMMethod | null; + methods?: PvMMethod[] | null; isOnTask?: boolean; + wildyBurst?: boolean; } -export function determineBoostChoice(params: DetermineBoostParams) { - let boostChoice = 'none'; - - // BSO Only: - if (!params.isOnTask) return boostChoice; - - if (params.method && params.method === 'none') { - return boostChoice; - } - if (params.method && (params.method as string) === 'chinning') { - boostChoice = 'chinning'; - } else if (params.method && params.method === 'barrage') { - boostChoice = 'barrage'; - } else if (params.method && params.method === 'burst') { - boostChoice = 'burst'; - } else if (params.method && params.method === 'cannon') { - boostChoice = 'cannon'; - } else if (params.cbOpts.includes(CombatOptionsEnum.AlwaysIceBarrage) && params.monster!.canBarrage) { - boostChoice = 'barrage'; - } else if (params.cbOpts.includes(CombatOptionsEnum.AlwaysIceBurst) && params.monster!.canBarrage) { - boostChoice = 'burst'; - } else if (params.cbOpts.includes(CombatOptionsEnum.AlwaysCannon)) { - boostChoice = 'cannon'; - } - - if (boostChoice === 'barrage' && params.user.skillLevel(SkillsEnum.Magic) < 94) { - boostChoice = 'burst'; - } - return boostChoice; +export function determineCombatBoosts(params: DetermineBoostParams) { + // if EHP slayer (PvMMethod) the methods are initialized with boostMethods variable + const boostMethods = (params.methods ?? ['none']).flat().filter(method => method); + + if (!params.isOnTask) return []; + + // check if user has cannon combat option turned on + if (params.cbOpts.includes(CombatOptionsEnum.AlwaysCannon)) { + boostMethods.includes('cannon') ? null : boostMethods.push('cannon'); + } + + // check for special burst case under wildyBurst variable + if (params.wildyBurst) { + if (params.cbOpts.includes(CombatOptionsEnum.AlwaysIceBarrage)) { + boostMethods.includes('barrage') ? null : boostMethods.push('barrage'); + } + if (params.cbOpts.includes(CombatOptionsEnum.AlwaysIceBurst)) { + boostMethods.includes('burst') ? null : boostMethods.push('burst'); + } + } + + // check if the monster can be barraged + if (params.monster.canBarrage) { + // check if the monster exists in catacombs + if (params.monster.existsInCatacombs) { + if (params.cbOpts.includes(CombatOptionsEnum.AlwaysIceBarrage)) { + boostMethods.includes('barrage') ? null : boostMethods.push('barrage'); + } + if (params.cbOpts.includes(CombatOptionsEnum.AlwaysIceBurst)) { + boostMethods.includes('burst') ? null : boostMethods.push('burst'); + } + } else if (!params.monster.cannonMulti) { + // prevents cases such as: cannoning in singles but receiving multi combat bursting boost + return boostMethods; + } else { + if (params.cbOpts.includes(CombatOptionsEnum.AlwaysIceBarrage)) { + boostMethods.includes('barrage') ? null : boostMethods.push('barrage'); + } + if (params.cbOpts.includes(CombatOptionsEnum.AlwaysIceBurst)) { + boostMethods.includes('burst') ? null : boostMethods.push('burst'); + } + } + } + + return boostMethods; } export async function calculateSlayerPoints(currentStreak: number, master: SlayerMaster, user: MUser) { @@ -96,7 +111,7 @@ export async function calculateSlayerPoints(currentStreak: number, master: Slaye return basePoints; } -export function weightedPick(filteredTasks: AssignableSlayerTask[]) { +function weightedPick(filteredTasks: AssignableSlayerTask[]) { let totalweight = 0; for (let i = 0; i < filteredTasks.length; i++) { totalweight += filteredTasks[i].weight; @@ -114,7 +129,7 @@ export function weightedPick(filteredTasks: AssignableSlayerTask[]) { } } - let task = filteredTasks[result]; + const task = filteredTasks[result]; return task; } @@ -127,12 +142,7 @@ export function userCanUseMaster(user: MUser, master: SlayerMaster) { ); } -export function userCanUseTask( - user: MUser, - task: AssignableSlayerTask, - master: SlayerMaster, - allowBossTasks: boolean = false -) { +function userCanUseTask(user: MUser, task: AssignableSlayerTask, master: SlayerMaster, allowBossTasks = false) { if (task.isBoss && !allowBossTasks) return false; if (task.dontAssign) return false; const myLastTask = user.user.slayer_last_task; @@ -161,7 +171,7 @@ export function userCanUseTask( ) { return false; } - if (lmon === 'grotesque guardians' && !bankHasItem(user.bank.bank, itemID('Brittle key'))) return false; + if (lmon === 'grotesque guardians' && !user.bank.has('Brittle key')) return false; if (lmon === 'lizardman' && !myUnlocks.includes(SlayerTaskUnlocksEnum.ReptileGotRipped)) return false; if (lmon === 'red dragon' && !myUnlocks.includes(SlayerTaskUnlocksEnum.SeeingRed)) return false; if (lmon === 'mithril dragon' && !myUnlocks.includes(SlayerTaskUnlocksEnum.IHopeYouMithMe)) return false; @@ -180,6 +190,12 @@ export function userCanUseTask( !myUnlocks.includes(SlayerTaskUnlocksEnum.Basilocked) ) return false; + if ( + (lmon === 'dust devil' || lmon === 'greater nechryael' || lmon === 'abyssal demon' || lmon === 'jelly') && + lmast === 'krystilia' && + !myUnlocks.includes(SlayerTaskUnlocksEnum.IWildyMoreSlayer) + ) + return false; return true; } @@ -188,6 +204,7 @@ export async function assignNewSlayerTask(_user: MUser, master: SlayerMaster) { // assignedTask is the task object, currentTask is the database row. const baseTasks = [...master.tasks].filter(t => userCanUseTask(_user, t, master, false)); let bossTask = false; + let wildyBossTask = false; if ( _user.user.slayer_unlocks.includes(SlayerTaskUnlocksEnum.LikeABoss) && (master.name.toLowerCase() === 'konar quo maten' || @@ -199,21 +216,41 @@ export async function assignNewSlayerTask(_user: MUser, master: SlayerMaster) { bossTask = true; } + if (_user.user.slayer_unlocks.includes(SlayerTaskUnlocksEnum.LikeABoss) && master.id === 8 && roll(25)) { + wildyBossTask = true; + } + let assignedTask: AssignableSlayerTask | null = null; + if (bossTask) { const baseBossTasks = bossTasks.filter(t => userCanUseTask(_user, t, master, true)); if (baseBossTasks.length > 0) { assignedTask = weightedPick(baseBossTasks); - } else { - assignedTask = weightedPick(baseTasks); } - } else { + } + + if (wildyBossTask) { + const baseWildyBossTasks = wildernessBossTasks.filter(t => userCanUseTask(_user, t, master, true)); + if (baseWildyBossTasks.length > 0) { + assignedTask = weightedPick(baseWildyBossTasks); + } + } + + if (assignedTask === null) { assignedTask = weightedPick(baseTasks); } - const newUser = await getNewUser(_user.id); + const newUser = await prisma.newUser.upsert({ + where: { + id: _user.id + }, + create: { + id: _user.id + }, + update: {} + }); - let maxQuantity = assignedTask!.amount[1]; + let maxQuantity = assignedTask?.amount[1]; if (bossTask && _user.user.slayer_unlocks.includes(SlayerTaskUnlocksEnum.LikeABoss)) { for (const tier of objectKeys(CombatAchievements)) { if (_user.hasCompletedCATier(tier)) { @@ -224,14 +261,14 @@ export async function assignNewSlayerTask(_user: MUser, master: SlayerMaster) { let quantity = randInt(assignedTask!.amount[0], maxQuantity); - const extendReward = SlayerRewardsShop.find(srs => srs.extendID && srs.extendID.includes(assignedTask!.monster.id)); + const extendReward = SlayerRewardsShop.find(srs => srs.extendID?.includes(assignedTask!.monster.id)); if (extendReward && unlocks.includes(extendReward.id)) { quantity = assignedTask.extendedAmount ? randInt(assignedTask.extendedAmount[0], assignedTask.extendedAmount[1]) : Math.ceil(quantity * extendReward.extendMult!); } - let messages: string[] = []; + const messages: string[] = []; if (unlocks.includes(SlayerTaskUnlocksEnum.SizeMatters) && !_user.bitfield.includes(BitField.DisableSizeMatters)) { quantity *= 2; messages.push('2x qty for Size Matters unlock'); @@ -250,12 +287,12 @@ export async function assignNewSlayerTask(_user: MUser, master: SlayerMaster) { quantity, quantity_remaining: quantity, slayer_master_id: master.id, - monster_id: assignedTask!.monster.id, + monster_id: assignedTask?.monster.id, skipped: false } }); await _user.update({ - slayer_last_task: assignedTask!.monster.id + slayer_last_task: assignedTask?.monster.id }); return { currentTask, assignedTask, messages }; @@ -313,6 +350,12 @@ export function getCommonTaskName(task: Monster) { case Monsters.TzHaarKet.id: commonName = 'TzHaar'; break; + case Monsters.RevenantImp.id: + commonName = 'Revenant'; + break; + case Monsters.DagannothPrime.id: + commonName = 'Dagannoth Kings'; + break; default: } if (commonName !== 'TzHaar' && !commonName.endsWith('s')) commonName += 's'; @@ -365,43 +408,9 @@ export async function getUsersCurrentSlayerInfo(id: string) { }; } -export const allSlayerHelmets = [ - 'Slayer helmet', - 'Slayer helmet (i)', - 'Black slayer helmet', - 'Black slayer helmet (i)', - 'Green slayer helmet', - 'Green slayer helmet (i)', - 'Red slayer helmet', - 'Red slayer helmet (i)', - 'Purple slayer helmet', - 'Purple slayer helmet (i)', - 'Turquoise slayer helmet', - 'Turquoise slayer helmet (i)', - 'Hydra slayer helmet', - 'Hydra slayer helmet (i)', - 'Twisted slayer helmet', - 'Twisted slayer helmet (i)' -]; - -export function getSlayerMasterOSJSbyID(slayerMasterID: number) { - const osjsSlayerMaster = [ - MonsterSlayerMaster.Turael, - MonsterSlayerMaster.Turael, - MonsterSlayerMaster.Mazchna, - MonsterSlayerMaster.Vannaka, - MonsterSlayerMaster.Chaeldar, - MonsterSlayerMaster.Konar, - MonsterSlayerMaster.Nieve, - MonsterSlayerMaster.Duradel, - MonsterSlayerMaster.Krystilia - ]; - return osjsSlayerMaster[slayerMasterID]; -} - -export function getSlayerReward(id: SlayerTaskUnlocksEnum): string { +function getSlayerReward(id: SlayerTaskUnlocksEnum): string { const { name } = SlayerRewardsShop.find(srs => { - return srs!.id === id; + return srs?.id === id; })!; return name; } @@ -513,8 +522,7 @@ export function filterLootReplace(myBank: Bank, myLoot: Bank) { } export async function getSlayerTaskStats(userID: string) { - const result: { monster_id: number; total_quantity: number; qty: number }[] = - await prisma.$queryRaw`SELECT monster_id, SUM(quantity)::int AS total_quantity, COUNT(monster_id)::int AS qty + const result: { monster_id: number; total_quantity: number; qty: number }[] = await prisma.$queryRaw`SELECT monster_id, SUM(quantity)::int AS total_quantity, COUNT(monster_id)::int AS qty FROM slayer_tasks WHERE user_id = ${userID} AND quantity_remaining = 0 diff --git a/src/lib/slayer/tasks/bossTasks.ts b/src/lib/slayer/tasks/bossTasks.ts index bac6139f2a8..11aaf4fc565 100644 --- a/src/lib/slayer/tasks/bossTasks.ts +++ b/src/lib/slayer/tasks/bossTasks.ts @@ -1,8 +1,7 @@ import { Monsters } from 'oldschooljs'; - import { BSOMonsters } from '../../minions/data/killableMonsters/custom/customMonsters'; import { getMonster } from '../../util'; -import { AssignableSlayerTask } from '../types'; +import type { AssignableSlayerTask } from '../types'; export const bossTasks: AssignableSlayerTask[] = [ { @@ -35,8 +34,9 @@ export const bossTasks: AssignableSlayerTask[] = [ monster: Monsters.Callisto, amount: [3, 35], weight: 1, - monsters: [Monsters.Callisto.id], - isBoss: true + monsters: [Monsters.Callisto.id, Monsters.Artio.id], + isBoss: true, + wilderness: true }, { monster: Monsters.Cerberus, @@ -54,14 +54,16 @@ export const bossTasks: AssignableSlayerTask[] = [ amount: [3, 35], weight: 1, monsters: [Monsters.ChaosElemental.id], - isBoss: true + isBoss: true, + wilderness: true }, { monster: Monsters.ChaosFanatic, amount: [3, 35], weight: 1, monsters: [Monsters.ChaosFanatic.id], - isBoss: true + isBoss: true, + wilderness: true }, { monster: Monsters.CommanderZilyana, @@ -79,7 +81,8 @@ export const bossTasks: AssignableSlayerTask[] = [ amount: [3, 35], weight: 1, monsters: [Monsters.CrazyArchaeologist.id], - isBoss: true + isBoss: true, + wilderness: true }, { monster: Monsters.DagannothPrime, @@ -88,27 +91,7 @@ export const bossTasks: AssignableSlayerTask[] = [ levelRequirements: { prayer: 43 }, - monsters: [Monsters.DagannothPrime.id], - isBoss: true - }, - { - monster: Monsters.DagannothSupreme, - amount: [3, 35], - weight: 1, - levelRequirements: { - prayer: 43 - }, - monsters: [Monsters.DagannothSupreme.id], - isBoss: true - }, - { - monster: Monsters.DagannothRex, - amount: [3, 35], - weight: 1, - levelRequirements: { - prayer: 43 - }, - monsters: [Monsters.DagannothRex.id], + monsters: [Monsters.DagannothPrime.id, Monsters.DagannothSupreme.id, Monsters.DagannothRex.id], isBoss: true }, { @@ -202,7 +185,8 @@ export const bossTasks: AssignableSlayerTask[] = [ amount: [3, 35], weight: 1, monsters: [Monsters.Scorpia.id], - isBoss: true + isBoss: true, + wilderness: true }, { monster: Monsters.ThermonuclearSmokeDevil, @@ -216,15 +200,17 @@ export const bossTasks: AssignableSlayerTask[] = [ monster: Monsters.Venenatis, amount: [3, 35], weight: 1, - monsters: [Monsters.Venenatis.id], - isBoss: true + monsters: [Monsters.Venenatis.id, Monsters.Spindel.id], + isBoss: true, + wilderness: true }, { monster: Monsters.Vetion, amount: [3, 35], weight: 1, - monsters: [Monsters.Vetion.id], - isBoss: true + monsters: [Monsters.Vetion.id, Monsters.Calvarion.id], + isBoss: true, + wilderness: true }, { monster: Monsters.Vorkath, @@ -262,3 +248,62 @@ export const bossTasks: AssignableSlayerTask[] = [ isBoss: true } ]; + +export const wildernessBossTasks: AssignableSlayerTask[] = [ + { + monster: Monsters.Callisto, + amount: [3, 35], + weight: 1, + monsters: [Monsters.Callisto.id, Monsters.Artio.id], + isBoss: true, + wilderness: true + }, + { + monster: Monsters.ChaosElemental, + amount: [3, 35], + weight: 1, + monsters: [Monsters.ChaosElemental.id], + isBoss: true, + wilderness: true + }, + { + monster: Monsters.ChaosFanatic, + amount: [3, 35], + weight: 1, + monsters: [Monsters.ChaosFanatic.id], + isBoss: true, + wilderness: true + }, + { + monster: Monsters.CrazyArchaeologist, + amount: [3, 35], + weight: 1, + monsters: [Monsters.CrazyArchaeologist.id], + isBoss: true, + wilderness: true + }, + { + monster: Monsters.Scorpia, + amount: [3, 35], + weight: 1, + monsters: [Monsters.Scorpia.id], + isBoss: true, + wilderness: true + }, + { + monster: Monsters.Venenatis, + amount: [3, 35], + weight: 1, + monsters: [Monsters.Venenatis.id, Monsters.Spindel.id], + isBoss: true, + wilderness: true + }, + { + monster: Monsters.Vetion, + amount: [3, 35], + weight: 1, + monsters: [Monsters.Vetion.id, Monsters.Calvarion.id], + isBoss: true, + wilderness: true + } +]; diff --git a/src/lib/slayer/tasks/chaeldarTasks.ts b/src/lib/slayer/tasks/chaeldarTasks.ts index 171cd6eebe9..16b06bf4ae5 100644 --- a/src/lib/slayer/tasks/chaeldarTasks.ts +++ b/src/lib/slayer/tasks/chaeldarTasks.ts @@ -2,7 +2,7 @@ import { Monsters } from 'oldschooljs'; import { KalphiteKingMonster } from '../../minions/data/killableMonsters/custom/bosses/KalphiteKing'; import { BSOMonsters } from '../../minions/data/killableMonsters/custom/customMonsters'; -import { AssignableSlayerTask } from '../types'; +import type { AssignableSlayerTask } from '../types'; import { bossTasks } from './bossTasks'; import { polyporeTasks } from './polyporeTasks'; diff --git a/src/lib/slayer/tasks/duradelTasks.ts b/src/lib/slayer/tasks/duradelTasks.ts index 2197b9e0404..cacf4431c02 100644 --- a/src/lib/slayer/tasks/duradelTasks.ts +++ b/src/lib/slayer/tasks/duradelTasks.ts @@ -5,7 +5,7 @@ import { Ignecarus } from '../../minions/data/killableMonsters/custom/bosses/Ign import { KalphiteKingMonster } from '../../minions/data/killableMonsters/custom/bosses/KalphiteKing'; import { BSOMonsters } from '../../minions/data/killableMonsters/custom/customMonsters'; import { SlayerTaskUnlocksEnum } from '../slayerUnlocks'; -import { AssignableSlayerTask } from '../types'; +import type { AssignableSlayerTask } from '../types'; import { bossTasks } from './bossTasks'; export const duradelTasks: AssignableSlayerTask[] = [ @@ -402,7 +402,7 @@ export const duradelTasks: AssignableSlayerTask[] = [ amount: [10, 20], weight: 7, monsters: [Monsters.SteelDragon.id], - levelRequirements: killableMonsters.find(k => k.id === Monsters.SteelDragon.id)!.levelRequirements, + levelRequirements: killableMonsters.find(k => k.id === Monsters.SteelDragon.id)?.levelRequirements, extendedAmount: [40, 60], extendedUnlockId: SlayerTaskUnlocksEnum.PedalToTheMetals, combatLevel: 85, diff --git a/src/lib/slayer/tasks/index.ts b/src/lib/slayer/tasks/index.ts index b33c3dccc07..5e025e6b39c 100644 --- a/src/lib/slayer/tasks/index.ts +++ b/src/lib/slayer/tasks/index.ts @@ -1,8 +1,9 @@ -import { AssignableSlayerTask } from '../types'; +import type { AssignableSlayerTask } from '../types'; import { bossTasks } from './bossTasks'; import { chaeldarTasks } from './chaeldarTasks'; import { duradelTasks } from './duradelTasks'; import { konarTasks } from './konarTasks'; +import { krystiliaTasks } from './krystiliaTasks'; import { mazchnaTasks } from './mazchnaTasks'; import { nieveTasks } from './nieveTasks'; import { turaelTasks } from './turaelTasks'; @@ -16,7 +17,8 @@ export const allSlayerTasks: AssignableSlayerTask[] = [ ...nieveTasks, ...turaelTasks, ...vannakaTasks, - ...duradelTasks + ...duradelTasks, + ...krystiliaTasks ]; export const allSlayerMonsters = allSlayerTasks.map(m => m.monster); diff --git a/src/lib/slayer/tasks/konarTasks.ts b/src/lib/slayer/tasks/konarTasks.ts index f754314bc51..d8c6a42e121 100644 --- a/src/lib/slayer/tasks/konarTasks.ts +++ b/src/lib/slayer/tasks/konarTasks.ts @@ -5,7 +5,7 @@ import { Ignecarus } from '../../minions/data/killableMonsters/custom/bosses/Ign import { KalphiteKingMonster } from '../../minions/data/killableMonsters/custom/bosses/KalphiteKing'; import { BSOMonsters } from '../../minions/data/killableMonsters/custom/customMonsters'; import { SlayerTaskUnlocksEnum } from '../slayerUnlocks'; -import { AssignableSlayerTask } from '../types'; +import type { AssignableSlayerTask } from '../types'; import { bossTasks } from './bossTasks'; export const konarTasks: AssignableSlayerTask[] = [ @@ -394,7 +394,7 @@ export const konarTasks: AssignableSlayerTask[] = [ amount: [30, 50], weight: 5, monsters: [Monsters.SteelDragon.id], - levelRequirements: killableMonsters.find(k => k.id === Monsters.SteelDragon.id)!.levelRequirements, + levelRequirements: killableMonsters.find(k => k.id === Monsters.SteelDragon.id)?.levelRequirements, extendedAmount: [40, 60], extendedUnlockId: SlayerTaskUnlocksEnum.PedalToTheMetals, combatLevel: 85, diff --git a/src/lib/slayer/tasks/krystiliaTasks.ts b/src/lib/slayer/tasks/krystiliaTasks.ts new file mode 100644 index 00000000000..f32758963e0 --- /dev/null +++ b/src/lib/slayer/tasks/krystiliaTasks.ts @@ -0,0 +1,337 @@ +import { Monsters } from 'oldschooljs'; + +import { SlayerTaskUnlocksEnum } from '../slayerUnlocks'; +import type { AssignableSlayerTask } from '../types'; +import { wildernessBossTasks } from './bossTasks'; + +export const krystiliaTasks: AssignableSlayerTask[] = [ + { + monster: Monsters.AbyssalDemon, + amount: [75, 125], + weight: 5, + monsters: [Monsters.AbyssalDemon.id], + extendedAmount: [200, 250], + extendedUnlockId: SlayerTaskUnlocksEnum.AugmentMyAbbies, + slayerLevel: 85, + unlocked: true, + wilderness: true + }, + { + monster: Monsters.Ankou, + amount: [75, 125], + weight: 6, + monsters: [Monsters.Ankou.id], + extendedAmount: [91, 150], + extendedUnlockId: SlayerTaskUnlocksEnum.AnkouVeryMuch, + unlocked: true, + wilderness: true + }, + { + monster: Monsters.Aviansie, + amount: [75, 125], + weight: 7, + monsters: [Monsters.Aviansie.id], + extendedAmount: [200, 250], + extendedUnlockId: SlayerTaskUnlocksEnum.BirdsOfAFeather, + unlocked: true, + wilderness: true + }, + // { + // monster: Monsters.Bandit, + // amount: [75, 125], + // weight: 4, + // monsters: [Monsters.Bandit.id], + // unlocked: true, + // wilderness: true + // }, + { + monster: Monsters.GrizzlyBear, + amount: [65, 100], + weight: 6, + monsters: [Monsters.GrizzlyBear.id, Monsters.Artio.id, Monsters.Callisto.id], + unlocked: true, + wilderness: true + }, + { + monster: Monsters.BlackDemon, + amount: [100, 150], + weight: 7, + monsters: [Monsters.BlackDemon.id], + extendedAmount: [200, 250], + extendedUnlockId: SlayerTaskUnlocksEnum.ItsDarkInHere, + unlocked: true, + wilderness: true + }, + { + monster: Monsters.BlackDragon, + amount: [8, 16], + weight: 4, + monsters: [Monsters.BlackDragon.id], + extendedAmount: [40, 60], + extendedUnlockId: SlayerTaskUnlocksEnum.FireAndDarkness, + unlocked: true, + wilderness: true + }, + { + monster: Monsters.BlackKnight, + amount: [75, 125], + weight: 3, + monsters: [Monsters.BlackKnight.id], + unlocked: true, + wilderness: true + }, + { + monster: Monsters.Bloodveld, + amount: [70, 110], + weight: 4, + monsters: [Monsters.Bloodveld.id], + extendedAmount: [200, 250], + extendedUnlockId: SlayerTaskUnlocksEnum.BleedMeDry, + slayerLevel: 50, + unlocked: true, + wilderness: true + }, + { + monster: Monsters.ChaosDruid, + amount: [50, 90], + weight: 5, + monsters: [Monsters.ChaosDruid.id, Monsters.ElderChaosDruid.id], + unlocked: true, + wilderness: true + }, + { + monster: Monsters.DarkWarrior, + amount: [75, 125], + weight: 4, + monsters: [Monsters.DarkWarrior.id], + unlocked: true, + wilderness: true + }, + { + monster: Monsters.DustDevil, + amount: [75, 125], + weight: 5, + monsters: [Monsters.DustDevil.id], + extendedAmount: [200, 250], + extendedUnlockId: SlayerTaskUnlocksEnum.ToDustYouShallReturn, + slayerLevel: 65, + unlocked: true, + wilderness: true + }, + { + monster: Monsters.EarthWarrior, + amount: [75, 125], + weight: 6, + monsters: [Monsters.EarthWarrior.id], + unlocked: true, + wilderness: true + }, + { + monster: Monsters.Ent, + amount: [35, 60], + weight: 5, + monsters: [Monsters.Ent.id], + unlocked: true, + wilderness: true + }, + { + monster: Monsters.FireGiant, + amount: [75, 125], + weight: 7, + monsters: [Monsters.FireGiant.id], + unlocked: true, + wilderness: true + }, + { + monster: Monsters.GreaterDemon, + amount: [100, 150], + weight: 8, + monsters: [Monsters.GreaterDemon.id], + extendedAmount: [200, 250], + extendedUnlockId: SlayerTaskUnlocksEnum.GreaterChallenge, + unlocked: true, + wilderness: true + }, + { + monster: Monsters.GreenDragon, + amount: [65, 100], + weight: 4, + monsters: [Monsters.GreenDragon.id], + unlocked: true, + wilderness: true + }, + { + monster: Monsters.Hellhound, + amount: [75, 125], + weight: 7, + monsters: [Monsters.Hellhound.id], + unlocked: true, + wilderness: true + }, + { + monster: Monsters.HillGiant, + amount: [75, 125], + weight: 3, + monsters: [Monsters.HillGiant.id], + unlocked: true, + wilderness: true + }, + { + monster: Monsters.IceGiant, + amount: [100, 150], + weight: 6, + monsters: [Monsters.IceGiant.id], + unlocked: true, + wilderness: true + }, + { + monster: Monsters.IceWarrior, + amount: [100, 150], + weight: 7, + monsters: [Monsters.IceWarrior.id], + unlocked: true, + wilderness: true + }, + { + monster: Monsters.Jelly, + amount: [100, 150], + weight: 5, + monsters: [Monsters.Jelly.id], + slayerLevel: 52, + unlocked: true, + wilderness: true + }, + { + monster: Monsters.LavaDragon, + amount: [35, 60], + weight: 3, + monsters: [Monsters.LavaDragon.id], + unlocked: true, + wilderness: true + }, + { + monster: Monsters.LesserDemon, + amount: [80, 120], + weight: 6, + monsters: [Monsters.LesserDemon.id], + unlocked: true, + wilderness: true + }, + { + monster: Monsters.MagicAxe, + amount: [75, 125], + weight: 7, + monsters: [Monsters.MagicAxe.id], + unlocked: true, + wilderness: true + }, + { + monster: Monsters.Mammoth, + amount: [75, 125], + weight: 6, + monsters: [Monsters.Mammoth.id], + unlocked: true, + wilderness: true + }, + { + monster: Monsters.MossGiant, + amount: [100, 150], + weight: 4, + monsters: [Monsters.MossGiant.id], + unlocked: true, + wilderness: true + }, + { + monster: Monsters.GreaterNechryael, + amount: [75, 125], + weight: 5, + monsters: [Monsters.GreaterNechryael.id], + extendedAmount: [200, 250], + extendedUnlockId: SlayerTaskUnlocksEnum.NechsPlease, + slayerLevel: 80, + unlocked: true, + wilderness: true + }, + { + monster: Monsters.Pirate, + amount: [62, 75], + weight: 3, + monsters: [Monsters.Pirate.id], + unlocked: true, + wilderness: true + }, + { + monster: Monsters.RevenantImp, + amount: [40, 100], + weight: 5, + monsters: [ + Monsters.RevenantCyclops.id, + Monsters.RevenantDarkBeast.id, + Monsters.RevenantDemon.id, + Monsters.RevenantDragon.id, + Monsters.RevenantGoblin.id, + Monsters.RevenantHellhound.id, + Monsters.RevenantHobgoblin.id, + Monsters.RevenantImp.id, + Monsters.RevenantKnight.id, + Monsters.RevenantOrk.id, + Monsters.RevenantPyrefiend.id + ], + extendedAmount: [100, 150], + extendedUnlockId: SlayerTaskUnlocksEnum.Revenenenenenants, + unlocked: true, + wilderness: true + }, + // { + // monster: Monsters.Rogue, + // amount: [75, 125], + // weight: 5, + // monsters: [Monsters.Rogue.id], + // unlocked: true, + // wilderness: true + // }, + { + monster: Monsters.Scorpion, + amount: [65, 100], + weight: 6, + monsters: [Monsters.Scorpia.id, Monsters.Scorpion.id], + unlocked: true, + wilderness: true + }, + { + monster: Monsters.Skeleton, + amount: [65, 100], + weight: 5, + monsters: [Monsters.Skeleton.id, Monsters.Vetion.id, Monsters.Calvarion.id], + unlocked: true, + wilderness: true + }, + { + monster: Monsters.Spider, + amount: [65, 100], + weight: 6, + monsters: [Monsters.Spider.id, Monsters.Venenatis.id, Monsters.Spindel.id], + unlocked: true, + wilderness: true + }, + { + monster: Monsters.SpiritualRanger, + amount: [100, 150], + weight: 6, + monsters: [Monsters.SpiritualMage.id, Monsters.SpiritualRanger.id, Monsters.SpiritualWarrior.id], + extendedAmount: [181, 250], + extendedUnlockId: SlayerTaskUnlocksEnum.SpiritualFervour, + slayerLevel: 63, + unlocked: true, + wilderness: true + }, + { + monster: Monsters.Zombie, + amount: [75, 125], + weight: 3, + monsters: [Monsters.Zombie.id], + unlocked: true, + wilderness: true + }, + ...wildernessBossTasks +]; diff --git a/src/lib/slayer/tasks/mazchnaTasks.ts b/src/lib/slayer/tasks/mazchnaTasks.ts index 3a9385395d4..962ce16b7bb 100644 --- a/src/lib/slayer/tasks/mazchnaTasks.ts +++ b/src/lib/slayer/tasks/mazchnaTasks.ts @@ -3,7 +3,7 @@ import { Monsters } from 'oldschooljs'; import killableMonsters from '../../minions/data/killableMonsters'; import { KalphiteKingMonster } from '../../minions/data/killableMonsters/custom/bosses/KalphiteKing'; import { BSOMonsters } from '../../minions/data/killableMonsters/custom/customMonsters'; -import { AssignableSlayerTask } from '../types'; +import type { AssignableSlayerTask } from '../types'; export const mazchnaTasks: AssignableSlayerTask[] = [ { @@ -103,7 +103,7 @@ export const mazchnaTasks: AssignableSlayerTask[] = [ amount: [40, 70], weight: 6, monsters: [Monsters.EarthWarrior.id], - levelRequirements: killableMonsters.find(k => k.id === Monsters.EarthWarrior.id)!.levelRequirements, + levelRequirements: killableMonsters.find(k => k.id === Monsters.EarthWarrior.id)?.levelRequirements, combatLevel: 35, unlocked: true }, diff --git a/src/lib/slayer/tasks/nieveTasks.ts b/src/lib/slayer/tasks/nieveTasks.ts index 034e1994111..7f0df346539 100644 --- a/src/lib/slayer/tasks/nieveTasks.ts +++ b/src/lib/slayer/tasks/nieveTasks.ts @@ -5,7 +5,7 @@ import { Ignecarus } from '../../minions/data/killableMonsters/custom/bosses/Ign import { KalphiteKingMonster } from '../../minions/data/killableMonsters/custom/bosses/KalphiteKing'; import { BSOMonsters } from '../../minions/data/killableMonsters/custom/customMonsters'; import { SlayerTaskUnlocksEnum } from '../slayerUnlocks'; -import { AssignableSlayerTask } from '../types'; +import type { AssignableSlayerTask } from '../types'; import { bossTasks } from './bossTasks'; import { polyporeTasks } from './polyporeTasks'; @@ -379,7 +379,7 @@ export const nieveTasks: AssignableSlayerTask[] = [ amount: [30, 60], weight: 5, monsters: [Monsters.SteelDragon.id], - levelRequirements: killableMonsters.find(k => k.id === Monsters.SteelDragon.id)!.levelRequirements, + levelRequirements: killableMonsters.find(k => k.id === Monsters.SteelDragon.id)?.levelRequirements, combatLevel: 85, questPoints: 34, unlocked: true diff --git a/src/lib/slayer/tasks/polyporeTasks.ts b/src/lib/slayer/tasks/polyporeTasks.ts index c1f2afc9a3d..24656f95a6d 100644 --- a/src/lib/slayer/tasks/polyporeTasks.ts +++ b/src/lib/slayer/tasks/polyporeTasks.ts @@ -1,6 +1,6 @@ import { BSOMonsters } from '../../minions/data/killableMonsters/custom/customMonsters'; import { getMonster } from '../../util'; -import { AssignableSlayerTask } from '../types'; +import type { AssignableSlayerTask } from '../types'; export const polyporeTasks: AssignableSlayerTask[] = [ { diff --git a/src/lib/slayer/tasks/turaelTasks.ts b/src/lib/slayer/tasks/turaelTasks.ts index 09e0435653d..401bc47b47f 100644 --- a/src/lib/slayer/tasks/turaelTasks.ts +++ b/src/lib/slayer/tasks/turaelTasks.ts @@ -2,7 +2,7 @@ import { Monsters } from 'oldschooljs'; import { KalphiteKingMonster } from '../../minions/data/killableMonsters/custom/bosses/KalphiteKing'; import { BSOMonsters } from '../../minions/data/killableMonsters/custom/customMonsters'; -import { AssignableSlayerTask } from '../types'; +import type { AssignableSlayerTask } from '../types'; export const turaelTasks: AssignableSlayerTask[] = [ { diff --git a/src/lib/slayer/tasks/vannakaTasks.ts b/src/lib/slayer/tasks/vannakaTasks.ts index bbabbaf594d..cab2e814b77 100644 --- a/src/lib/slayer/tasks/vannakaTasks.ts +++ b/src/lib/slayer/tasks/vannakaTasks.ts @@ -3,7 +3,7 @@ import { Monsters } from 'oldschooljs'; import killableMonsters from '../../minions/data/killableMonsters'; import { KalphiteKingMonster } from '../../minions/data/killableMonsters/custom/bosses/KalphiteKing'; import { BSOMonsters } from '../../minions/data/killableMonsters/custom/customMonsters'; -import { AssignableSlayerTask } from '../types'; +import type { AssignableSlayerTask } from '../types'; export const vannakaTasks: AssignableSlayerTask[] = [ { @@ -178,7 +178,7 @@ export const vannakaTasks: AssignableSlayerTask[] = [ amount: [40, 80], weight: 6, monsters: [Monsters.EarthWarrior.id], - levelRequirements: killableMonsters.find(k => k.id === Monsters.EarthWarrior.id)!.levelRequirements, + levelRequirements: killableMonsters.find(k => k.id === Monsters.EarthWarrior.id)?.levelRequirements, combatLevel: 35, unlocked: true }, diff --git a/src/lib/slayer/types.ts b/src/lib/slayer/types.ts index 2a1bffb60ec..0ae74811065 100644 --- a/src/lib/slayer/types.ts +++ b/src/lib/slayer/types.ts @@ -1,8 +1,8 @@ -import { MonsterSlayerMaster } from 'oldschooljs'; -import { Item } from 'oldschooljs/dist/meta/types'; -import Monster from 'oldschooljs/dist/structures/Monster'; +import type { MonsterSlayerMaster } from 'oldschooljs'; +import type { Item } from 'oldschooljs/dist/meta/types'; +import type Monster from 'oldschooljs/dist/structures/Monster'; -import { LevelRequirements } from '../skilling/types'; +import type { LevelRequirements } from '../skilling/types'; export interface AssignableSlayerTask { monster: Monster; @@ -18,6 +18,7 @@ export interface AssignableSlayerTask { dontAssign?: boolean; extendedAmount?: [number, number]; extendedUnlockId?: number; + wilderness?: boolean; dungeoneeringLevel?: number; } diff --git a/src/lib/sorts.ts b/src/lib/sorts.ts index 8f7d7676dda..a214ce510be 100644 --- a/src/lib/sorts.ts +++ b/src/lib/sorts.ts @@ -1,4 +1,4 @@ -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; import { marketPriceOrBotPrice } from './marketPrices'; diff --git a/src/lib/startupScripts.ts b/src/lib/startupScripts.ts index c7ae96e5b2e..ca417284a4d 100644 --- a/src/lib/startupScripts.ts +++ b/src/lib/startupScripts.ts @@ -1,44 +1,61 @@ import { Items } from 'oldschooljs'; +import { globalConfig } from './constants'; -import { prisma } from './settings/prisma'; -import { logError } from './util/logError'; +const startupScripts: { sql: string; ignoreErrors?: true }[] = []; -export const startupScripts: { sql: string; ignoreErrors?: true }[] = []; - -const arrayColumns = [ - ['clientStorage', 'userBlacklist'], - ['clientStorage', 'guildBlacklist'], - ['guilds', 'disabledCommands'], - ['guilds', 'staffOnlyChannels'], - ['users', 'badges'], - ['users', 'bitfield'], - ['users', 'favoriteItems'], - ['users', 'favorite_alchables'], - ['users', 'favorite_food'], - ['users', 'favorite_bh_seeds'], - ['users', 'attack_style'], - ['users', 'combat_options'], - ['users', 'ironman_alts'], - ['users', 'slayer.unlocks'], - ['users', 'slayer.blocked_ids'], - ['users', 'slayer.autoslay_options'], - ['users', 'monkeys_fought'], - ['users', 'unlocked_blueprints'], - ['users', 'disabled_inventions'], - ['users', 'unlocked_gear_templates'] -]; +startupScripts.push({ + sql: `CREATE OR REPLACE FUNCTION add_item_to_bank( + bank JSONB, + key TEXT, + quantity INT +) RETURNS JSONB LANGUAGE plpgsql AS $$ +BEGIN + RETURN ( + CASE + WHEN bank ? key THEN + jsonb_set( + bank, + ARRAY[key], + to_jsonb((bank->>key)::INT + quantity) + ) + ELSE + jsonb_set( + bank, + ARRAY[key], + to_jsonb(quantity) + ) + END + ); +END; +$$;` +}); -for (const [table, column] of arrayColumns) { - startupScripts.push({ - sql: `UPDATE "${table}" SET "${column}" = '{}' WHERE "${column}" IS NULL;` - }); - startupScripts.push({ - sql: ` -ALTER TABLE "${table}" - ALTER COLUMN "${column}" SET DEFAULT '{}', - ALTER COLUMN "${column}" SET NOT NULL;` - }); -} +startupScripts.push({ + sql: `CREATE OR REPLACE FUNCTION remove_item_from_bank( + bank JSONB, + key TEXT, + quantity INT +) RETURNS JSONB LANGUAGE plpgsql AS $$ +DECLARE + current_value INT; +BEGIN + IF bank ? key THEN + current_value := (bank->>key)::INT - quantity; + IF current_value > 0 THEN + RETURN jsonb_set( + bank, + ARRAY[key], + to_jsonb(current_value) + ); + ELSE + RETURN bank - key; + END IF; + ELSE + RETURN bank; + END IF; +END; +$$;` +}); interface CheckConstraint { table: string; @@ -47,29 +64,17 @@ interface CheckConstraint { body: string; } const checkConstraints: CheckConstraint[] = [ - { - table: 'users', - column: 'lms_points', - name: 'users_lms_points_min', - body: 'lms_points >= 0' - }, { table: 'users', column: '"GP"', name: 'users_gp', body: '"GP" >= 0' }, - { - table: 'users', - column: '"QP"', - name: 'users_qp', - body: '"QP" >= 0' - }, { table: 'ge_listing', column: 'asking_price_per_item', name: 'asking_price_per_item_min', - body: 'asking_price_per_item_min >= 1' + body: 'asking_price_per_item >= 1' }, { table: 'ge_listing', @@ -120,9 +125,22 @@ const checkConstraints: CheckConstraint[] = [ body: 'quantity >= 0' } ]; + for (const { table, name, body } of checkConstraints) { - startupScripts.push({ sql: `ALTER TABLE ${table} ADD CONSTRAINT ${name} CHECK (${body});`, ignoreErrors: true }); + startupScripts.push({ + sql: `DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 + FROM information_schema.check_constraints + WHERE constraint_name = '${name}' + AND constraint_schema = 'public') + THEN + ALTER TABLE "${table}" ADD CONSTRAINT "${name}" CHECK (${body}); + END IF; +END$$;` + }); } + startupScripts.push({ sql: 'CREATE UNIQUE INDEX IF NOT EXISTS activity_only_one_task ON activity (user_id, completed) WHERE NOT completed;' }); @@ -130,6 +148,19 @@ startupScripts.push({ sql: 'CREATE UNIQUE INDEX IF NOT EXISTS tame_only_one_task ON tame_activity (user_id, completed) WHERE NOT completed;' }); +startupScripts.push({ + sql: `CREATE INDEX IF NOT EXISTS idx_ge_listing_buy_filter_sort +ON ge_listing (type, fulfilled_at, cancelled_at, user_id, asking_price_per_item DESC, created_at ASC);` +}); +startupScripts.push({ + sql: `CREATE INDEX IF NOT EXISTS idx_ge_listing_sell_filter_sort +ON ge_listing (type, fulfilled_at, cancelled_at, user_id, asking_price_per_item ASC, created_at ASC);` +}); + +startupScripts.push({ + sql: `CREATE INDEX IF NOT EXISTS ge_transaction_sell_listing_id_created_at_idx +ON ge_transaction (sell_listing_id, created_at DESC);` +}); const itemMetaDataNames = Items.map(item => `(${item.id}, '${item.name.replace(/'/g, "''")}')`).join(', '); const itemMetaDataQuery = ` INSERT INTO item_metadata (id, name) @@ -139,13 +170,10 @@ DO UPDATE SET name = EXCLUDED.name WHERE item_metadata.name IS DISTINCT FROM EXCLUDED.name; `; - -startupScripts.push({ sql: itemMetaDataQuery }); +if (globalConfig.isProduction) { + startupScripts.push({ sql: itemMetaDataQuery }); +} export async function runStartupScripts() { - for (const query of startupScripts) { - await prisma - .$queryRawUnsafe(query.sql) - .catch(err => (query.ignoreErrors ? null : logError(`Startup script failed: ${err.message} ${query.sql}`))); - } + await prisma.$transaction(startupScripts.map(query => prisma.$queryRawUnsafe(query.sql))); } diff --git a/src/lib/structures/Bank.ts b/src/lib/structures/Bank.ts new file mode 100644 index 00000000000..adc1d727639 --- /dev/null +++ b/src/lib/structures/Bank.ts @@ -0,0 +1,10 @@ +import { GeneralBank, type GeneralBankType } from '@oldschoolgg/toolkit'; + +import type { DegradeableItem } from '../degradeableItems'; +import { degradeableItems } from '../degradeableItems'; + +export class ChargeBank extends GeneralBank { + constructor(initialBank?: GeneralBankType) { + super({ initialBank, allowedKeys: degradeableItems.map(i => i.settingsKey) }); + } +} diff --git a/src/lib/structures/Banks.ts b/src/lib/structures/Banks.ts index 501ae9cc36f..d98dc24e334 100644 --- a/src/lib/structures/Banks.ts +++ b/src/lib/structures/Banks.ts @@ -1,6 +1,6 @@ -import { DegradeableItem, degradeableItems } from '../degradeableItems'; -import { SkillNameType, SkillsArray } from '../skilling/types'; -import { GeneralBank, GeneralBankType } from './GeneralBank'; +import { GeneralBank, type GeneralBankType } from '@oldschoolgg/toolkit'; +import { type DegradeableItem, degradeableItems } from '../degradeableItems'; +import { type SkillNameType, SkillsArray } from '../skilling/types'; export class ChargeBank extends GeneralBank { constructor(initialBank?: GeneralBankType) { diff --git a/src/lib/structures/Boss.ts b/src/lib/structures/Boss.ts index 05eddab7abc..7f0361998c5 100644 --- a/src/lib/structures/Boss.ts +++ b/src/lib/structures/Boss.ts @@ -1,18 +1,18 @@ -import { AttachmentBuilder, BaseMessageOptions, TextChannel } from 'discord.js'; -import { calcPercentOfNum, calcWhatPercent, randFloat, reduceNumByPercent, sumArr, Time } from 'e'; +import { AttachmentBuilder, type BaseMessageOptions, type TextChannel } from 'discord.js'; +import { Time, calcPercentOfNum, calcWhatPercent, randFloat, reduceNumByPercent, sumArr } from 'e'; import { Bank } from 'oldschooljs'; -import { GearSetupType, GearStats } from '../gear'; +import type { GearSetupType, GearStats } from '../gear'; import { trackLoot } from '../lootTrack'; import { effectiveMonsters } from '../minions/data/killableMonsters'; import { setupParty } from '../party'; -import { Skills } from '../types'; -import { NewBossOptions } from '../types/minions'; +import type { Skills } from '../types'; +import type { NewBossOptions } from '../types/minions'; import { formatDuration, formatSkillRequirements, hasSkillReqs, isWeekend, makeTable } from '../util'; import addSubTaskToActivityTask from '../util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../util/calcMaxTripLength'; -import { ClientBankKey, updateBankSetting } from '../util/updateBankSetting'; -import { Gear } from './Gear'; +import { type ClientBankKey, updateBankSetting } from '../util/updateBankSetting'; +import type { Gear } from './Gear'; export const gpCostPerKill = (user: MUser) => user.gear.melee.hasEquipped(['Ring of charos', 'Ring of charos(a)'], false) ? 5_000_000 : 10_000_000; @@ -22,7 +22,7 @@ export const calcDwwhChance = (users: MUser[]) => { const baseRate = 850; const modDenominator = 15; - let dropRate = (baseRate / 2) * (1 + size / modDenominator); + const dropRate = (baseRate / 2) * (1 + size / modDenominator); let groupRate = Math.ceil(dropRate / size); groupRate = Math.ceil(groupRate); @@ -96,7 +96,7 @@ function calcSetupPercent( totalPercent = Math.floor(Math.max(0, totalPercent / 2)); } - if (isNaN(totalPercent) || totalPercent < 0 || totalPercent > 100) { + if (Number.isNaN(totalPercent) || totalPercent < 0 || totalPercent > 100) { throw new Error(`Invalid total gear percent: ${totalPercent}`); } @@ -167,12 +167,12 @@ export class BossInstance { ignoreStats: (keyof GearStats)[] = []; food: Bank | ((user: MUser) => Bank); bossUsers: BossUser[] = []; - duration: number = -1; + duration = -1; quantity: number | null = null; tempQty: number | null = null; - allowMoreThan1Solo: boolean = false; - allowMoreThan1Group: boolean = false; - totalPercent: number = -1; + allowMoreThan1Solo = false; + allowMoreThan1Group = false; + totalPercent = -1; settingsKeys?: [ClientBankKey, ClientBankKey]; channel: TextChannel; activity: 'VasaMagus' | 'KingGoldemar' | 'Ignecarus' | 'BossEvent'; @@ -188,9 +188,9 @@ export class BossInstance { automaticStartTime: number; maxSize: number; boostMax: number | null = null; - speedMaxReduction: number = 40; - speedGearWeight: number = 25; - speedKcWeight: number = 35; + speedMaxReduction = 40; + speedGearWeight = 25; + speedKcWeight = 35; skipInvalidUsers?: boolean = false; allowedMentions?: BaseMessageOptions['allowedMentions']; @@ -223,7 +223,7 @@ export class BossInstance { this.allowMoreThan1Group = options.allowMoreThan1Group ?? false; this.quantity = options.quantity ?? null; this.maxSize = options.maxSize ?? 10; - let massText = [options.massText, '\n']; + const massText = [options.massText, '\n']; if (Object.keys(this.skillRequirements).length > 0) { massText.push(`**Skill Reqs:** ${formatSkillRequirements(this.skillRequirements)}`); } @@ -253,7 +253,7 @@ export class BossInstance { } calculateQty(duration: number) { - let baseQty = this.tempQty; + const baseQty = this.tempQty; // Calculate max kill qty let tempQty = 1; const maxTripLength = this.leader ? calcMaxTripLength(this.leader, this.activity) : Time.Hour; @@ -285,7 +285,7 @@ export class BossInstance { message: this.massText, massTimeout: this.automaticStartTime, allowedMentions: this.allowedMentions - }); + }); this.tempQty = this.quantity; // Force qty to 1 for init calculations @@ -340,7 +340,7 @@ export class BossInstance { async calcFoodForUser(user: MUser, solo = false) { const kc = await user.getKC(this.id); - let itemsToRemove = calcFood(solo, kc); + const itemsToRemove = calcFood(solo, kc); if (this.itemCost) { return this.itemCost({ user, kills: this.quantity ?? 0, baseFood: itemsToRemove, solo }); } @@ -365,7 +365,7 @@ export class BossInstance { // Track user len outside the loop because the loop corrupts it. (calcFoodForUser()) for (const user of this.users!) { const gear = user.gear[this.gearSetup]; - let debugStr = []; + const debugStr = []; let userPercentChange = 0; // Gear @@ -499,7 +499,7 @@ export class BossInstance { async simulate() { const arr = Array(30).fill(this.leader); - let results: any[] = []; + const results: any[] = []; for (const num of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) { this.users = arr.slice(0, num); const { bossUsers, duration } = await this.calculateBossUsers(); diff --git a/src/lib/structures/Gear.ts b/src/lib/structures/Gear.ts index ff0d5d98bf1..d8665db347e 100644 --- a/src/lib/structures/Gear.ts +++ b/src/lib/structures/Gear.ts @@ -1,24 +1,25 @@ -import { GearPreset } from '@prisma/client'; +import type { GearPreset } from '@prisma/client'; import { notEmpty, objectKeys, uniqueArr } from 'e'; import { Bank } from 'oldschooljs'; -import { EquipmentSlot, Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; +import { EquipmentSlot } from 'oldschooljs/dist/meta/types'; +import { resolveItems } from 'oldschooljs/dist/util/util'; import { getSimilarItems, inverseSimilarItems } from '../data/similarItems'; -import { +import type { DefenceGearStat, GearSetup, GearSetupType, GearSlotItem, - GearStat, GearStats, OffenceGearStat, OtherGearStat } from '../gear/types'; -import { GearRequirement } from '../minions/types'; +import { GearStat } from '../gear/types'; +import type { GearRequirement } from '../minions/types'; import { assert } from '../util'; import getOSItem from '../util/getOSItem'; import itemID from '../util/itemID'; -import resolveItems from '../util/resolveItems'; export type PartialGearSetup = Partial<{ [key in EquipmentSlot]: string; @@ -26,10 +27,10 @@ export type PartialGearSetup = Partial<{ export function addStatsOfItemsTogether(items: number[], statWhitelist = Object.values(GearStat)) { const osItems = items.map(i => getOSItem(i)); - let base: Required = {} as Required; + const base: Required = {} as Required; for (const item of osItems) { for (const stat of Object.values(GearStat)) { - let thisStat = item.equipment?.[stat] ?? 0; + const thisStat = item.equipment?.[stat] ?? 0; if (!base[stat]) base[stat] = 0; if (statWhitelist.includes(stat)) { base[stat] += thisStat; @@ -88,14 +89,7 @@ export const defaultGear: GearSetup = { [EquipmentSlot.Weapon]: null }; Object.freeze(defaultGear); -export function filterGearSetup(gear: undefined | null | GearSetup | PartialGearSetup): GearSetup | undefined { - const filteredGear = !gear - ? undefined - : typeof gear.ammo === 'undefined' || typeof gear.ammo === 'string' - ? constructGearSetup(gear as PartialGearSetup) - : (gear as GearSetup); - return filteredGear; -} + export const globalPresets: (GearPreset & { defaultSetup: GearSetupType })[] = [ { name: 'graceful', @@ -558,7 +552,7 @@ export class Gear { if (allItems.length === 0) { return 'No items'; } - let items = []; + const items = []; for (const item of allItems) { items.push(getOSItem(item).name); } diff --git a/src/lib/structures/GeneralBank.ts b/src/lib/structures/GeneralBank.ts deleted file mode 100644 index b360b62650a..00000000000 --- a/src/lib/structures/GeneralBank.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { mathjs } from '../constants'; -import { assert } from '../util'; - -export type GeneralBankType = Record; - -interface GeneralBankValueSchema { - min: number; - max: number; - floats: boolean; -} - -interface BankValidator { - (key: T, value: number, bank: GeneralBankType): void; -} - -export class GeneralBank { - private bank: GeneralBankType; - private allowedKeys?: Set; - private validator?: BankValidator; - private valueSchema: GeneralBankValueSchema; - - constructor({ - allowedKeys, - validator, - initialBank, - valueSchema - }: { - allowedKeys?: T[] | readonly T[]; - validator?: BankValidator; - initialBank?: GeneralBankType; - valueSchema?: GeneralBankValueSchema; - } = {}) { - this.bank = initialBank ?? ({} as GeneralBankType); - this.allowedKeys = allowedKeys ? new Set(allowedKeys) : undefined; - this.validator = validator; - - this.valueSchema = valueSchema ?? { min: 1, max: Number.MAX_SAFE_INTEGER, floats: false }; - if (this.valueSchema.min < 0) throw new Error('Value schema min must be non-negative.'); - if (this.valueSchema.max < this.valueSchema.min) throw new Error('Value schema max must be greater than min.'); - - this.validate(); - } - - clone(): GeneralBank { - return new GeneralBank({ - allowedKeys: this.allowedKeys ? Array.from(this.allowedKeys) : undefined, - validator: this.validator, - initialBank: { ...this.bank }, - valueSchema: this.valueSchema - }); - } - - validate(): void { - for (const key of Object.keys(this.bank) as T[]) { - const value = this.bank[key]; - if (this.allowedKeys && !this.allowedKeys.has(key)) { - throw new Error(`Key ${key} is not allowed.`); - } - assert( - typeof value === 'number' && value >= this.valueSchema.min && value <= this.valueSchema.max, - `Invalid value (not within minmax ${this.valueSchema.min}-${this.valueSchema.max}) for ${key}: ${value}` - ); - if (!this.valueSchema.floats) { - assert(Number.isInteger(value), `Value for ${key} is not an integer: ${value}`); - } - this.validator?.(key, value, this.bank); - } - } - - entries() { - return Object.entries(this.bank) as [T, number][]; - } - - amount(key: T): number { - return this.bank[key] ?? 0; - } - - has(key: T): boolean { - return this.amount(key) >= 1; - } - - toString(): string { - const entries = Object.entries(this.bank); - if (entries.length === 0) return 'Bank is empty'; - return entries.map(([key, value]) => `${key}: ${value}`).join(', '); - } - - private addItem(key: T, quantity: number): this { - assert(quantity >= 0, 'Quantity must be non-negative.'); - if (this.allowedKeys && !this.allowedKeys.has(key)) { - throw new Error(`Key ${key} is not allowed.`); - } - const newValue = mathjs.add(this.amount(key), quantity); - if (newValue > this.valueSchema.max) { - throw new Error(`Value for ${key} exceeds the maximum of ${this.valueSchema.max}.`); - } - this.bank[key] = newValue; - this.validate(); - return this; - } - - private removeItem(key: T, quantity: number): this { - assert(quantity >= 0, 'Quantity must be non-negative.'); - const currentAmount = this.amount(key); - if (currentAmount < quantity) { - throw new Error(`Not enough ${key} to remove.`); - } - const newValue = mathjs.subtract(currentAmount, quantity); - this.bank[key] = newValue; - if (newValue === 0) { - delete this.bank[key]; - } - this.validate(); - return this; - } - - add(keyOrBank: T | GeneralBank, quantity: number = 1): this { - if (keyOrBank instanceof GeneralBank) { - for (const [key, qty] of keyOrBank.entries()) { - this.addItem(key, qty); - } - } else { - this.addItem(keyOrBank, quantity); - } - return this; - } - - remove(keyOrBank: T | GeneralBank, quantity: number = 1): this { - if (keyOrBank instanceof GeneralBank) { - for (const [key, qty] of Object.entries(keyOrBank.bank) as [T, number][]) { - this.removeItem(key as T, qty); - } - } else { - this.removeItem(keyOrBank, quantity); - } - return this; - } -} diff --git a/src/lib/structures/LastManStandingUsage.ts b/src/lib/structures/LastManStandingUsage.ts index 134da10b55a..41330ba4f7f 100644 --- a/src/lib/structures/LastManStandingUsage.ts +++ b/src/lib/structures/LastManStandingUsage.ts @@ -14,7 +14,8 @@ export default class LastManStandingUsage { public parse(usage: string): void { let current = ''; - for (let i = 0, char; i < usage.length; i++) { + let char: string | undefined = undefined; + for (let i = 0; i < usage.length; i++) { char = usage.charAt(i); if (char === '{') { // If there was text, push buffer diff --git a/src/lib/structures/MTame.ts b/src/lib/structures/MTame.ts index c4a944e937f..37d6fcbb3e5 100644 --- a/src/lib/structures/MTame.ts +++ b/src/lib/structures/MTame.ts @@ -1,11 +1,11 @@ import { type Tame, tame_growth } from '@prisma/client'; -import { round } from 'e'; +import { Time, roll, round } from 'e'; import { Bank, Items } from 'oldschooljs'; -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; import { getSimilarItems } from '../data/similarItems'; -import { prisma } from '../settings/prisma'; -import { Species, tameFeedableItems, tameSpecies, TameSpeciesID } from '../tames'; + +import { type Species, TameSpeciesID, tameFeedableItems, tameSpecies } from '../tames'; import type { ItemBank } from '../types'; import getOSItem from '../util/getOSItem'; @@ -14,15 +14,21 @@ export class MTame { id: number; userID: string; species: Species; - growthStage: string; + growthStage: tame_growth; fedItems: Bank; equippedArmor: Item | null; equippedPrimary: Item | null; nickname: string | null; - maxSupportLevel: number; growthLevel: number; currentSupportLevel: number; totalLoot: Bank; + speciesVariant: number; + + maxSupportLevel: number; + maxCombatLevel: number; + maxGathererLevel: number; + maxArtisanLevel: number; + growthPercentage: number; private currentLevel(maxLevel: number) { return round(maxLevel / this.growthLevel, 2); @@ -38,16 +44,64 @@ export class MTame { this.fedItems = new Bank(this.tame.fed_items as ItemBank); this.equippedArmor = tame.equipped_armor === null ? null : getOSItem(tame.equipped_armor); this.equippedPrimary = tame.equipped_primary === null ? null : getOSItem(tame.equipped_primary); - this.maxSupportLevel = tame.max_support_level; this.growthLevel = 3 - [tame_growth.baby, tame_growth.juvenile, tame_growth.adult].indexOf(tame.growth_stage); - this.currentSupportLevel = this.currentLevel(this.maxSupportLevel); this.totalLoot = new Bank(this.tame.max_total_loot as ItemBank); + this.speciesVariant = tame.species_variant; + this.growthPercentage = tame.growth_percent; + + this.maxSupportLevel = tame.max_support_level; + this.maxCombatLevel = tame.max_combat_level; + this.maxGathererLevel = tame.max_gatherer_level; + this.maxArtisanLevel = tame.max_artisan_level; + + this.currentSupportLevel = this.currentLevel(this.maxSupportLevel); } toString() { return `${this.nickname ?? this.species.name}`; } + async addDuration(durationMilliseconds: number): Promise { + if (this.growthStage === tame_growth.adult) return null; + const percentToAdd = durationMilliseconds / Time.Minute / 20; + const newPercent = Math.max(1, Math.min(100, this.growthPercentage + percentToAdd)); + + if (newPercent >= 100) { + const newTame = await prisma.tame.update({ + where: { + id: this.id + }, + data: { + growth_stage: this.growthStage === tame_growth.baby ? tame_growth.juvenile : tame_growth.adult, + growth_percent: 0 + } + }); + return `Your tame has grown into a ${newTame.growth_stage}!`; + } + + await prisma.tame.update({ + where: { + id: this.id + }, + data: { + growth_percent: newPercent + } + }); + + return `Your tame has grown ${percentToAdd.toFixed(2)}%!`; + } + + doubleLootCheck(loot: Bank) { + const hasMrE = this.fedItems.has('Mr. E'); + let doubleLootMsg = ''; + if (hasMrE && roll(12)) { + loot.multiply(2); + doubleLootMsg = '\n**2x Loot from Mr. E**'; + } + + return { loot, doubleLootMsg }; + } + hasBeenFed(itemID: number | string) { const { id } = Items.get(itemID)!; const items = getSimilarItems(id); diff --git a/src/lib/structures/MUserStats.ts b/src/lib/structures/MUserStats.ts index 9451c83ca22..3f4e3c80081 100644 --- a/src/lib/structures/MUserStats.ts +++ b/src/lib/structures/MUserStats.ts @@ -1,21 +1,32 @@ -import { UserStats } from '@prisma/client'; +import type { UserStats } from '@prisma/client'; import { Bank } from 'oldschooljs'; import { ClueTiers } from '../clues/clueTiers'; -import { ClueBank } from '../minions/types'; -import { prisma } from '../settings/prisma'; -import { ItemBank } from '../types'; +import type { ClueBank } from '../minions/types'; +import type { ItemBank } from '../types'; import { getToaKCs } from '../util/smallUtils'; export class MUserStats { userStats: UserStats; baHonourLevel: number; sacrificedBank: Bank; + titheFarmsCompleted: number; + lapsScores: ItemBank; + openableScores: Bank; + kcBank: ItemBank; + highGambles: number; + gotrRiftSearches: number; constructor(userStats: UserStats) { this.userStats = userStats; this.baHonourLevel = userStats.honour_level; this.sacrificedBank = new Bank().add(this.userStats.sacrificed_bank as ItemBank); + this.titheFarmsCompleted = this.userStats.tithe_farms_completed; + this.lapsScores = userStats.laps_scores as ItemBank; + this.openableScores = new Bank().add(userStats.openable_scores as ItemBank); + this.kcBank = userStats.monster_scores as ItemBank; + this.highGambles = userStats.high_gambles; + this.gotrRiftSearches = userStats.gotr_rift_searches; } static async fromID(id: string) { @@ -31,10 +42,6 @@ export class MUserStats { return new MUserStats(userStats); } - get lapsScores() { - return this.userStats.laps_scores as ItemBank; - } - getToaKCs() { return getToaKCs(this.userStats.toa_raid_levels_bank); } @@ -44,7 +51,7 @@ export class MUserStats { for (const tier of ClueTiers) clueCounts[tier.name] = 0; for (const [key, val] of Object.entries(this.userStats.openable_scores as ItemBank)) { - const clueTier = ClueTiers.find(i => i.id === parseInt(key)); + const clueTier = ClueTiers.find(i => i.id === Number.parseInt(key)); if (!clueTier) continue; clueCounts[clueTier.name] += val; } diff --git a/src/lib/structures/OldSchoolBotClient.ts b/src/lib/structures/OldSchoolBotClient.ts index 9fc3db3a39b..4514c8ece10 100644 --- a/src/lib/structures/OldSchoolBotClient.ts +++ b/src/lib/structures/OldSchoolBotClient.ts @@ -1,27 +1,16 @@ -import { Client, ClientOptions, User } from 'discord.js'; -import { FastifyInstance } from 'fastify'; -import { MahojiClient } from 'mahoji'; +import type { MahojiClient } from '@oldschoolgg/toolkit'; +import type { User } from 'discord.js'; +import { Client } from 'discord.js'; -import { production } from '../../config'; -import { Peak } from '../tickers'; - -if (typeof production !== 'boolean') { - throw new Error('Must provide production boolean.'); -} +import type { Peak } from '../tickers'; export class OldSchoolBotClient extends Client { public busyCounterCache = new Map(); - public production = production ?? false; public mahojiClient!: MahojiClient; public isShuttingDown = false; _badgeCache: Map = new Map(); _peakIntervalCache!: Peak[]; - fastifyServer!: FastifyInstance; - - public constructor(clientOptions: ClientOptions) { - super(clientOptions); - } async fetchUser(id: string | bigint): Promise { const user = await this.users.fetch(typeof id === 'string' ? id : id.toString()); diff --git a/src/lib/structures/PercentCounter.ts b/src/lib/structures/PercentCounter.ts index 220ea69208c..82e0b6b4942 100644 --- a/src/lib/structures/PercentCounter.ts +++ b/src/lib/structures/PercentCounter.ts @@ -12,7 +12,7 @@ export class PercentCounter { } add(isApplying: boolean, percent: number, message: string) { - let change = this.value * (percent / 100); + const change = this.value * (percent / 100); const formattedChange = this.type === 'time' ? formatDuration(change, true) : `${change.toFixed(2)}%`; this[isApplying ? 'messages' : 'missed'].push( diff --git a/src/lib/structures/Requirements.ts b/src/lib/structures/Requirements.ts index 5e6862bf26e..6a215a736d0 100644 --- a/src/lib/structures/Requirements.ts +++ b/src/lib/structures/Requirements.ts @@ -1,18 +1,21 @@ -import { Minigame } from '@prisma/client'; +import type { Minigame, PlayerOwnedHouse, activity_type_enum } from '@prisma/client'; import { calcWhatPercent, objectEntries } from 'e'; -import { Bank } from 'oldschooljs'; - -import { getParsedStashUnits, ParsedUnit } from '../../mahoji/lib/abstracted_commands/stashUnitsCommand'; -import { ClueTier } from '../clues/clueTiers'; -import { BitField, BitFieldData, BOT_TYPE } from '../constants'; -import { diariesObject, DiaryTierName, userhasDiaryTier } from '../diaries'; +import type { Bank } from 'oldschooljs'; + +import type { ParsedUnit } from '../../mahoji/lib/abstracted_commands/stashUnitsCommand'; +import { getParsedStashUnits } from '../../mahoji/lib/abstracted_commands/stashUnitsCommand'; +import type { ClueTier } from '../clues/clueTiers'; +import type { BitField } from '../constants'; +import { BOT_TYPE, BitFieldData } from '../constants'; +import { diaries, userhasDiaryTierSync } from '../diaries'; import { effectiveMonsters } from '../minions/data/killableMonsters'; -import { ClueBank } from '../minions/types'; +import type { ClueBank, DiaryID, DiaryTierName } from '../minions/types'; import type { RobochimpUser } from '../roboChimp'; -import { MinigameName } from '../settings/minigames'; +import type { MinigameName } from '../settings/minigames'; import Agility from '../skilling/skills/agility'; -import { Skills } from '../types'; +import type { Skills } from '../types'; import { itemNameFromID } from '../util'; +import type { MTame } from './MTame'; import { MUserStats } from './MUserStats'; export interface RequirementFailure { @@ -26,19 +29,13 @@ interface RequirementUserArgs { stats: MUserStats; roboChimpUser: RobochimpUser; clueCounts: ClueBank; + poh: PlayerOwnedHouse; + uniqueRunesCrafted: number[]; + uniqueActivitiesDone: activity_type_enum[]; + tames: MTame[]; } -type ManualHasFunction = ( - args: RequirementUserArgs -) => - | Promise - | RequirementFailure[] - | undefined - | Promise - | string - | Promise - | boolean - | Promise; +type ManualHasFunction = (args: RequirementUserArgs) => RequirementFailure[] | undefined | string | boolean; type Requirement = { name?: string; @@ -53,7 +50,7 @@ type Requirement = { | { OR: Requirement[] } | { minigames: Partial> } | { bitfieldRequirement: BitField } - | { diaryRequirement: [keyof typeof diariesObject, DiaryTierName][] } + | { diaryRequirement: [DiaryID, DiaryTierName][] } | { clueCompletions: Partial> } ); @@ -87,7 +84,7 @@ export class Requirements { if ('kcRequirement' in req) { requirementParts.push( `Kill Count Requirement: ${Object.entries(req.kcRequirement) - .map(([k, v]) => `${v}x ${effectiveMonsters.find(i => i.id === Number(k))!.name}`) + .map(([k, v]) => `${v}x ${effectiveMonsters.find(i => i.id === Number(k))?.name}`) .join(', ')}.` ); } @@ -99,7 +96,7 @@ export class Requirements { if ('lapsRequirement' in req) { requirementParts.push( `Agility Course Laps Requirements: ${Object.entries(req.lapsRequirement) - .map(([k, v]) => `${v}x laps of ${Agility.Courses.find(i => i.id === Number(k))!.name}`) + .map(([k, v]) => `${v}x laps of ${Agility.Courses.find(i => i.id === Number(k))?.name}`) .join(', ')}.` ); } @@ -123,7 +120,7 @@ export class Requirements { if ('diaryRequirement' in req) { requirementParts.push( `Achievement Diary Requirement: ${req.diaryRequirement - .map(i => `${i[1]} ${diariesObject[i[0]].name}`) + .map(i => `${i[1]} ${diaries.find(d => d.id === i[0])?.name}`) .join(', ')}` ); } @@ -155,15 +152,12 @@ export class Requirements { return this; } - async checkSingleRequirement( - requirement: Requirement, - userArgs: RequirementUserArgs - ): Promise { + checkSingleRequirement(requirement: Requirement, userArgs: RequirementUserArgs): RequirementFailure[] { const { user, stats, minigames, clueCounts } = userArgs; const results: RequirementFailure[] = []; if ('has' in requirement) { - const result = await requirement.has(userArgs); + const result = requirement.has(userArgs); if (typeof result === 'boolean') { if (!result) { results.push({ reason: requirement.name }); @@ -211,7 +205,7 @@ export class Requirements { for (const [id, amount] of Object.entries(requirement.kcRequirement)) { if (!kcs[id] || kcs[id] < amount) { missingMonsterNames.push( - `${amount}x ${effectiveMonsters.find(m => m.id === parseInt(id))?.name ?? id}` + `${amount}x ${effectiveMonsters.find(m => m.id === Number.parseInt(id))?.name ?? id}` ); } } @@ -236,7 +230,7 @@ export class Requirements { if (!laps[id] || laps[id] < amount) { results.push({ reason: `You need ${amount}x laps in the ${ - Agility.Courses.find(i => i.id.toString() === id)!.name + Agility.Courses.find(i => i.id.toString() === id)?.name } agility course.` }); } @@ -276,14 +270,18 @@ export class Requirements { } if ('diaryRequirement' in requirement) { - const unmetDiaries = ( - await Promise.all( - requirement.diaryRequirement.map(async ([diary, tier]) => ({ - has: await userhasDiaryTier(user, diariesObject[diary][tier]), - tierName: `${tier} ${diariesObject[diary].name}` - })) - ) - ).filter(i => !i.has[0]); + const unmetDiaries = requirement.diaryRequirement + .map(([diary, tier]) => { + const { hasDiary, diaryGroup } = userhasDiaryTierSync(user, [diary, tier], { + stats, + minigameScores: minigames + }); + return { + has: hasDiary, + tierName: `${tier} ${diaryGroup.name}` + }; + }) + .filter(i => !i.has); if (unmetDiaries.length > 0) { results.push({ reason: `You need to finish these achievement diaries: ${unmetDiaries @@ -304,7 +302,7 @@ export class Requirements { } if ('OR' in requirement) { - const orResults = await Promise.all(requirement.OR.map(req => this.checkSingleRequirement(req, userArgs))); + const orResults = requirement.OR.map(req => this.checkSingleRequirement(req, userArgs)); if (!orResults.some(i => i.length === 0)) { results.push({ reason: `You need to meet one of these requirements:\n${orResults.map((res, index) => { @@ -317,7 +315,7 @@ export class Requirements { return results; } - async check(user: MUser) { + static async fetchRequiredData(user: MUser) { const minigames = await user.fetchMinigames(); const stashUnits = await getParsedStashUnits(user.id); const stats = await MUserStats.fromID(user.id); @@ -325,21 +323,48 @@ export class Requirements { const clueCounts = BOT_TYPE === 'OSB' ? stats.clueScoresFromOpenables() : (await user.calcActualClues()).clueCounts; - const requirementResults = this.requirements.map(async i => ({ - result: await this.checkSingleRequirement(i, { - user, - minigames, - stashUnits, - stats, - roboChimpUser, - clueCounts - }), + const [_uniqueRunesCrafted, uniqueActivitiesDone, poh] = await prisma.$transaction([ + prisma.$queryRaw<{ rune_id: string }[]>`SELECT DISTINCT(data->>'runeID') AS rune_id +FROM activity +WHERE user_id = ${BigInt(user.id)} +AND type = 'Runecraft' +AND data->>'runeID' IS NOT NULL;`, + prisma.$queryRaw<{ type: activity_type_enum }[]>`SELECT DISTINCT(type) +FROM activity +WHERE user_id = ${BigInt(user.id)} +GROUP BY type;`, + prisma.playerOwnedHouse.upsert({ where: { user_id: user.id }, update: {}, create: { user_id: user.id } }) + ]); + const uniqueRunesCrafted = _uniqueRunesCrafted.map(i => Number(i.rune_id)); + + const tames = await user.fetchTames(); + + return { + user, + minigames, + stashUnits, + stats, + roboChimpUser, + clueCounts, + poh, + uniqueRunesCrafted, + uniqueActivitiesDone: uniqueActivitiesDone.map(i => i.type), + tames + }; + } + + static async checkMany(user: MUser, requirements: Requirements[]) { + const data = await Requirements.fetchRequiredData(user); + return requirements.map(i => i.check(data)); + } + + check(data: Awaited>) { + const results = this.requirements.map(i => ({ + result: this.checkSingleRequirement(i, data), requirement: i })); - const results = await Promise.all(requirementResults); - const flatReasons = results.map(r => r.result).flat(); - + const flatReasons = results.flatMap(r => r.result); const totalRequirements = this.requirements.length; const metRequirements = results.filter(i => i.result.length === 0).length; const completionPercentage = calcWhatPercent(metRequirements, totalRequirements); diff --git a/src/lib/systemInfo.ts b/src/lib/systemInfo.ts new file mode 100644 index 00000000000..846ae041165 --- /dev/null +++ b/src/lib/systemInfo.ts @@ -0,0 +1,43 @@ +import { execSync } from 'node:child_process'; +import { PrismaClient } from '@prisma/client'; + +async function getPostgresVersion() { + if (process.env.TEST) return '???'; + const client = new PrismaClient(); + try { + const result = await client.$queryRawUnsafe('SELECT version();'); + const version = result[0].version.split(',')[0]; + return version; + } catch (err) { + return 'UNKNOWN'; + } finally { + await client.$disconnect(); + } +} + +function getCommandOutput(command: string): string { + try { + return execSync(command).toString().trim(); + } catch { + return 'Not Installed'; + } +} + +export async function getSystemInfo() { + const gitBranch = getCommandOutput('git rev-parse --abbrev-ref HEAD'); + const gitHash = getCommandOutput('git rev-parse --short HEAD'); + const yarnVersion = getCommandOutput('yarn --version'); + if (yarnVersion !== '4.3.1') { + console.error('ERROR: You are not using yarn v4.3.1.'); + process.exit(1); + } + if (process.version !== 'v20.15.0') { + console.error('ERROR: You are not using Node v20.15.0.'); + process.exit(1); + } + const postgresVersion = await getPostgresVersion(); + const singleStr = `Node ${process.version} ${postgresVersion} ${gitBranch}[${gitHash}]`; + return { + singleStr + }; +} diff --git a/src/lib/tableBank.ts b/src/lib/tableBank.ts index c4c345251a3..d8c4414a48e 100644 --- a/src/lib/tableBank.ts +++ b/src/lib/tableBank.ts @@ -1,7 +1,6 @@ import { Bank } from 'oldschooljs'; -import { prisma } from './settings/prisma'; -import { ItemBank } from './types'; +import type { ItemBank } from './types'; import { validateBankAndThrow } from './util'; export function makeTransactFromTableBankQueries({ @@ -36,11 +35,6 @@ DO UPDATE SET quantity = "ge_bank"."quantity" + ${quantity};`) return queries; } -export async function transactFromTableBank({ bankToAdd, bankToRemove }: { bankToAdd?: Bank; bankToRemove?: Bank }) { - const queries = makeTransactFromTableBankQueries({ bankToAdd, bankToRemove }); - await prisma.$transaction(queries); -} - export async function fetchTableBank() { const result = await prisma.$queryRawUnsafe<{ bank: ItemBank }[]>( 'SELECT json_object_agg(item_id, quantity) as bank FROM ge_bank WHERE quantity != 0;' diff --git a/src/lib/tames.ts b/src/lib/tames.ts index ccfc6232368..747143439d9 100644 --- a/src/lib/tames.ts +++ b/src/lib/tames.ts @@ -1,16 +1,17 @@ -/* eslint-disable no-case-declarations */ -import { Tame, tame_growth } from '@prisma/client'; -import { objectEntries, Time } from 'e'; +import type { Tame, TameActivity } from '@prisma/client'; +import { Time, objectEntries } from 'e'; import { Bank, Misc, Monsters } from 'oldschooljs'; -import { Item, ItemBank } from 'oldschooljs/dist/meta/types'; +import type { Item, ItemBank } from 'oldschooljs/dist/meta/types'; import { ChambersOfXeric, TheatreOfBlood } from 'oldschooljs/dist/simulation/misc'; import killableMonsters, { NightmareMonster } from './minions/data/killableMonsters'; import { customDemiBosses } from './minions/data/killableMonsters/custom/demiBosses'; import { Planks } from './minions/data/planks'; -import { KillableMonster } from './minions/types'; -import { prisma } from './settings/prisma'; +import type { KillableMonster } from './minions/types'; + +import type { handleFinish } from '../tasks/tames/tameTasks'; import Tanning from './skilling/skills/crafting/craftables/tanning'; +import type { MTame } from './structures/MTame'; import { assert, calculateSimpleMonsterDeathChance } from './util'; import getOSItem from './util/getOSItem'; import { handleSpecialCoxLoot } from './util/handleSpecialCoxLoot'; @@ -204,15 +205,15 @@ export const igneArmors = [ ].map(i => ({ ...i, tameSpecies: [TameSpeciesID.Igne], slot: 'equipped_armor' as const })); export type TameKillableMonster = { - loot: (opts: { quantity: number; tame: Tame }) => Bank; + loot: (opts: { quantity: number; tame: MTame }) => Bank; deathChance?: (opts: { tame: Tame; kc: number }) => number; oriWorks?: boolean; mustBeAdult?: boolean; minArmorTier?: Item; } & Omit; -function calcPointsForTame(tame: Tame) { - const lvl = tame.max_combat_level; +function calcPointsForTame(tame: MTame) { + const lvl = tame.maxCombatLevel; if (lvl < 75) return 25_000; if (lvl < 80) return 26_500; if (lvl < 85) return 28_000; @@ -231,7 +232,7 @@ export const tameKillableMonsters: TameKillableMonster[] = [ difficultyRating: 4, itemsRequired: resolveItems([]), loot({ quantity, tame }) { - let loot = new Bank(); + const loot = new Bank(); for (let i = 0; i < quantity; i++) { loot.add( ChambersOfXeric.complete({ team: [{ id: '1', personalPoints: calcPointsForTame(tame) }] })['1'] @@ -263,9 +264,9 @@ export const tameKillableMonsters: TameKillableMonster[] = [ timeToFinish: Time.Minute * 90, itemsRequired: resolveItems([]), loot({ quantity }) { - let loot = new Bank(); + const loot = new Bank(); for (let i = 0; i < quantity; i++) { - let thisLoot = TheatreOfBlood.complete({ + const thisLoot = TheatreOfBlood.complete({ hardMode: true, team: [ { id: '1', deaths: [] }, @@ -294,7 +295,7 @@ export const tameKillableMonsters: TameKillableMonster[] = [ timeToFinish: Time.Minute * 35, itemsRequired: resolveItems([]), loot({ quantity }) { - let loot = new Bank(); + const loot = new Bank(); for (let i = 0; i < quantity; i++) { const _loot = Misc.Nightmare.kill({ team: [ @@ -380,15 +381,13 @@ for (const override of overrides) { } export async function getIgneTameKC(tame: Tame) { - const result = await prisma.$queryRaw< - { mid: number; kc: number }[] - >`SELECT (data->>'monsterID')::int AS mid, SUM((data->>'quantity')::int)::int AS kc + const result = await prisma.$queryRaw<{ mid: number; kc: number }[]>`SELECT (data->>'monsterID')::int AS mid, SUM((data->>'quantity')::int)::int AS kc FROM tame_activity WHERE tame_id = ${tame.id} AND completed = true GROUP BY data->>'monsterID';`; - let namedBank: Record = {}; - let idBank: Record = {}; + const namedBank: Record = {}; + const idBank: Record = {}; for (const { mid, kc } of result) { const mon = tameKillableMonsters.find(i => i.id === mid); if (!mon) continue; @@ -408,7 +407,7 @@ export type Nursery = { hasFuel: boolean; } | null; -export const enum TameType { +export enum TameType { Combat = 'pvm', Gatherer = 'collect', Support = 'support', @@ -445,10 +444,12 @@ export interface ArbitraryTameActivity { name: string; id: 'Tempoross' | 'Wintertodt'; run: (opts: { - handleFinish(res: { loot: Bank | null; message: string; user: MUser }): Promise; + previousTameCL: Bank; + handleFinish: typeof handleFinish; user: MUser; - tame: Tame; + tame: MTame; duration: number; + activity: TameActivity; }) => Promise; allowedTames: TameSpeciesID[]; } @@ -541,36 +542,6 @@ export const tameSpecies: Species[] = [ } ]; -export async function addDurationToTame(tame: Tame, duration: number) { - if (tame.growth_stage === tame_growth.adult) return null; - const percentToAdd = duration / Time.Minute / 20; - let newPercent = Math.max(1, Math.min(100, tame.growth_percent + percentToAdd)); - - if (newPercent >= 100) { - const newTame = await prisma.tame.update({ - where: { - id: tame.id - }, - data: { - growth_stage: tame.growth_stage === tame_growth.baby ? tame_growth.juvenile : tame_growth.adult, - growth_percent: 0 - } - }); - return `Your tame has grown into a ${newTame.growth_stage}!`; - } - - await prisma.tame.update({ - where: { - id: tame.id - }, - data: { - growth_percent: newPercent - } - }); - - return `Your tame has grown ${percentToAdd.toFixed(2)}%!`; -} - export interface Species { id: TameSpeciesID; type: TameType; diff --git a/src/lib/tickers.ts b/src/lib/tickers.ts index 2d115f1e069..f30a76f2826 100644 --- a/src/lib/tickers.ts +++ b/src/lib/tickers.ts @@ -1,20 +1,22 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder, TextChannel } from 'discord.js'; -import { noOp, randInt, removeFromArr, shuffleArr, Time } from 'e'; +import { Stopwatch } from '@oldschoolgg/toolkit'; +import type { TextChannel } from 'discord.js'; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from 'discord.js'; +import { Time, noOp, randInt, removeFromArr, shuffleArr } from 'e'; +import { TimerManager } from '@sapphire/timer-manager'; import { production } from '../config'; import { userStatsUpdate } from '../mahoji/mahojiSettings'; import { runTameTask } from '../tasks/tames/tameTasks'; -import { bossEvents, startBossEvent } from './bossEvents'; -import { BitField, Channel, informationalButtons, PeakTier } from './constants'; +import { mahojiUserSettingsUpdate } from './MUser'; +import { processPendingActivities } from './Task'; +import { BitField, Channel, PeakTier, informationalButtons } from './constants'; import { GrandExchange } from './grandExchange'; -import { cacheGEPrices } from './marketPrices'; import { collectMetrics } from './metrics'; -import { mahojiUserSettingsUpdate } from './MUser'; -import { prisma, queryCountStore } from './settings/prisma'; +import { queryCountStore } from './settings/prisma'; import { runCommand } from './settings/settings'; import { getFarmingInfo } from './skilling/functions/getFarmingInfo'; import Farming from './skilling/skills/farming'; -import { processPendingActivities } from './Task'; +import { MTame } from './structures/MTame'; import { awaitMessageComponentInteraction, getSupportGuild, makeComponents, stringMatches } from './util'; import { farmingPatchNames, getFarmingKeyFromName } from './util/farmingHelpers'; import { handleGiveawayCompletion } from './util/giveaway'; @@ -50,9 +52,16 @@ export interface Peak { /** * Tickers should idempotent, and be able to run at any time. */ -export const tickers: { name: string; interval: number; timer: NodeJS.Timeout | null; cb: () => Promise }[] = [ +export const tickers: { + name: string; + startupWait?: number; + interval: number; + timer: NodeJS.Timeout | null; + cb: () => Promise; +}[] = [ { name: 'giveaways', + startupWait: Time.Second * 30, interval: Time.Second * 10, timer: null, cb: async () => { @@ -73,14 +82,14 @@ export const tickers: { name: string; interval: number; timer: NodeJS.Timeout | timer: null, interval: Time.Minute, cb: async () => { - let storedCount = queryCountStore.value; + const storedCount = queryCountStore.value; queryCountStore.value = 0; const data = { timestamp: Math.floor(Date.now() / 1000), ...(await collectMetrics()), qps: storedCount / 60 }; - if (isNaN(data.eventLoopDelayMean)) { + if (Number.isNaN(data.eventLoopDelayMean)) { data.eventLoopDelayMean = 0; } await prisma.metric.create({ @@ -90,6 +99,7 @@ export const tickers: { name: string; interval: number; timer: NodeJS.Timeout | }, { name: 'minion_activities', + startupWait: Time.Second * 10, timer: null, interval: production ? Time.Second * 5 : 500, cb: async () => { @@ -99,6 +109,7 @@ export const tickers: { name: string; interval: number; timer: NodeJS.Timeout | { name: 'daily_reminders', interval: Time.Minute * 3, + startupWait: Time.Minute, timer: null, cb: async () => { const result = await prisma.$queryRawUnsafe<{ id: string; last_daily_timestamp: bigint }[]>( @@ -115,7 +126,7 @@ WHERE bitfield && '{2,3,4,5,6,7,8,12,21,24}'::int[] AND user_stats."last_daily_t .setEmoji('493286312854683654') .setStyle(ButtonStyle.Secondary); const components = [dailyDMButton]; - let str = 'Your daily is ready!'; + const str = 'Your daily is ready!'; for (const row of result.values()) { if (!production) continue; @@ -144,7 +155,7 @@ WHERE bitfield && '{2,3,4,5,6,7,8,12,21,24}'::int[] AND user_stats."last_daily_t // Divide the current day into interverals for (let i = 0; i <= 10; i++) { - let randomedTime = randInt(1, 2); + const randomedTime = randInt(1, 2); const [peakTier] = shuffleArr(peakTiers); const peak: Peak = { startTime: randomedTime, @@ -167,7 +178,7 @@ WHERE bitfield && '{2,3,4,5,6,7,8,12,21,24}'::int[] AND user_stats."last_daily_t let currentTime = new Date().getTime(); - for (let peak of peakInterval) { + for (const peak of peakInterval) { peak.startTime = currentTime; currentTime += peak.finishTime * Time.Hour; peak.finishTime = currentTime; @@ -178,11 +189,12 @@ WHERE bitfield && '{2,3,4,5,6,7,8,12,21,24}'::int[] AND user_stats."last_daily_t }, { name: 'farming_reminder_ticker', + startupWait: Time.Minute, interval: Time.Minute * 3.5, timer: null, cb: async () => { if (!production) return; - let basePlantTime = 1_626_556_507_451; + const basePlantTime = 1_626_556_507_451; const now = Date.now(); const users = await prisma.user.findMany({ where: { @@ -192,7 +204,6 @@ WHERE bitfield && '{2,3,4,5,6,7,8,12,21,24}'::int[] AND user_stats."last_daily_t BitField.IsPatronTier4, BitField.IsPatronTier5, BitField.IsPatronTier6, - BitField.isContributor, BitField.isModerator ] } @@ -213,11 +224,11 @@ WHERE bitfield && '{2,3,4,5,6,7,8,12,21,24}'::int[] AND user_stats."last_daily_t const storeHarvestablePlant = patch.lastPlanted; const planted = storeHarvestablePlant ? Farming.Plants.find(plants => stringMatches(plants.name, storeHarvestablePlant)) ?? - Farming.Plants.find( + Farming.Plants.find( plants => stringMatches(plants.name, storeHarvestablePlant) || stringMatches(plants.name.split(' ')[0], storeHarvestablePlant) - ) + ) : null; const difference = now - patch.plantTime; if (!planted) continue; @@ -298,6 +309,7 @@ WHERE bitfield && '{2,3,4,5,6,7,8,12,21,24}'::int[] AND user_stats."last_daily_t { name: 'support_channel_messages', timer: null, + startupWait: Time.Second * 22, interval: Time.Minute * 20, cb: async () => { if (!production) return; @@ -305,7 +317,7 @@ WHERE bitfield && '{2,3,4,5,6,7,8,12,21,24}'::int[] AND user_stats."last_daily_t const channel = guild?.channels.cache.get(Channel.HelpAndSupport) as TextChannel | undefined; if (!channel) return; const messages = await channel.messages.fetch({ limit: 5 }); - if (messages.some(m => m.author.id === globalClient.user!.id)) return; + if (messages.some(m => m.author.id === globalClient.user?.id)) return; if (lastMessageID) { const message = await channel.messages.fetch(lastMessageID).catch(noOp); if (message) { @@ -321,6 +333,7 @@ WHERE bitfield && '{2,3,4,5,6,7,8,12,21,24}'::int[] AND user_stats."last_daily_t }, { name: 'tame_activities', + startupWait: Time.Second * 15, timer: null, interval: Time.Second * 5, cb: async () => { @@ -329,13 +342,14 @@ WHERE bitfield && '{2,3,4,5,6,7,8,12,21,24}'::int[] AND user_stats."last_daily_t finish_date: production ? { lt: new Date() - } + } : undefined, completed: false }, include: { tame: true - } + }, + take: 5 }); await prisma.tameActivity.updateMany({ @@ -350,54 +364,18 @@ WHERE bitfield && '{2,3,4,5,6,7,8,12,21,24}'::int[] AND user_stats."last_daily_t }); for (const task of tameTasks) { - runTameTask(task, task.tame); - } - } - }, - - { - name: 'pumpkinhead', - timer: null, - interval: Time.Hour * 5, - cb: async () => { - const mass = await prisma.bossEvent.findFirst({ - where: { - start_date: { lt: new Date() }, - completed: false - } - }); - if (mass) { - startBossEvent({ boss: bossEvents.find(b => b.id === mass.boss_id)!, id: mass.id }); - - prisma.bossEvent - .update({ - where: { - id: mass.id - }, - data: { - completed: true - } - }) - .catch(noOp); + await runTameTask(task, new MTame(task.tame)); } } }, { name: 'ge_ticker', + startupWait: Time.Second * 30, timer: null, - interval: Time.Second * 3, + interval: Time.Second * 10, cb: async () => { await GrandExchange.tick(); } - }, - { - name: 'Cache g.e prices and validate', - timer: null, - interval: Time.Hour * 4, - cb: async () => { - await cacheGEPrices(); - await GrandExchange.extensiveVerification(); - } } ]; @@ -407,14 +385,22 @@ export function initTickers() { const fn = async () => { try { if (globalClient.isShuttingDown) return; + const stopwatch = new Stopwatch().start(); await ticker.cb(); + stopwatch.stop(); + if (stopwatch.duration > 100) { + debugLog(`Ticker ${ticker.name} took ${stopwatch}`); + } } catch (err) { logError(err); debugLog(`${ticker.name} ticker errored`, { type: 'TICKER' }); } finally { - ticker.timer = setTimeout(fn, ticker.interval); + if (ticker.timer) TimerManager.clearTimeout(ticker.timer); + ticker.timer = TimerManager.setTimeout(fn, ticker.interval); } }; - fn(); + ticker.timer = TimerManager.setTimeout(() => { + fn(); + }, ticker.startupWait ?? 1); } } diff --git a/src/lib/types/index.ts b/src/lib/types/index.ts index 443db5c2fc7..9846a534180 100644 --- a/src/lib/types/index.ts +++ b/src/lib/types/index.ts @@ -1,22 +1,10 @@ -import { BaseMessageOptions } from 'discord.js'; - -import { SkillsEnum } from '../skilling/types'; +import type { BaseMessageOptions } from 'discord.js'; +import type { SkillsEnum } from '../skilling/types'; export interface ItemBank { [key: string]: number; } -export interface Patron { - patreonID: string; - discordID?: string; - entitledTiers: string[]; - lastChargeDate: string; - lastChargeStatus: string; - lifeTimeSupportCents: number; - patronStatus: string; - pledgeRelationshipStart: string; -} - type ResolvableItem = number | string; export type ArrayItemsResolvable = (ResolvableItem | ResolvableItem[])[]; export type ArrayItemsResolved = (number | number[])[]; diff --git a/src/lib/types/minions.ts b/src/lib/types/minions.ts index d9821df1d8d..9e634329654 100644 --- a/src/lib/types/minions.ts +++ b/src/lib/types/minions.ts @@ -1,19 +1,19 @@ import type { CropUpgradeType } from '@prisma/client'; -import { BathhouseTierName } from '../baxtorianBathhouses'; -import { TuraelsTrialsMethod } from '../bso/turaelsTrials'; -import { NMZStrategy, TwitcherGloves, UnderwaterAgilityThievingTrainingSkill } from '../constants'; -import { Kibble } from '../data/kibble'; -import { IMaterialBank, MaterialType } from '../invention'; +import type { TeamMember } from 'discord.js'; +import type { ItemBank } from '.'; +import type { BathhouseTierName } from '../baxtorianBathhouses'; +import type { TuraelsTrialsMethod } from '../bso/turaelsTrials'; +import type { NMZStrategy, TwitcherGloves, UnderwaterAgilityThievingTrainingSkill } from '../constants'; +import type { Kibble } from '../data/kibble'; +import type { IMaterialBank, MaterialType } from '../invention'; import type { IPatchData } from '../minions/farming/types'; -import { Monkey } from '../monkeyRumble'; +import type { Monkey } from '../monkeyRumble'; import type { MinigameName } from '../settings/minigames'; -import { RaidLevel } from '../simulation/toa'; -import { TeamMember } from '../simulation/tob'; -import { BossUser } from '../structures/Boss'; +import type { RaidLevel } from '../simulation/toa'; +import type { BossUser } from '../structures/Boss'; import type { Peak } from '../tickers'; import type { BirdhouseData } from './../skilling/skills/hunter/defaultBirdHouseTrap'; -import type { ItemBank } from '.'; export interface ActivityTaskOptions { userID: string; @@ -69,7 +69,8 @@ export interface ActivityTaskOptionsWithNoChanges extends ActivityTaskOptions { | 'StrongholdOfSecurity' | 'TrickOrTreat' | 'HalloweenMiniMinigame' - | 'BirthdayCollectIngredients'; + | 'BirthdayCollectIngredients' + | 'CombatRing'; } export interface ActivityTaskOptionsWithQuantity extends ActivityTaskOptions { @@ -98,7 +99,7 @@ export interface ShootingStarsOptions extends ActivityTaskOptions { totalXp: number; lootItems: ItemBank; } -export interface ActivityTaskOptionsWithUsers extends ActivityTaskOptions { +interface ActivityTaskOptionsWithUsers extends ActivityTaskOptions { users: string[]; } @@ -149,23 +150,28 @@ export interface ConstructionActivityTaskOptions extends ActivityTaskOptions { export interface MonsterActivityTaskOptions extends ActivityTaskOptions { type: 'MonsterKilling'; - monsterID: number; - quantity: number; + mi: number; + q: number; iQty?: number; usingCannon?: boolean; cannonMulti?: boolean; chinning?: boolean; - burstOrBarrage?: number; + bob?: number; died?: boolean; pkEncounters?: number; hasWildySupplies?: boolean; + isInWilderness?: boolean; } +export type UndoneChangesMonsterOptions = Omit & { + quantity: number; + monsterID: number; +}; + export interface ClueActivityTaskOptions extends ActivityTaskOptions { type: 'ClueCompletion'; - - clueID: number; - quantity: number; + ci: number; + q: number; implingID?: number; implingClues?: number; } @@ -515,11 +521,22 @@ export interface TheatreOfBloodTaskOptions extends ActivityTaskOptionsWithUsers cc?: string; } +export interface ColoTaskOptions extends ActivityTaskOptions { + type: 'Colosseum'; + fakeDuration: number; + diedAt?: number; + loot?: ItemBank; + maxGlory: number; + scytheCharges: number; + venatorBowCharges: number; + bloodFuryCharges: number; +} + type UserID = string; type Points = number; type RoomIDsDiedAt = number[]; -export type TOAUser = [UserID, Points[], RoomIDsDiedAt[]]; +type TOAUser = [UserID, Points[], RoomIDsDiedAt[]]; export interface TOAOptions extends ActivityTaskOptionsWithUsers { type: 'TombsOfAmascut'; leader: string; @@ -775,4 +792,6 @@ export type ActivityTaskData = | CutLeapingFishActivityTaskOptions | MortimerOptions | MemoryHarvestOptions - | TuraelsTrialsOptions; + | TuraelsTrialsOptions + | CutLeapingFishActivityTaskOptions + | ColoTaskOptions; diff --git a/src/lib/util.ts b/src/lib/util.ts index 5f665ea2d51..189a7312226 100644 --- a/src/lib/util.ts +++ b/src/lib/util.ts @@ -1,109 +1,64 @@ -import { gzip } from 'node:zlib'; - -import { stripEmojis } from '@oldschoolgg/toolkit'; -import { PrismaClient } from '@prisma/client'; -import { Stopwatch } from '@sapphire/stopwatch'; +import { Stopwatch, stripEmojis } from '@oldschoolgg/toolkit'; +import type { CommandResponse } from '@oldschoolgg/toolkit'; import { - BaseMessageOptions, + type BaseMessageOptions, + type ButtonInteraction, + type CacheType, + type Collection, + type CollectorFilter, + type Guild, + type InteractionReplyOptions, + type Message, + type MessageEditOptions, + type SelectMenuInteraction, + type TextChannel, bold, - ButtonBuilder, - ButtonInteraction, - CacheType, - Collection, - CollectorFilter, - ComponentType, - escapeMarkdown, - Guild, - InteractionReplyOptions, - InteractionType, - Message, - MessageEditOptions, - SelectMenuInteraction, - TextChannel + escapeMarkdown } from 'discord.js'; -import { - calcWhatPercent, - chunk, - increaseNumByPercent, - notEmpty, - objectEntries, - randArrItem, - randInt, - shuffleArr, - sumArr, - Time -} from 'e'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; -import murmurHash from 'murmurhash'; +import type { ComponentType } from 'discord.js'; +import { Time, calcWhatPercent, notEmpty, objectEntries, randArrItem, randInt, shuffleArr, sumArr } from 'e'; import { Bank, Items, Monsters } from 'oldschooljs'; -import { Item, ItemBank } from 'oldschooljs/dist/meta/types'; -import Monster from 'oldschooljs/dist/structures/Monster'; -import { convertLVLtoXP } from 'oldschooljs/dist/util/util'; -import { bool, integer, nodeCrypto, real } from 'random-js'; +import { bool, integer, nativeMath, nodeCrypto, real } from 'random-js'; -import { ADMIN_IDS, OWNER_IDS, production, SupportServer } from '../config'; +import type { Prisma, PrismaClient } from '@prisma/client'; +import { LRUCache } from 'lru-cache'; +import type { Item } from 'oldschooljs/dist/meta/types'; +import type Monster from 'oldschooljs/dist/structures/Monster'; +import { convertLVLtoXP } from 'oldschooljs/dist/util/util'; +import { ADMIN_IDS, OWNER_IDS, SupportServer } from '../config'; +import type { MUserClass } from './MUser'; +import { PaginatedMessage } from './PaginatedMessage'; import { ClueTiers } from './clues/clueTiers'; -import { - badgesCache, - BitField, - globalConfig, - ONE_TRILLION, - projectiles, - ProjectileType, - usernameCache -} from './constants'; -import { UserStatsDataNeededForCL } from './data/Collections'; +import { BitField, ONE_TRILLION, type ProjectileType, globalConfig, projectiles } from './constants'; import { doaCL } from './data/CollectionsExport'; import { getSimilarItems } from './data/similarItems'; -import { DefenceGearStat, GearSetupType, GearSetupTypes, GearStat, OffenceGearStat } from './gear/types'; +import type { DefenceGearStat, GearSetupType, OffenceGearStat } from './gear/types'; +import { GearSetupTypes, GearStat } from './gear/types'; import type { Consumable } from './minions/types'; -import { MUserClass } from './MUser'; -import { PaginatedMessage } from './PaginatedMessage'; import type { POHBoosts } from './poh'; import { SkillsEnum } from './skilling/types'; -import { Gear } from './structures/Gear'; -import { MUserStats } from './structures/MUserStats'; -import type { Skills } from './types'; +import type { Gear } from './structures/Gear'; +import type { ItemBank, Skills } from './types'; import type { GroupMonsterActivityTaskOptions, NexTaskOptions, RaidsOptions, + TOAOptions, TheatreOfBloodTaskOptions } from './types/minions'; import getOSItem, { getItem } from './util/getOSItem'; import itemID from './util/itemID'; +import { makeBadgeString } from './util/makeBadgeString'; import resolveItems from './util/resolveItems'; import { itemNameFromID } from './util/smallUtils'; -export { cleanString, stringMatches, stripEmojis } from '@oldschoolgg/toolkit'; +export * from '@oldschoolgg/toolkit'; export * from 'oldschooljs/dist/util/index'; -const zeroWidthSpace = '\u200b'; // @ts-ignore ignore -// eslint-disable-next-line no-extend-native, func-names BigInt.prototype.toJSON = function () { return this.toString(); }; -export function cleanMentions(guild: Guild | null, input: string, showAt = true) { - const at = showAt ? '@' : ''; - return input - .replace(/@(here|everyone)/g, `@${zeroWidthSpace}$1`) - .replace(/<(@[!&]?|#)(\d{17,19})>/g, (match, type, id) => { - switch (type) { - case '@': - case '@!': { - const tag = guild?.client.users.cache.get(id); - return tag ? `${at}${tag.username}` : `<${type}${zeroWidthSpace}${id}>`; - } - case '@&': { - const role = guild?.roles.cache.get(id); - return role ? `${at}${role.name}` : match; - } - default: - return `<${type}${zeroWidthSpace}${id}>`; - } - }); -} export function inlineCodeblock(input: string) { return `\`${input.replace(/ /g, '\u00A0').replace(/`/g, '`\u200B')}\``; @@ -114,14 +69,6 @@ export function britishTime() { return currentDate; } -export function isNightTime() { - const time = britishTime(); - let hours = time.getHours(); - - if (!production) hours = 20; - return hours > 16 || hours < 5; -} - export function isWeekend() { const currentDate = new Date(Date.now() - Time.Hour * 6); return [6, 0].includes(currentDate.getDay()); @@ -141,16 +88,18 @@ export function convertXPtoLVL(xp: number, cap = 120) { return cap; } +const randEngine = process.env.TEST ? nativeMath : nodeCrypto; + export function cryptoRand(min: number, max: number) { - return integer(min, max)(nodeCrypto); + return integer(min, max)(randEngine); } export function randFloat(min: number, max: number) { - return real(min, max)(nodeCrypto); + return real(min, max)(randEngine); } export function percentChance(percent: number) { - return bool(percent / 100)(nodeCrypto); + return bool(percent / 100)(randEngine); } export function roll(max: number) { @@ -264,19 +213,6 @@ export function formatItemCosts(consumable: Consumable, timeToFinish: number) { return str.join(''); } -export const calculateTripConsumableCost = (c: Consumable, quantity: number, duration: number) => { - const consumableCost = c.itemCost.clone(); - if (c.qtyPerKill) { - consumableCost.multiply(quantity); - } else if (c.qtyPerMinute) { - consumableCost.multiply(duration / Time.Minute); - } - for (const [item, qty] of Object.entries(consumableCost.bank)) { - consumableCost.bank[item] = Math.ceil(qty); - } - return consumableCost; -}; - export function formatPohBoosts(boosts: POHBoosts) { const bonusStr = []; const slotStr = []; @@ -293,16 +229,6 @@ export function formatPohBoosts(boosts: POHBoosts) { return slotStr.join(', '); } -function gaussianRand(rolls: number = 3) { - let rand = 0; - for (let i = 0; i < rolls; i += 1) { - rand += Math.random(); - } - return rand / rolls; -} -export function gaussianRandom(min: number, max: number, rolls?: number) { - return Math.floor(min + gaussianRand(rolls) * (max - min + 1)); -} export function isValidNickname(str?: string) { return Boolean( str && @@ -314,7 +240,7 @@ export function isValidNickname(str?: string) { ); } -export type PaginatedMessagePage = MessageEditOptions; +export type PaginatedMessagePage = MessageEditOptions | (() => Promise); export async function makePaginatedMessage(channel: TextChannel, pages: PaginatedMessagePage[], target?: string) { const m = new PaginatedMessage({ pages, channel }); @@ -371,7 +297,7 @@ export function getMonster(str: string): Monster { } export function calcDropRatesFromBank(bank: Bank, iterations: number, uniques: number[]) { - let result = []; + const result = []; let uniquesReceived = 0; for (const [item, qty] of bank.items().sort((a, b) => a[1] - b[1])) { if (uniques.includes(item.id)) { @@ -396,17 +322,6 @@ export function convertPercentChance(percent: number) { return (1 / (percent / 100)).toFixed(1); } -export function murMurHashChance(input: string, percent: number) { - const hash = murmurHash.v3(input) % 1e4; - return hash < percent * 100; -} - -const getMurKey = (input: string | number, sortHash: string) => `${input.toString()}-${sortHash}`; - -export function murMurSort(arr: T[], sortHash: string) { - return [...arr].sort((a, b) => murmurHash.v3(getMurKey(b, sortHash)) - murmurHash.v3(getMurKey(a, sortHash))); -} - export function convertAttackStyleToGearSetup(style: OffenceGearStat | DefenceGearStat) { let setup: GearSetupType = 'melee'; @@ -426,22 +341,10 @@ export function convertAttackStyleToGearSetup(style: OffenceGearStat | DefenceGe return setup; } -export function formatTimestamp(date: Date, relative = false) { - const unixTime = date.getTime() / 1000; - if (relative) { - return ``; - } - return ``; -} - export function ISODateString(date?: Date) { return (date ?? new Date()).toISOString().slice(0, 10); } -export function averageArr(arr: number[]) { - return sumArr(arr) / arr.length; -} - export function convertPvmStylesToGearSetup(attackStyles: SkillsEnum[]) { const usedSetups: GearSetupType[] = []; if (attackStyles.includes(SkillsEnum.Ranged)) usedSetups.push('range'); @@ -492,19 +395,6 @@ export function validateBankAndThrow(bank: Bank) { } } -export function convertBankToPerHourStats(bank: Bank, time: number) { - let result = []; - for (const [item, qty] of bank.items()) { - result.push(`${(qty / (time / Time.Hour)).toFixed(1)}/hr ${item.name}`); - } - return result; -} - -export function isAtleastThisOld(date: Date | number, age: number) { - const difference = Date.now() - (typeof date === 'number' ? date : date.getTime()); - return difference >= age; -} - export function removeMarkdownEmojis(str: string) { return escapeMarkdown(stripEmojis(str)); } @@ -512,6 +402,7 @@ export function removeMarkdownEmojis(str: string) { export function moidLink(items: number[]) { return `https://chisel.weirdgloop.org/moid/item_id.html#${items.join(',')}`; } + export async function bankValueWithMarketPrices(prisma: PrismaClient, bank: Bank) { const marketPrices = (await prisma.clientStorage.findFirst({ where: { id: globalConfig.clientID }, @@ -549,46 +440,30 @@ export function roughMergeMahojiResponse( ): InteractionReplyOptions { const first = normalizeMahojiResponse(one); const second = normalizeMahojiResponse(two); + const newContent: string[] = []; + const newResponse: InteractionReplyOptions = { content: '', files: [], components: [] }; for (const res of [first, second]) { - if (res.content) newResponse.content += `${res.content} `; + if (res.content) newContent.push(res.content); if (res.files) newResponse.files = [...newResponse.files!, ...res.files]; if (res.components) newResponse.components = res.components; } + newResponse.content = newContent.join('\n\n'); return newResponse; } -export async function asyncGzip(buffer: Buffer) { - return new Promise((resolve, reject) => { - gzip(buffer, {}, (error, gzipped) => { - if (error) { - reject(error); - } - resolve(gzipped); - }); - }); -} - -export function increaseBankQuantitesByPercent(bank: Bank, percent: number, whitelist: number[] | null = null) { - for (const [key, value] of Object.entries(bank.bank)) { - if (whitelist !== null && !whitelist.includes(parseInt(key))) continue; - const increased = Math.floor(increaseNumByPercent(value, percent)); - bank.bank[key] = increased; - } -} - export function generateXPLevelQuestion() { const level = randInt(1, 120); const xp = randInt(convertLVLtoXP(level), convertLVLtoXP(level + 1) - 1); - let chanceOfSwitching = randInt(1, 4); + const chanceOfSwitching = randInt(1, 4); - let answers: string[] = [level.toString()]; - let arr = shuffleArr(['plus', 'minus'] as const); + const answers: string[] = [level.toString()]; + const arr = shuffleArr(['plus', 'minus'] as const); while (answers.length < 4) { - let modifier = randArrItem([1, 1, 2, 2, 3, 4, 5, 5, 6, 7, 7, 8, 9, 10, 10]); - let action = roll(chanceOfSwitching) ? arr[0] : arr[1]; + const modifier = randArrItem([1, 1, 2, 2, 3, 4, 5, 5, 6, 7, 7, 8, 9, 10, 10]); + const action = roll(chanceOfSwitching) ? arr[0] : arr[1]; let potentialAnswer = action === 'plus' ? level + modifier : level - modifier; if (potentialAnswer < 1) potentialAnswer = level + modifier; else if (potentialAnswer > 120) potentialAnswer = level - modifier; @@ -617,17 +492,31 @@ export function skillingPetDropRate( return { petDropRate: dropRate }; } -export function getBadges(user: MUser | string | bigint) { - if (typeof user === 'string' || typeof user === 'bigint') { - return badgesCache.get(user.toString()) ?? ''; - } - return user.badgeString; +const usernameWithBadgesCache = new LRUCache({ max: 2000 }); + +export async function getUsername(_id: string | bigint): Promise { + const id = _id.toString(); + const cached = usernameWithBadgesCache.get(id); + if (cached) return cached; + const user = await prisma.user.findFirst({ + where: { + id + }, + select: { + username: true, + badges: true, + minion_ironman: true + } + }); + if (!user?.username) return 'Unknown'; + const badges = makeBadgeString(user.badges, user.minion_ironman); + const newValue = `${badges ? `${badges} ` : ''}${user.username}`; + usernameWithBadgesCache.set(id, newValue); + return newValue; } -export function getUsername(id: string | bigint, withBadges: boolean = true) { - let username = usernameCache.get(id.toString()) ?? 'Unknown'; - if (withBadges) username = `${getBadges(id)} ${username}`; - return username; +export function getUsernameSync(_id: string | bigint) { + return usernameWithBadgesCache.get(_id.toString()) ?? 'Unknown'; } export function clAdjustedDroprate( @@ -646,16 +535,6 @@ export function clAdjustedDroprate( return Math.floor(newRate); } -export function makeComponents(components: ButtonBuilder[]): InteractionReplyOptions['components'] { - return chunk(components, 5).map(i => ({ components: i, type: ComponentType.ActionRow })); -} - -type test = CollectorFilter< - [ - ButtonInteraction | SelectMenuInteraction, - Collection | SelectMenuInteraction> - ] ->; export function awaitMessageComponentInteraction({ message, filter, @@ -663,7 +542,12 @@ export function awaitMessageComponentInteraction({ }: { time: number; message: Message; - filter: test; + filter: CollectorFilter< + [ + ButtonInteraction | SelectMenuInteraction, + Collection | SelectMenuInteraction> + ] + >; }): Promise | ButtonInteraction> { return new Promise((resolve, reject) => { const collector = message.createMessageComponentCollector({ max: 1, filter, time }); @@ -675,32 +559,23 @@ export function awaitMessageComponentInteraction({ }); } -export async function runTimedLoggedFn(name: string, fn: () => Promise) { - debugLog(`Starting ${name}...`); +export async function runTimedLoggedFn(name: string, fn: () => Promise, threshholdToLog = 100): Promise { + const logger = globalConfig.isProduction ? debugLog : console.log; const stopwatch = new Stopwatch(); stopwatch.start(); - await fn(); + const result = await fn(); stopwatch.stop(); - debugLog(`Finished ${name} in ${stopwatch.toString()}`); -} - -export function getAllIDsOfUser(user: MUser) { - let main = user.user.main_account; - const allAccounts: string[] = [...user.user.ironman_alts, user.id]; - if (main) { - allAccounts.push(main); + if (!globalConfig.isProduction || stopwatch.duration > threshholdToLog) { + logger(`Took ${stopwatch} to do ${name}`); } - return allAccounts; + return result; } -export function getInteractionTypeName(type: InteractionType) { - return { - [InteractionType.Ping]: 'Ping', - [InteractionType.ApplicationCommand]: 'ApplicationCommand', - [InteractionType.MessageComponent]: 'MessageComponent', - [InteractionType.ApplicationCommandAutocomplete]: 'ApplicationCommandAutocomplete', - [InteractionType.ModalSubmit]: 'ModalSubmit' - }[type]; +export function logWrapFn Promise>( + name: string, + fn: T +): (...args: Parameters) => ReturnType { + return (...args: Parameters): ReturnType => runTimedLoggedFn(name, () => fn(...args)) as ReturnType; } export function isModOrAdmin(user: MUser) { @@ -727,21 +602,6 @@ export async function calcClueScores(user: MUser) { .filter(notEmpty); } -export async function fetchStatsForCL(user: MUser): Promise { - const stats = await MUserStats.fromID(user.id); - const { userStats } = stats; - return { - sacrificedBank: new Bank(userStats.sacrificed_bank as ItemBank), - titheFarmsCompleted: userStats.tithe_farms_completed, - lapsScores: userStats.laps_scores as ItemBank, - openableScores: new Bank(userStats.openable_scores as ItemBank), - kcBank: userStats.monster_scores as ItemBank, - highGambles: userStats.high_gambles, - gotrRiftSearches: userStats.gotr_rift_searches, - stats - }; -} - export { assert } from './util/logError'; export * from './util/smallUtils'; export { channelIsSendable } from '@oldschoolgg/toolkit'; @@ -753,10 +613,7 @@ export function checkRangeGearWeapon(gear: Gear) { if (!ammo) return 'You have no ammo equipped.'; const projectileCategory = objectEntries(projectiles).find(i => - i[1].weapons - .map(w => getSimilarItems(w)) - .flat() - .includes(weapon.id) + i[1].weapons.flatMap(w => getSimilarItems(w)).includes(weapon.id) ); if (!projectileCategory) return 'You have an invalid range weapon.'; if (!projectileCategory[1].items.includes(ammo.item)) { @@ -774,3 +631,25 @@ export function checkRangeGearWeapon(gear: Gear) { export function hasUnlockedAtlantis(user: MUser) { return doaCL.some(itemID => user.cl.has(itemID)); } + +export function normalizeTOAUsers(data: TOAOptions) { + const _detailedUsers = data.detailedUsers; + const detailedUsers = ( + (Array.isArray(_detailedUsers[0]) ? _detailedUsers : [_detailedUsers]) as [string, number, number[]][][] + ).map(userArr => + userArr.map(user => ({ + id: user[0], + points: user[1], + deaths: user[2] + })) + ); + return detailedUsers; +} + +export function anyoneDiedInTOARaid(data: TOAOptions) { + return normalizeTOAUsers(data).some(userArr => userArr.some(user => user.deaths.length > 0)); +} + +export type JsonKeys = { + [K in keyof T]: T[K] extends Prisma.JsonValue ? K : never; +}[keyof T]; diff --git a/src/lib/util/activityInArea.ts b/src/lib/util/activityInArea.ts index a22948ce69b..a953d048e03 100644 --- a/src/lib/util/activityInArea.ts +++ b/src/lib/util/activityInArea.ts @@ -1,7 +1,8 @@ import { Monsters } from 'oldschooljs'; +import { resolveItems } from 'oldschooljs/dist/util/util'; import { soteSkillRequirements } from '../skilling/functions/questRequirements'; -import { +import type { ActivityTaskData, AgilityActivityTaskOptions, HunterActivityTaskOptions, @@ -9,11 +10,10 @@ import { PickpocketActivityTaskOptions, WoodcuttingActivityTaskOptions } from '../types/minions'; -import resolveItems from './resolveItems'; -export const enum WorldLocations { - Priffdinas, - World +export enum WorldLocations { + Priffdinas = 0, + World = 1 } const WorldLocationsChecker = [ @@ -25,7 +25,7 @@ const WorldLocationsChecker = [ if ( activity.type === 'MonsterKilling' && [Monsters.DarkBeast.id, Monsters.PrifddinasElf.id].includes( - (activity as MonsterActivityTaskOptions).monsterID + (activity as MonsterActivityTaskOptions).mi ) ) { return true; diff --git a/src/lib/util/addSubTaskToActivityTask.ts b/src/lib/util/addSubTaskToActivityTask.ts index 90582eecefc..8e0fe2f06ca 100644 --- a/src/lib/util/addSubTaskToActivityTask.ts +++ b/src/lib/util/addSubTaskToActivityTask.ts @@ -1,8 +1,7 @@ -import { UserError } from '@oldschoolgg/toolkit/dist/lib/UserError'; +import { UserError } from '@oldschoolgg/toolkit'; -import { prisma } from '../settings/prisma'; import { activitySync } from '../settings/settings'; -import { ActivityTaskData, ActivityTaskOptions } from '../types/minions'; +import type { ActivityTaskData, ActivityTaskOptions } from '../types/minions'; import { isGroupActivity } from '../util'; import { logError } from './logError'; import { getActivityOfUser } from './minionIsBusy'; @@ -17,7 +16,7 @@ export default async function addSubTaskToActivityTask = { ...taskToAdd } as Partial; - delete __newData.type; - delete __newData.userID; - delete __newData.id; - delete __newData.channelID; - delete __newData.duration; + const __newData: Partial = { ...taskToAdd } as Partial; + __newData.type = undefined; + __newData.userID = undefined; + __newData.id = undefined; + __newData.channelID = undefined; + __newData.duration = undefined; - let newData: Omit = { + const newData: Omit = { ...__newData }; + const data = { + user_id: BigInt(taskToAdd.userID), + start_date: new Date(), + finish_date: finishDate, + completed: false, + type: taskToAdd.type, + data: newData, + group_activity: isGroupActivity(taskToAdd), + channel_id: BigInt(taskToAdd.channelID), + duration + } as const; try { const createdActivity = await prisma.activity.create({ - data: { - user_id: BigInt(taskToAdd.userID), - start_date: new Date(), - finish_date: finishDate, - completed: false, - type: taskToAdd.type, - data: newData, - group_activity: isGroupActivity(taskToAdd), - channel_id: BigInt(taskToAdd.channelID), - duration - } + data }); activitySync(createdActivity); return createdActivity; } catch (err: any) { - logError(err); + logError(err, { + user_id: taskToAdd.userID, + data: JSON.stringify(data) + }); throw new UserError('There was an error starting your activity.'); } } diff --git a/src/lib/util/ashSanctifier.ts b/src/lib/util/ashSanctifier.ts index 5042fb06e14..3fdc04ce3b1 100644 --- a/src/lib/util/ashSanctifier.ts +++ b/src/lib/util/ashSanctifier.ts @@ -1,4 +1,4 @@ -import { Bank } from 'oldschooljs'; +import type { Bank } from 'oldschooljs'; import { SkillsEnum } from 'oldschooljs/dist/constants'; import { userStatsUpdate } from '../../mahoji/mahojiSettings'; @@ -15,7 +15,7 @@ export async function ashSanctifierEffect(user: MUser, loot: Bank, duration: num const [hasEliteDiary] = await userhasDiaryTier(user, KourendKebosDiary.elite); const ashXpModifider = hasEliteDiary ? 1 : 0.5; - let startingAshSanctifierCharges = await checkDegradeableItemCharges({ + const startingAshSanctifierCharges = await checkDegradeableItemCharges({ item: getOSItem('Ash sanctifier'), user }); diff --git a/src/lib/util/cachedUserIDs.ts b/src/lib/util/cachedUserIDs.ts index 20e025620aa..4158efe1d50 100644 --- a/src/lib/util/cachedUserIDs.ts +++ b/src/lib/util/cachedUserIDs.ts @@ -1,43 +1,41 @@ -import { Stopwatch } from '@sapphire/stopwatch'; +import { Prisma } from '@prisma/client'; import { ChannelType } from 'discord.js'; import { objectEntries } from 'e'; import { OWNER_IDS, SupportServer } from '../../config'; import { globalConfig } from '../constants'; -import { prisma } from '../settings/prisma'; import { runTimedLoggedFn } from '../util'; export const CACHED_ACTIVE_USER_IDS = new Set(); CACHED_ACTIVE_USER_IDS.add(globalConfig.clientID); for (const id of OWNER_IDS) CACHED_ACTIVE_USER_IDS.add(id); -export async function syncActiveUserIDs() { - const [users, otherUsers] = await Promise.all([ - prisma.$queryRaw<{ user_id: string }[]>`SELECT DISTINCT(user_id::text) -FROM command_usage -WHERE date > now() - INTERVAL '72 hours';`, - prisma.$queryRaw<{ id: string }[]>`SELECT id -FROM users -WHERE main_account IS NOT NULL - OR CARDINALITY(ironman_alts) > 0 - OR bitfield && ARRAY[2,3,4,5,6,7,8,12,11,21,19];` - ]); +export const syncActiveUserIDs = async () => { + const users = await prisma.$queryRawUnsafe< + { user_id: string }[] + >(`SELECT DISTINCT(${Prisma.ActivityScalarFieldEnum.user_id}::text) +FROM activity +WHERE finish_date > now() - INTERVAL '48 hours'`); - for (const id of [...users.map(i => i.user_id), ...otherUsers.map(i => i.id)]) { + const perkTierUsers = await roboChimpClient.$queryRawUnsafe<{ id: string }[]>(`SELECT id::text +FROM "user" +WHERE perk_tier > 0;`); + + for (const id of [...users.map(i => i.user_id), ...perkTierUsers.map(i => i.id)]) { CACHED_ACTIVE_USER_IDS.add(id); } debugLog(`${CACHED_ACTIVE_USER_IDS.size} cached active user IDs`); -} +}; export function memoryAnalysis() { - let guilds = globalClient.guilds.cache.size; + const guilds = globalClient.guilds.cache.size; let emojis = 0; - let channels = globalClient.channels.cache.size; - let voiceChannels = 0; - let guildTextChannels = 0; + const channels = globalClient.channels.cache.size; + const voiceChannels = 0; + const guildTextChannels = 0; let roles = 0; let members = 0; - let channelCounter: Record = {} as any; + const channelCounter: Record = {} as any; let messages = 0; let voiceStates = 0; let commands = 0; @@ -98,11 +96,6 @@ export const emojiServers = new Set([ export function cacheCleanup() { if (!globalClient.isReady()) return; - let stopwatch = new Stopwatch(); - stopwatch.start(); - debugLog('Cache Cleanup Start', { - type: 'CACHE_CLEANUP' - }); return runTimedLoggedFn('Cache Cleanup', async () => { await runTimedLoggedFn('Clear Channels', async () => { for (const channel of globalClient.channels.cache.values()) { @@ -110,15 +103,15 @@ export function cacheCleanup() { globalClient.channels.cache.delete(channel.id); } // @ts-ignore ignore - delete channel.topic; + channel.topic = undefined; // @ts-ignore ignore - delete channel.rateLimitPerUser; + channel.rateLimitPerUser = undefined; // @ts-ignore ignore - delete channel.nsfw; + channel.nsfw = undefined; // @ts-ignore ignore - delete channel.parentId; + channel.parentId = undefined; // @ts-ignore ignore - delete channel.name; + channel.name = undefined; // @ts-ignore ignore channel.lastMessageId = null; // @ts-ignore ignore @@ -145,34 +138,29 @@ export function cacheCleanup() { guild.emojis?.cache.clear(); for (const channel of guild.channels.cache.values()) { - if (channel.type === ChannelType.GuildVoice || channel.type === ChannelType.GuildNewsThread) { + if (channel.type === ChannelType.GuildVoice || channel.type === ChannelType.AnnouncementThread) { guild.channels.cache.delete(channel.id); } } for (const role of guild.roles.cache.values()) { // @ts-ignore ignore - delete role.managed; + role.managed = undefined; // @ts-ignore ignore - delete role.name; + role.name = undefined; // @ts-ignore ignore - delete role.tags; + role.tags = undefined; // @ts-ignore ignore - delete role.icon; + role.icon = undefined; // @ts-ignore ignore - delete role.unicodeEmoji; + role.unicodeEmoji = undefined; // @ts-ignore ignore - delete role.rawPosition; + role.rawPosition = undefined; // @ts-ignore ignore - delete role.color; + role.color = undefined; // @ts-ignore ignore - delete role.hoist; + role.hoist = undefined; } } }); - - stopwatch.stop(); - debugLog(`Cache Cleanup Finish After ${stopwatch.toString()}`, { - type: 'CACHE_CLEANUP' - }); }); } diff --git a/src/lib/util/calcConBonusXP.ts b/src/lib/util/calcConBonusXP.ts index a6cb9bc0309..413c28c4776 100644 --- a/src/lib/util/calcConBonusXP.ts +++ b/src/lib/util/calcConBonusXP.ts @@ -1,6 +1,6 @@ import { round } from 'e'; -import { GearSetup } from '../gear/types'; +import type { GearSetup } from '../gear/types'; import itemID from './itemID'; export function calcConBonusXP(setup: GearSetup): number { diff --git a/src/lib/util/calcDropRatesFromBank.ts b/src/lib/util/calcDropRatesFromBank.ts index a437635d71f..12f927bb294 100644 --- a/src/lib/util/calcDropRatesFromBank.ts +++ b/src/lib/util/calcDropRatesFromBank.ts @@ -1,9 +1,9 @@ import { bold } from 'discord.js'; import { calcWhatPercent } from 'e'; -import { Bank } from 'oldschooljs'; +import type { Bank } from 'oldschooljs'; export function calcDropRatesFromBank(bank: Bank, iterations: number, uniques: number[]) { - let result = []; + const result = []; let uniquesReceived = 0; for (const [item, qty] of bank.items().sort((a, b) => a[1] - b[1])) { if (uniques.includes(item.id)) { @@ -25,7 +25,7 @@ export function calcDropRatesFromBank(bank: Bank, iterations: number, uniques: n } export function calcDropRatesFromBankWithoutUniques(bank: Bank, iterations: number) { - let results = []; + const results = []; for (const [item, qty] of bank.items().sort((a, b) => a[1] - b[1])) { const rate = Math.round(iterations / qty); if (rate < 2) continue; diff --git a/src/lib/util/calcMassDurationQuantity.ts b/src/lib/util/calcMassDurationQuantity.ts index 7c0943e4a48..47627131829 100644 --- a/src/lib/util/calcMassDurationQuantity.ts +++ b/src/lib/util/calcMassDurationQuantity.ts @@ -1,5 +1,5 @@ import { reducedTimeForGroup } from '../minions/functions'; -import { KillableMonster } from '../minions/types'; +import type { KillableMonster } from '../minions/types'; import { calcMaxTripLength } from './calcMaxTripLength'; export default async function calcDurQty( diff --git a/src/lib/util/calcMaxTripLength.ts b/src/lib/util/calcMaxTripLength.ts index 97bce591392..9dc67ec3dfb 100644 --- a/src/lib/util/calcMaxTripLength.ts +++ b/src/lib/util/calcMaxTripLength.ts @@ -1,5 +1,5 @@ -import { activity_type_enum } from '@prisma/client'; -import { calcPercentOfNum, calcWhatPercent, Time } from 'e'; +import type { activity_type_enum } from '@prisma/client'; +import { Time, calcPercentOfNum, calcWhatPercent } from 'e'; import { BitField, PerkTier } from '../constants'; import { SkillsEnum } from '../skilling/types'; diff --git a/src/lib/util/calcWildyPkChance.ts b/src/lib/util/calcWildyPkChance.ts index ef35b14d628..ba1315399ab 100644 --- a/src/lib/util/calcWildyPkChance.ts +++ b/src/lib/util/calcWildyPkChance.ts @@ -1,12 +1,12 @@ -import { calcPercentOfNum, calcWhatPercent, reduceNumByPercent, Time } from 'e'; +import { Time, calcPercentOfNum, calcWhatPercent, reduceNumByPercent } from 'e'; import { randomVariation } from 'oldschooljs/dist/util'; import { userStatsUpdate } from '../../mahoji/mahojiSettings'; import { PeakTier } from '../constants'; -import { KillableMonster } from '../minions/types'; +import type { KillableMonster } from '../minions/types'; import { SkillsEnum } from '../skilling/types'; import { maxDefenceStats } from '../structures/Gear'; -import { Peak } from '../tickers'; +import type { Peak } from '../tickers'; import { percentChance } from '../util'; const peakFactor = [ @@ -25,13 +25,13 @@ const peakFactor = [ ]; // Returns a value 0 - 100 representing the % of 10 hours spent in pk-able wilderness. -export async function getPkEvasionExp(user: MUser) { +async function getPkEvasionExp(user: MUser) { const maxBoostDuration = Time.Hour * 10; const stats: { pk_evasion_exp: number } = await user.fetchStats({ pk_evasion_exp: true }); return Math.min(100, (stats.pk_evasion_exp / maxBoostDuration) * 100); } -export async function getWildEvasionPercent(user: MUser) { +async function getWildEvasionPercent(user: MUser) { const maxReductionPercent = 75; return randomVariation(calcPercentOfNum(await getPkEvasionExp(user), maxReductionPercent), 10); } @@ -46,11 +46,12 @@ export async function calcWildyPKChance( peak: Peak, monster: KillableMonster, duration: number, - supplies: boolean + supplies: boolean, + cannonMulti: boolean ): Promise<[number, boolean, string]> { // Chance per minute, Difficulty from 1 to 10, and factor a million difference, High peak 5x as likley encounter, Medium peak 1x, Low peak 5x as unlikley const peakInfluence = peakFactor.find(_peaktier => _peaktier.peakTier === peak?.peakTier)?.factor ?? 1; - let pkChance = (1 / (7_000_000 / (Math.pow(monster.pkActivityRating ?? 1, 6) * peakInfluence))) * 100; + const pkChance = (1 / (7_000_000 / (Math.pow(monster.pkActivityRating ?? 1, 6) * peakInfluence))) * 100; let chanceString = `**PK Chance:** ${pkChance.toFixed(2)}% per min (${peak.peakTier} peak time)`; const evasionReduction = await getWildEvasionPercent(user); @@ -72,27 +73,25 @@ export async function calcWildyPKChance( deathChance += deathChanceFromLevels; // Multi does make it riskier, but only if there's actually a team on you - const wildyMultiMultiplier = monster.wildyMulti === true ? 2 : 1; + const wildyMultiMultiplier = monster.wildyMulti || cannonMulti ? 2 : 1; const hasSupplies = supplies ? 0.5 : 1; const hasOverheads = user.skillLevel(SkillsEnum.Prayer) >= 43 ? 0.25 : 1; // Weighted Melee gear percent protection 60% slash, 30% crush, 10% stab const defensiveMeleeGearPercent = - (Math.max(0, calcWhatPercent(user.gear.wildy.getStats().defence_slash, maxDefenceStats['defence_slash'])) * 60 + - Math.max(0, calcWhatPercent(user.gear.wildy.getStats().defence_crush, maxDefenceStats['defence_crush'])) * - 30 + - Math.max(0, calcWhatPercent(user.gear.wildy.getStats().defence_stab, maxDefenceStats['defence_stab'])) * - 10) / + (Math.max(0, calcWhatPercent(user.gear.wildy.getStats().defence_slash, maxDefenceStats.defence_slash)) * 60 + + Math.max(0, calcWhatPercent(user.gear.wildy.getStats().defence_crush, maxDefenceStats.defence_crush)) * 30 + + Math.max(0, calcWhatPercent(user.gear.wildy.getStats().defence_stab, maxDefenceStats.defence_stab)) * 10) / 100; const defensiveRangeGearPercent = Math.max( 0, - calcWhatPercent(user.gear.wildy.getStats().defence_ranged, maxDefenceStats['defence_ranged']) + calcWhatPercent(user.gear.wildy.getStats().defence_ranged, maxDefenceStats.defence_ranged) ); const defensiveMageGearPercent = Math.max( 0, - calcWhatPercent(user.gear.wildy.getStats().defence_magic, maxDefenceStats['defence_magic']) + calcWhatPercent(user.gear.wildy.getStats().defence_magic, maxDefenceStats.defence_magic) ); // Weighted attack style percent, 60% magic, 40% ranged, 20% melee diff --git a/src/lib/util/calculateGearLostOnDeathWilderness.ts b/src/lib/util/calculateGearLostOnDeathWilderness.ts index e925325f3d5..1c975c36458 100644 --- a/src/lib/util/calculateGearLostOnDeathWilderness.ts +++ b/src/lib/util/calculateGearLostOnDeathWilderness.ts @@ -1,8 +1,8 @@ import { deepClone, objectEntries } from 'e'; import { Bank } from 'oldschooljs'; -import { EquipmentSlot, Item } from 'oldschooljs/dist/meta/types'; +import type { EquipmentSlot, Item } from 'oldschooljs/dist/meta/types'; -import { GearSetup } from '../gear/types'; +import type { GearSetup } from '../gear/types'; import skillcapes from '../skilling/skillcapes'; import getOSItem from './getOSItem'; import itemID from './itemID'; @@ -11,13 +11,13 @@ import resolveItems from './resolveItems'; interface IGearSwap { [key: number]: number[]; } -export const gearSwap: IGearSwap = { +const gearSwap: IGearSwap = { [itemID("Craw's bow")]: [itemID("Craw's bow (u)")], [itemID("Thammaron's sceptre")]: [itemID("Thammaron's sceptre (u)")], [itemID("Viggora's chainmace")]: [itemID("Viggora's chainmace (u)")], - 23_330: [itemID('Rune scimitar ornament kit (guthix)'), itemID('Rune scimitar')], - 23_332: [itemID('Rune scimitar ornament kit (saradomin)'), itemID('Rune scimitar')], - 23_334: [itemID('Rune scimitar ornament kit (zamorak)'), itemID('Rune scimitar')], + 23330: [itemID('Rune scimitar ornament kit (guthix)'), itemID('Rune scimitar')], + 23332: [itemID('Rune scimitar ornament kit (saradomin)'), itemID('Rune scimitar')], + 23334: [itemID('Rune scimitar ornament kit (zamorak)'), itemID('Rune scimitar')], [itemID('Rune defender (t)')]: [itemID('Rune defender'), itemID('Rune defender ornament kit')], [itemID('Dragon full helm (g)')]: [itemID('Dragon full helm'), itemID('Dragon full helm ornament kit')], [itemID('Dragon chainbody (g)')]: [itemID('Dragon chainbody'), itemID('Dragon chainbody ornament kit')], @@ -56,38 +56,38 @@ export const gearSwap: IGearSwap = { [itemID('Holy sanguinesti staff')]: [itemID('Holy ornament kit'), itemID('Sanguinesti staff')], [itemID('Holy scythe of vitur')]: [itemID('Holy ornament kit'), itemID('Scythe of vitur')], [itemID('Sanguine scythe of vitur')]: [itemID('Sanguine ornament kit'), itemID('Scythe of vitur')], - 24_743: [itemID('Graceful hood'), itemID('Dark dye')], - 24_749: [itemID('Graceful top'), itemID('Dark dye')], - 24_752: [itemID('Graceful legs'), itemID('Dark dye')], - 24_755: [itemID('Graceful gloves'), itemID('Dark dye')], - 24_758: [itemID('Graceful boots'), itemID('Dark dye')], - 24_746: [itemID('Graceful cape'), itemID('Dark dye')], - 25_069: [itemID('Graceful hood'), itemID('Trailblazer graceful ornament kit')], - 25_075: [itemID('Graceful top'), itemID('Trailblazer graceful ornament kit')], - 25_078: [itemID('Graceful legs'), itemID('Trailblazer graceful ornament kit')], - 25_081: [itemID('Graceful gloves'), itemID('Trailblazer graceful ornament kit')], - 25_084: [itemID('Graceful boots'), itemID('Trailblazer graceful ornament kit')], - 25_072: [itemID('Graceful cape'), itemID('Trailblazer graceful ornament kit')], + 24743: [itemID('Graceful hood'), itemID('Dark dye')], + 24749: [itemID('Graceful top'), itemID('Dark dye')], + 24752: [itemID('Graceful legs'), itemID('Dark dye')], + 24755: [itemID('Graceful gloves'), itemID('Dark dye')], + 24758: [itemID('Graceful boots'), itemID('Dark dye')], + 24746: [itemID('Graceful cape'), itemID('Dark dye')], + 25069: [itemID('Graceful hood'), itemID('Trailblazer graceful ornament kit')], + 25075: [itemID('Graceful top'), itemID('Trailblazer graceful ornament kit')], + 25078: [itemID('Graceful legs'), itemID('Trailblazer graceful ornament kit')], + 25081: [itemID('Graceful gloves'), itemID('Trailblazer graceful ornament kit')], + 25084: [itemID('Graceful boots'), itemID('Trailblazer graceful ornament kit')], + 25072: [itemID('Graceful cape'), itemID('Trailblazer graceful ornament kit')], [itemID('Dragon axe (or)')]: [itemID('Dragon axe'), itemID('Trailblazer tool ornament kit')], [itemID('Infernal axe (or)')]: [itemID('Infernal axe'), itemID('Trailblazer tool ornament kit')], [itemID('Dragon harpoon (or)')]: [itemID('Dragon harpoon'), itemID('Trailblazer tool ornament kit')], [itemID('Infernal harpoon (or)')]: [itemID('Infernal harpoon'), itemID('Trailblazer tool ornament kit')], - 25_376: [itemID('Dragon pickaxe'), itemID('Trailblazer tool ornament kit')], + 25376: [itemID('Dragon pickaxe'), itemID('Trailblazer tool ornament kit')], [itemID('Infernal pickaxe (or)')]: [itemID('Infernal pickaxe'), itemID('Trailblazer tool ornament kit')], [itemID('Dragon pickaxe (or)')]: [itemID('Dragon pickaxe'), itemID('Zalcano shard')], - 12_797: [itemID('Dragon pickaxe'), itemID('Dragon pickaxe upgrade kit')], - 12_795: [itemID('Steam battlestaff'), itemID('Steam staff upgrade kit')], - 21_198: [itemID('Lava battlestaff'), itemID('Lava staff upgrade kit')], - 12_807: [itemID('Odium ward'), itemID('Ward upgrade kit')], - 12_806: [itemID('Malediction ward'), itemID('Ward upgrade kit')], - 12_765: [itemID('Dark bow'), itemID('Green dark bow paint')], - 12_766: [itemID('Dark bow'), itemID('Blue dark bow paint')], - 12_767: [itemID('Dark bow'), itemID('Yellow dark bow paint')], - 12_768: [itemID('Dark bow'), itemID('White dark bow paint')], + 12797: [itemID('Dragon pickaxe'), itemID('Dragon pickaxe upgrade kit')], + 12795: [itemID('Steam battlestaff'), itemID('Steam staff upgrade kit')], + 21198: [itemID('Lava battlestaff'), itemID('Lava staff upgrade kit')], + 12807: [itemID('Odium ward'), itemID('Ward upgrade kit')], + 12806: [itemID('Malediction ward'), itemID('Ward upgrade kit')], + 12765: [itemID('Dark bow'), itemID('Green dark bow paint')], + 12766: [itemID('Dark bow'), itemID('Blue dark bow paint')], + 12767: [itemID('Dark bow'), itemID('Yellow dark bow paint')], + 12768: [itemID('Dark bow'), itemID('White dark bow paint')], [itemID('Volcanic abyssal whip')]: [itemID('Abyssal whip'), itemID('Volcanic whip mix')], [itemID('Frozen abyssal whip')]: [itemID('Abyssal whip'), itemID('Frozen whip mix')], - 12_848: [itemID('Granite maul'), itemID('Granite clamp')], - 24_227: [24_225, itemID('Granite clamp')], + 12848: [itemID('Granite maul'), itemID('Granite clamp')], + 24227: [24_225, itemID('Granite clamp')], [itemID('Abyssal tentacle')]: [itemID('Abyssal whip'), itemID('Kraken tentacle')], [itemID("Thammaron's sceptre (a)")]: [itemID("Thammaron's sceptre (au)")], [itemID('Webweaver bow')]: [itemID('Webweaver bow (u)')], @@ -98,7 +98,7 @@ export const gearSwap: IGearSwap = { [itemID('Venator bow')]: [itemID('Venator bow (uncharged)')] }; -export const lockedItems = resolveItems([ +const lockedItems = resolveItems([ 'Rune pouch (l)', 'Fire cape (l)', 'Infernal cape (l)', @@ -188,8 +188,8 @@ export default function calculateGearLostOnDeathWilderness( >{} ) { // 1 - Duplicate user gear - let userGear = { ...deepClone(options.gear) }; - let removableItems: { slot: EquipmentSlot; sorter: number; originalItem: Item }[] = []; + const userGear = { ...deepClone(options.gear) }; + const removableItems: { slot: EquipmentSlot; sorter: number; originalItem: Item }[] = []; // 2 - Swap user gear to the correct gear for death calculations for (const [_slot, _data] of objectEntries(userGear)) { diff --git a/src/lib/util/calculateTripConsumableCost.ts b/src/lib/util/calculateTripConsumableCost.ts new file mode 100644 index 00000000000..08b9fe3a0df --- /dev/null +++ b/src/lib/util/calculateTripConsumableCost.ts @@ -0,0 +1,15 @@ +import { Time } from 'e'; +import type { Consumable } from '../minions/types'; + +export const calculateTripConsumableCost = (c: Consumable, quantity: number, duration: number) => { + const consumableCost = c.itemCost.clone(); + if (c.qtyPerKill) { + consumableCost.multiply(quantity); + } else if (c.qtyPerMinute) { + consumableCost.multiply(duration / Time.Minute); + } + for (const [item, qty] of Object.entries(consumableCost.bank)) { + consumableCost.bank[item] = Math.ceil(qty); + } + return consumableCost; +}; diff --git a/src/lib/util/canvasUtil.ts b/src/lib/util/canvasUtil.ts index 10d26f5d41c..aba8ff06108 100644 --- a/src/lib/util/canvasUtil.ts +++ b/src/lib/util/canvasUtil.ts @@ -1,4 +1,5 @@ -import { Canvas, Image, loadImage, SKRSContext2D } from '@napi-rs/canvas'; +import type { Image, SKRSContext2D } from '@napi-rs/canvas'; +import { Canvas, loadImage } from '@napi-rs/canvas'; import { formatItemStackQuantity, generateHexColorForCashStack } from '@oldschoolgg/toolkit'; import { assert } from '../util'; @@ -31,10 +32,6 @@ export function drawTitleText(ctx: SKRSContext2D, title: string, x: number, y: n fillTextXTimesInCtx(ctx, title, x, y); } -export function canvasImageFromBuffer(imageBuffer: Buffer): Promise { - return loadImage(imageBuffer); -} - export function drawImageWithOutline( ctx: SKRSContext2D, image: Canvas | Image, @@ -43,8 +40,8 @@ export function drawImageWithOutline( dw: number, dh: number, outlineColor: string, - outlineWidth: number = 1, - alpha: number = 0.5 + outlineWidth = 1, + alpha = 0.5 ): void { const dArr = [-1, -1, 0, -1, 1, -1, -1, 0, 1, 0, -1, 1, 0, 1, 1, 1]; const purplecanvas = new Canvas(image.width + (outlineWidth + 2), image.height + (outlineWidth + 2)); @@ -60,7 +57,7 @@ export function drawImageWithOutline( } export function calcAspectRatioFit(srcWidth: number, srcHeight: number, maxWidth: number, maxHeight: number) { - let ratio = Math.min(maxWidth / srcWidth, maxHeight / srcHeight); + const ratio = Math.min(maxWidth / srcWidth, maxHeight / srcHeight); return { width: srcWidth * ratio, height: srcHeight * ratio }; } @@ -70,11 +67,10 @@ function printMultilineText(ctx: SKRSContext2D, text: string, x: number, y: numb let linePositionY = y; for (const line of lines) { - let lineMeasured = ctx.measureText(line); - let thisX = Math.floor(x - lineMeasured.width / 2); + const lineMeasured = ctx.measureText(line); + const thisX = Math.floor(x - lineMeasured.width / 2); ctx.fillText(line, thisX, Math.floor(linePositionY)); - // eslint-disable-next-line @typescript-eslint/restrict-plus-operands - let height: number = lineMeasured.actualBoundingBoxAscent + lineMeasured.actualBoundingBoxDescent; + const height: number = lineMeasured.actualBoundingBoxAscent + lineMeasured.actualBoundingBoxDescent; linePositionY += height + 1; } } diff --git a/src/lib/util/chart.ts b/src/lib/util/chart.ts index a7c251bc484..85e394d9646 100644 --- a/src/lib/util/chart.ts +++ b/src/lib/util/chart.ts @@ -1,157 +1,163 @@ -import type { ChartConfiguration } from 'chart.js'; -import { ChartJSNodeCanvas } from 'chartjs-node-canvas'; -import ChartDataLabels from 'chartjs-plugin-datalabels'; +import deepmerge from 'deepmerge'; +import fetch from 'node-fetch'; -const width = 1000; // px -const height = 500; // px -const backgroundColour = 'white'; -const canvasRenderService = new ChartJSNodeCanvas({ width, height, backgroundColour }); +const colors = [ + '#fd7f6f', + '#7eb0d5', + '#b2e061', + '#bd7ebe', + '#ffb55a', + '#ffee65', + '#beb9db', + '#fdcce5', + '#8bd3c7', + '#ea5545', + '#f46a9b', + '#ef9b20', + '#edbf33', + '#ede15b', + '#bdcf32', + '#87bc45', + '#27aeef', + '#b33dc6', + '#e60049', + '#0bb4ff', + '#50e991', + '#e6d800', + '#9b19f5', + '#ffa300', + '#dc0ab4', + '#b3d4ff', + '#00bfa0' +]; +function getWrappedArrayItem(array: T[], index: number): T { + const wrappedIndex = ((index % array.length) + array.length) % array.length; + return array[wrappedIndex]; +} -export async function generateChart(config: ChartConfiguration) { - const buffer = await canvasRenderService.renderToBuffer(config); - return buffer; +function randomHexColor(value: number): string { + return getWrappedArrayItem(colors, Math.floor(value)); } -function randomHexColor(num: number) { - const hue = num * 137.508; // use golden angle approximation - return `hsl(${hue},50%,75%)`; +async function renderChart(url: string) { + if (process.env.TEST) { + console.log('\nRENDERING CHART\n'); + } + const response = await fetch(url, { + method: 'GET', + headers: { 'Content-Type': 'application/json' } + }); + + return response.buffer(); } -export async function pieChart(title: string, format: (value: any) => string, values: [string, number, string?][]) { - const options: ChartConfiguration = { - type: 'pie', - data: { - labels: values.map(i => i[0]), - datasets: [ - { - data: values.map(i => i[1]), - backgroundColor: values.map((val, index) => val[2] ?? randomHexColor(index)) - } - ] - }, - options: { - plugins: { - title: { display: true, text: title }, - datalabels: { - font: { - weight: 'bolder' - }, - formatter(value) { - return format(value); - } - } - } - }, - plugins: [ChartDataLabels] - }; - return generateChart(options); +export interface ChartOptions { + type: 'pie' | 'line' | 'bar'; + title: string; + values: ([string, number] | [string, number, string])[]; + format: ChartNumberFormat; } -export async function lineChart( - title: string, - values: [string, number, string?][], - yFormat: (value: number) => string = (value: number) => value.toString(), - xFormat: (value: string) => string = (value: string) => value, - showDataLabels = true -) { - const options: ChartConfiguration<'line'> = { - type: 'line', - data: { - labels: values.map(i => xFormat(i[0])), - datasets: [ - { - data: values.map(i => i[1]), - backgroundColor: values.map((_, index) => randomHexColor(index)), - fill: false, - pointRadius: 0 - } - ] + +export function createApexChartConfig({ type, title, values, format }: ChartOptions) { + const categories = values.map(([label]) => label); + const seriesName = title; + + const formatter = (formatList.find(f => f.name === format) ?? formatList[0]).format; + + let config = { + chart: { type }, + title: { text: title }, + series: [ + { + name: seriesName, + data: values.map(([label, value, color]) => ({ + x: label, + y: value, + fillColor: color ?? randomHexColor(value) + })) + } + ], + xaxis: { categories }, + dataLabels: { + enabled: true, + style: { + colors: ['#000'] + }, + formatter: 'FORMATTER' }, - options: { - plugins: { - title: { display: true, text: title }, - datalabels: { - display: showDataLabels, - font: { - weight: 'bolder' - }, - formatter(value: number) { - return yFormat(value); - } - }, - legend: { - display: false - } + yaxis: { + labels: { + formatter: 'FORMATTER' } } }; - return generateChart(options); -} - -export async function barChart( - title: string, - format: (value: any) => string, - values: [string, number, string?][], - useRelativeColors: boolean = false -) { - const positiveValues = values.map(i => i[1]).filter(v => v > 0); - const negativeValues = values.map(i => i[1]).filter(v => v < 0); + if (type === 'pie') { + config = deepmerge(config, { + plotOptions: { + pie: { + dataLabels: { + offset: 10 + } + } + } + }); + } - const maxPositiveValue = positiveValues.length > 0 ? Math.max(...positiveValues) : 0; - const maxNegativeValue = negativeValues.length > 0 ? Math.abs(Math.min(...negativeValues)) : 0; + if (type === 'bar') { + config = deepmerge(config, { + plotOptions: { + bar: { + dataLabels: { + position: 'top' + } + } + } + }); + } - const getRelativeSaturation = (value: number) => { - const saturationRange = 10; - const minSaturation = 60; - if (value >= 0 && maxPositiveValue !== 0) { - return minSaturation + (value / maxPositiveValue) * saturationRange; - } else if (value < 0 && maxNegativeValue !== 0) { - return minSaturation + (Math.abs(value) / maxNegativeValue) * saturationRange; - } - return minSaturation; - }; + if (format === 'percent') { + config = deepmerge(config, { + yaxis: { + min: 0, + max: 100 + } + }); + } - const getLightness = (value: number) => { - if (value >= 0) { - return 30 + (value / maxPositiveValue) * 20; - } - return 70 - (Math.abs(value) / maxNegativeValue) * 20; - }; + const encoded = JSON.stringify(config).replaceAll('"FORMATTER"', formatter.toString()); - const getColorForValue = (value: number) => { - const hue = value >= 0 ? 120 : 0; - const saturation = useRelativeColors ? getRelativeSaturation(value) : 100; - const lightness = useRelativeColors ? getLightness(value) : 50; - return `hsl(${hue}, ${saturation}%, ${lightness}%)`; + if (encoded.includes('FORMATTER')) { + throw new Error('Failed to encode chart config'); + } + return { + encoded, + config, + url: `https://quickchart.io/apex-charts/render?config=${encodeURIComponent(encoded)}` }; +} - const options: ChartConfiguration = { - type: 'bar', - data: { - labels: values.map(i => i[0]), - datasets: [ - { - data: values.map(i => i[1]), - backgroundColor: values.map(val => val[2] ?? getColorForValue(val[1])) - } - ] - }, - options: { - plugins: { - title: { display: true, text: title }, - datalabels: { - font: { - weight: 'bolder' - }, - formatter(value) { - return format(value); - } - }, - legend: { - display: false - } +const formatList = [ + { + name: 'kmb', + format: (v: number) => { + if (v > 999_999_999 || v < -999_999_999) { + return `${Math.round(v / 1_000_000_000)}b`; + } else if (v > 999_999 || v < -999_999) { + return `${Math.round(v / 1_000_000)}m`; + } else if (v > 999 || v < -999) { + return `${Math.round(v / 1000)}k`; } + return Math.round(v); } - }; - return generateChart(options); + }, + { name: 'percent', format: (v: number) => `${v}%` }, + { name: 'hours', format: (v: number) => `${v}hrs` }, + { name: 'delta', format: (v: number) => (v === 0 ? '0' : v > 0 ? `+${v}` : `-${v}`) } +] as const; +type ChartNumberFormat = (typeof formatList)[number]['name']; + +export async function createChart(options: ChartOptions) { + const res = createApexChartConfig(options); + return renderChart(res.url); } diff --git a/src/lib/util/chatHeadImage.ts b/src/lib/util/chatHeadImage.ts index 3aa5af3f6a2..24348ae112d 100644 --- a/src/lib/util/chatHeadImage.ts +++ b/src/lib/util/chatHeadImage.ts @@ -18,8 +18,9 @@ const marimboChatHead = loadAndCacheLocalImage('./src/lib/resources/images/marim const partyPeteHead = loadAndCacheLocalImage('./src/lib/resources/images/partyPete.png'); const mysteriousFigureHead = loadAndCacheLocalImage('./src/lib/resources/images/mysteriousFigure.png'); const rudolphChatHead = loadAndCacheLocalImage('./src/lib/resources/images/rudolph.png'); +const minimusHead = loadAndCacheLocalImage('./src/lib/resources/images/minimus.png'); -export const chatHeads = { +const chatHeads = { mejJal: mejJalChatHead, jane: janeChatHead, santa: santaChatHead, @@ -33,7 +34,8 @@ export const chatHeads = { bunny: bunnyChatHead, partyPete: partyPeteHead, mysteriousFigure: mysteriousFigureHead, - rudolph: rudolphChatHead + rudolph: rudolphChatHead, + minimus: minimusHead }; const names: Record = { @@ -44,13 +46,14 @@ const names: Record = { alry: 'Alry the Angler', wurMuTheMonkey: 'Wur Mu the Monkey', marimbo: 'Marimbo', - ketKeh: 'Tzhaar-Ket-Keh', + ketKeh: 'TzHaar-Ket-Keh', gertrude: 'Gertrude', antiSanta: 'Anti-Santa', bunny: 'Easter Bunny', partyPete: 'Party Pete', mysteriousFigure: 'Mysterious Figure', - rudolph: 'Rudolph the Reindeer' + rudolph: 'Rudolph the Reindeer', + minimus: 'Minimus' }; export async function newChatHeadImage({ content, head }: { content: string; head: keyof typeof chatHeads }) { diff --git a/src/lib/util/clLeaderboard.ts b/src/lib/util/clLeaderboard.ts index 3c010efa17b..6f44455d8a3 100644 --- a/src/lib/util/clLeaderboard.ts +++ b/src/lib/util/clLeaderboard.ts @@ -1,56 +1,64 @@ -import { UserEvent } from '@prisma/client'; - -import { prisma } from '../settings/prisma'; +import { stringMatches } from '@oldschoolgg/toolkit'; import { userEventsToMap } from './userEvents'; -export async function fetchCLLeaderboard({ - ironmenOnly, - items, - resultLimit, - method = 'cl_array', - userEvents -}: { - ironmenOnly: boolean; - items: number[]; - resultLimit: number; - method?: 'cl_array' | 'raw_cl'; - userEvents: UserEvent[] | null; -}) { - const userEventMap = userEventsToMap(userEvents); - const userIds = Array.from(userEventMap.keys()); - if (method === 'cl_array') { - const userIdsList = userIds.length > 0 ? userIds.map(i => `'${i}'`).join(', ') : 'NULL'; - const specificUsers = - userIds.length > 0 - ? await prisma.$queryRawUnsafe<{ id: string; qty: number }[]>(` - SELECT user_id::text AS id, CARDINALITY(cl_array) - CARDINALITY(cl_array - array[${items - .map(i => `${i}`) - .join(', ')}]) AS qty - FROM user_stats s INNER JOIN users u ON s.user_id::text = u.id - WHERE ${ironmenOnly ? 'u."minion.ironman" = true AND' : ''} user_id::text IN (${userIdsList}) -`) - : []; - const generalUsers = await prisma.$queryRawUnsafe<{ id: string; qty: number }[]>(` - SELECT user_id::text AS id, CARDINALITY(cl_array) - CARDINALITY(cl_array - array[${items - .map(i => `${i}`) - .join(', ')}]) AS qty - FROM user_stats - ${ironmenOnly ? 'INNER JOIN "users" on "users"."id" = "user_stats"."user_id"::text' : ''} - WHERE (cl_array && array[${items.map(i => `${i}`).join(', ')}] - ${ironmenOnly ? 'AND "users"."minion.ironman" = true' : ''}) - ${userIds.length > 0 ? `AND user_id::text NOT IN (${userIdsList})` : ''} - ORDER BY qty DESC - LIMIT ${resultLimit} -`); +export async function fetchMultipleCLLeaderboards( + leaderboards: { + ironmenOnly: boolean; + items: number[]; + resultLimit: number; + clName: string; + }[] +) { + const userEvents = await prisma.userEvent.findMany({ + where: { + type: 'CLCompletion' + }, + orderBy: { + date: 'asc' + } + }); + const parsedLeaderboards = leaderboards.map(l => { + const userEventMap = userEventsToMap( + userEvents.filter(e => e.collection_log_name && stringMatches(e.collection_log_name, l.clName)) + ); + return { + ...l, + userEventMap + }; + }); + + const results = await prisma.$transaction([ + ...parsedLeaderboards.map(({ items, userEventMap, ironmenOnly, resultLimit }) => { + const SQL_ITEMS = `ARRAY[${items.map(i => `${i}`).join(', ')}]`; + const userIds = Array.from(userEventMap.keys()); + const userIdsList = userIds.length > 0 ? userIds.map(i => `'${i}'`).join(', ') : 'NULL'; + + const query = ` +SELECT id, qty +FROM ( + SELECT id, CARDINALITY(cl_array & ${SQL_ITEMS}) AS qty + FROM users + WHERE (cl_array && ${SQL_ITEMS} + ${ironmenOnly ? 'AND "users"."minion.ironman" = true' : ''}) ${userIds.length > 0 ? `OR id IN (${userIdsList})` : ''} + ) AS subquery +ORDER BY qty DESC +LIMIT ${resultLimit}; +`; + return prisma.$queryRawUnsafe<{ id: string; qty: number }[]>(query); + }) + ]); - const users = [...specificUsers, ...generalUsers] + return results.map((res, index) => { + const src = parsedLeaderboards[index]; + + const users = res .sort((a, b) => { const valueDifference = b.qty - a.qty; if (valueDifference !== 0) { return valueDifference; } - const dateA = userEventMap.get(a.id); - const dateB = userEventMap.get(b.id); + const dateA = src.userEventMap.get(a.id); + const dateB = src.userEventMap.get(b.id); if (dateA && dateB) { return dateA - dateB; } @@ -63,32 +71,28 @@ export async function fetchCLLeaderboard({ return 0; }) .filter(i => i.qty > 0); - return users; - } - const users = ( - await prisma.$queryRawUnsafe<{ id: string; qty: number }[]>(` -SELECT id, (cardinality(u.cl_keys) - u.inverse_length) as qty - FROM ( - SELECT array(SELECT * FROM jsonb_object_keys("collectionLogBank")) "cl_keys", - id, "collectionLogBank", - cardinality(array(SELECT * FROM jsonb_object_keys("collectionLogBank" - array[${items - .map(i => `'${i}'`) - .join(', ')}]))) "inverse_length" - FROM users - WHERE ("collectionLogBank" ?| array[${items.map(i => `'${i}'`).join(', ')}] - ${ironmenOnly ? 'AND "minion.ironman" = true' : ''}) - ${ - userIds.length > 0 - ? `OR (id in (${userIds.map(i => `'${i}'`).join(', ')})${ironmenOnly ? 'AND "minion.ironman" = true' : ''})` - : '' - } -) u -ORDER BY qty DESC -LIMIT ${resultLimit}; -`) - ).filter(i => i.qty > 0); - return users; + return { + ...src, + users + }; + }); +} + +export async function fetchCLLeaderboard({ + ironmenOnly, + items, + resultLimit, + clName +}: { + ironmenOnly: boolean; + items: number[]; + resultLimit: number; + method?: 'cl_array'; + clName: string; +}) { + const result = await fetchMultipleCLLeaderboards([{ ironmenOnly, items, resultLimit, clName }]); + return result[0]; } export async function fetchTameCLLeaderboard({ items, resultLimit }: { items: number[]; resultLimit: number }) { diff --git a/src/lib/util/clientSettings.ts b/src/lib/util/clientSettings.ts index 857a074834c..ad8faa445bb 100644 --- a/src/lib/util/clientSettings.ts +++ b/src/lib/util/clientSettings.ts @@ -1,7 +1,6 @@ -import { ClientStorage, Prisma } from '@prisma/client'; +import type { ClientStorage, Prisma } from '@prisma/client'; import { globalConfig } from '../constants'; -import { prisma } from '../settings/prisma'; // Is not typesafe, returns only what is selected, but will say it contains everything. export async function mahojiClientSettingsFetch(select: Prisma.ClientStorageSelect = { id: true }) { diff --git a/src/lib/util/combatAmmoUsage.ts b/src/lib/util/combatAmmoUsage.ts index e9d53cde3f9..402130e1b09 100644 --- a/src/lib/util/combatAmmoUsage.ts +++ b/src/lib/util/combatAmmoUsage.ts @@ -1,8 +1,8 @@ -import { reduceNumByPercent, Time } from 'e'; +import { Time, reduceNumByPercent } from 'e'; import { Bank } from 'oldschooljs'; -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; -import { GearSetupType } from '../gear'; +import type { GearSetupType } from '../gear'; import getOSItem from './getOSItem'; interface CombatItemConsumption { @@ -45,7 +45,7 @@ export default function combatAmmoUsage(options: { duration: number; gearType: G // Ignore this gear, if this boost cant be applied to this gear type if (requiredGearType && !requiredGearType.includes(options.gearType)) continue; if (gear.hasEquipped(item.name)) { - let requiredBank = required.clone(); + const requiredBank = required.clone(); let toRemove = Math.ceil(duration / every) * consume; if (reductions) { for (const [reductionItem, reductionPercentage] of reductions.items()) { diff --git a/src/lib/util/commandUsage.ts b/src/lib/util/commandUsage.ts index e93e5ae221b..153064fcdb1 100644 --- a/src/lib/util/commandUsage.ts +++ b/src/lib/util/commandUsage.ts @@ -1,5 +1,5 @@ -import { command_usage_status, Prisma } from '@prisma/client'; -import { CommandOptions } from 'mahoji/dist/lib/types'; +import type { CommandOptions } from '@oldschoolgg/toolkit'; +import type { Prisma, command_name_enum } from '@prisma/client'; import { getCommandArgs } from '../../mahoji/lib/util'; @@ -7,7 +7,6 @@ export function makeCommandUsage({ userID, channelID, guildID, - flags, commandName, args, isContinue, @@ -17,7 +16,6 @@ export function makeCommandUsage({ userID: string | bigint; channelID: string | bigint; guildID?: string | bigint | null; - flags: null | Record; commandName: string; args: CommandOptions; isContinue: null | boolean; @@ -26,12 +24,10 @@ export function makeCommandUsage({ }): Prisma.CommandUsageCreateInput { return { user_id: BigInt(userID), - command_name: commandName, - status: command_usage_status.Unknown, + command_name: commandName as command_name_enum, args: getCommandArgs(commandName, args), channel_id: BigInt(channelID), guild_id: guildID ? BigInt(guildID) : null, - flags: flags ? (Object.keys(flags).length > 0 ? flags : undefined) : undefined, is_continue: isContinue ?? undefined, inhibited, continue_delta_millis: continueDeltaMillis diff --git a/src/lib/util/customItemEffects.ts b/src/lib/util/customItemEffects.ts index a6a38030cf5..23c2c5b22c0 100644 --- a/src/lib/util/customItemEffects.ts +++ b/src/lib/util/customItemEffects.ts @@ -1,5 +1,5 @@ -import { Canvas, Image, loadImage } from '@napi-rs/canvas'; -import LRUCache from 'lru-cache'; +import { Canvas, type Image, loadImage } from '@napi-rs/canvas'; +import { LRUCache } from 'lru-cache'; import { getPaintedItemImage, paintColorsMap } from '../paintColors'; import itemID from './itemID'; diff --git a/src/lib/util/determineRunes.ts b/src/lib/util/determineRunes.ts index 318122b4b8a..bffcc197a97 100644 --- a/src/lib/util/determineRunes.ts +++ b/src/lib/util/determineRunes.ts @@ -1,7 +1,7 @@ import { notEmpty } from 'e'; import { Bank } from 'oldschooljs'; -import resolveItems from './resolveItems'; +import { resolveItems } from 'oldschooljs/dist/util/util'; const res = resolveItems; diff --git a/src/lib/util/elderClueRequirements.ts b/src/lib/util/elderClueRequirements.ts index 1f6dc10280c..2182f13856d 100644 --- a/src/lib/util/elderClueRequirements.ts +++ b/src/lib/util/elderClueRequirements.ts @@ -50,7 +50,7 @@ export const elderSherlockItems = deepResolveItems([ ]); export async function checkElderClueRequirements(user: MUser) { - let unmetRequirements: string[] = []; + const unmetRequirements: string[] = []; // 120 all const skillsNot120 = Object.entries(user.skillsAsLevels) diff --git a/src/lib/util/equipMulti.ts b/src/lib/util/equipMulti.ts index dba19bc0e50..844a1fed17d 100644 --- a/src/lib/util/equipMulti.ts +++ b/src/lib/util/equipMulti.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import { EquipmentSlot } from 'oldschooljs/dist/meta/types'; -import { GearSetup } from '../gear/types'; +import type { GearSetup } from '../gear/types'; import { isValidGearSetup, skillsMeetRequirements } from '../util'; import { parseStringBank } from './parseStringBank'; @@ -56,22 +56,22 @@ export function gearEquipMultiImpl( for (const [item, qty] of equipBank.items()) { const { slot } = item.equipment!; if (slot === EquipmentSlot.TwoHanded && equippedGear[EquipmentSlot.Shield]) { - unequipBank.add(equippedGear[EquipmentSlot.Shield]!.item, equippedGear[EquipmentSlot.Shield]!.quantity); + unequipBank.add(equippedGear[EquipmentSlot.Shield]?.item, equippedGear[EquipmentSlot.Shield]?.quantity); equippedGear[EquipmentSlot.Shield] = null; } if (slot === EquipmentSlot.TwoHanded && equippedGear[EquipmentSlot.Weapon]) { - unequipBank.add(equippedGear[EquipmentSlot.Weapon]!.item, equippedGear[EquipmentSlot.Weapon]!.quantity); + unequipBank.add(equippedGear[EquipmentSlot.Weapon]?.item, equippedGear[EquipmentSlot.Weapon]?.quantity); equippedGear[EquipmentSlot.Weapon] = null; } if (equippedGear[EquipmentSlot.TwoHanded] && (slot === EquipmentSlot.Shield || slot === EquipmentSlot.Weapon)) { unequipBank.add( - equippedGear[EquipmentSlot.TwoHanded]!.item, - equippedGear[EquipmentSlot.TwoHanded]!.quantity + equippedGear[EquipmentSlot.TwoHanded]?.item, + equippedGear[EquipmentSlot.TwoHanded]?.quantity ); equippedGear[EquipmentSlot.TwoHanded] = null; } if (equippedGear[slot]) { - unequipBank.add(equippedGear[slot]!.item, equippedGear[slot]!.quantity); + unequipBank.add(equippedGear[slot]?.item, equippedGear[slot]?.quantity); } equippedGear[slot] = { item: item.id, quantity: qty }; } diff --git a/src/lib/util/farmingHelpers.ts b/src/lib/util/farmingHelpers.ts index a9fa6953420..c58a32610cc 100644 --- a/src/lib/util/farmingHelpers.ts +++ b/src/lib/util/farmingHelpers.ts @@ -1,10 +1,11 @@ -import { FarmedCrop, User } from '@prisma/client'; -import { BaseMessageOptions, ButtonBuilder } from 'discord.js'; +import type { User } from '@prisma/client'; +import type { BaseMessageOptions, ButtonBuilder } from 'discord.js'; +import { dateFm } from '@oldschoolgg/toolkit'; import { Emoji } from '../constants'; -import { IPatchData, IPatchDataDetailed } from '../minions/farming/types'; +import type { IPatchData, IPatchDataDetailed } from '../minions/farming/types'; import Farming from '../skilling/skills/farming'; -import { dateFm, makeAutoFarmButton, makeComponents, stringMatches } from '../util'; +import { makeAutoFarmButton, makeComponents, stringMatches } from '../util'; export const farmingPatchNames = [ 'herb', @@ -72,17 +73,3 @@ export function userGrowingProgressStr(patchesDetailed: IPatchDataDetailed[]): B components: makeComponents(buttons) }; } - -export function parseFarmedCrop(crop: FarmedCrop) { - return { - id: crop.id, - userID: crop.user_id, - datePlanted: crop.date_planted, - dateHarvested: crop.date_harvested, - itemID: crop.item_id, - plant: Farming.Plants.find(i => i.id === crop.item_id)!, - quantityPlanted: crop.quantity_planted, - upgradeType: crop.upgrade_type, - paid: crop.paid_for_protection - }; -} diff --git a/src/lib/util/findBISGear.ts b/src/lib/util/findBISGear.ts index 16a5abb2f0f..b4066eca01a 100644 --- a/src/lib/util/findBISGear.ts +++ b/src/lib/util/findBISGear.ts @@ -3,7 +3,7 @@ import { EquipmentSlot } from 'oldschooljs/dist/meta/types'; import { allEquippableItems } from '../../mahoji/lib/mahojiCommandOptions'; import { getSimilarItems } from '../data/similarItems'; import { allDyedItems } from '../dyedItems'; -import { GearStat } from '../gear/types'; +import type { GearStat } from '../gear/types'; import { Gear } from '../structures/Gear'; import { itemNameFromID } from './smallUtils'; diff --git a/src/lib/util/findGroupOfUser.ts b/src/lib/util/findGroupOfUser.ts new file mode 100644 index 00000000000..e9528d5b0c5 --- /dev/null +++ b/src/lib/util/findGroupOfUser.ts @@ -0,0 +1,15 @@ +export async function findGroupOfUser(userID: string) { + const user = await roboChimpClient.user.findUnique({ + where: { + id: BigInt(userID) + } + }); + if (!user || !user.user_group_id) return [userID]; + const group = await roboChimpClient.user.findMany({ + where: { + user_group_id: user.user_group_id + } + }); + if (!group) return [userID]; + return group.map(u => u.id.toString()); +} diff --git a/src/lib/util/findMonster.ts b/src/lib/util/findMonster.ts index 5088158d9ab..cc6f4ba2e09 100644 --- a/src/lib/util/findMonster.ts +++ b/src/lib/util/findMonster.ts @@ -1,5 +1,5 @@ import killableMonsters from '../minions/data/killableMonsters'; -import { KillableMonster } from '../minions/types'; +import type { KillableMonster } from '../minions/types'; import { stringMatches } from '../util'; export default function findMonster(str = ''): KillableMonster | undefined { diff --git a/src/lib/util/forcefullyUnequipItem.ts b/src/lib/util/forcefullyUnequipItem.ts index 41aaac3ad7b..2b3ca7d5427 100644 --- a/src/lib/util/forcefullyUnequipItem.ts +++ b/src/lib/util/forcefullyUnequipItem.ts @@ -1,5 +1,5 @@ import { objectEntries } from 'e'; -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; export async function forcefullyUnequipItem(user: MUser, item: Item) { const setup = objectEntries(user.gear).find(g => g[1].hasEquipped(item.id, false, false)); diff --git a/src/lib/util/getKCByName.ts b/src/lib/util/getKCByName.ts index 7a744f63c55..76cf7d9cf78 100644 --- a/src/lib/util/getKCByName.ts +++ b/src/lib/util/getKCByName.ts @@ -1,8 +1,8 @@ import { stringMatches } from '@oldschoolgg/toolkit'; -import Monster from 'oldschooljs/dist/structures/Monster'; +import type Monster from 'oldschooljs/dist/structures/Monster'; import { effectiveMonsters } from '../minions/data/killableMonsters'; -import { getMinigameScore, Minigames } from '../settings/minigames'; +import { Minigames, getMinigameScore } from '../settings/minigames'; import creatures from '../skilling/skills/hunter/creatures'; export async function getKCByName(user: MUser, kcName: string): Promise<[string, number] | [null, 0]> { diff --git a/src/lib/util/getKalphiteKingGearStats.ts b/src/lib/util/getKalphiteKingGearStats.ts index 689087590b9..b61966b5451 100644 --- a/src/lib/util/getKalphiteKingGearStats.ts +++ b/src/lib/util/getKalphiteKingGearStats.ts @@ -1,7 +1,7 @@ import { calcWhatPercent, randInt } from 'e'; import { maxOffenceStats } from '../gear'; -import { GearStats } from '../gear/types'; +import type { GearStats } from '../gear/types'; import { KalphiteKingMonster } from '../minions/data/killableMonsters/custom/bosses/KalphiteKing'; export async function getKalphiteKingGearStats( diff --git a/src/lib/util/getNexGearStats.ts b/src/lib/util/getNexGearStats.ts index 9eab2f5e2c4..3a16cd8b08e 100644 --- a/src/lib/util/getNexGearStats.ts +++ b/src/lib/util/getNexGearStats.ts @@ -2,7 +2,7 @@ import { calcWhatPercent, randInt } from 'e'; import { itemID } from 'oldschooljs/dist/util'; import { maxOffenceStats } from '../gear'; -import { GearStats } from '../gear/types'; +import type { GearStats } from '../gear/types'; import { NexMonster } from '../nex'; export async function getNexGearStats( diff --git a/src/lib/util/getOSItem.ts b/src/lib/util/getOSItem.ts index 87bf336a5ad..1e3fc7bb221 100644 --- a/src/lib/util/getOSItem.ts +++ b/src/lib/util/getOSItem.ts @@ -1,39 +1,5 @@ -import { UserError } from '@oldschoolgg/toolkit/dist/lib/UserError'; -import { Items } from 'oldschooljs'; -import { Item } from 'oldschooljs/dist/meta/types'; +import { getItem, getItemOrThrow } from 'oldschooljs/dist/util/util'; -import { production } from '../../config'; +export { getItem, getItemOrThrow as getOSItem }; -const cache = new Map(); - -function cleanItemName(itemName: string) { - return itemName.replace(/’/g, "'"); -} - -export default function getOSItem(itemName: string | number): Item { - if (cache.has(itemName)) { - return cache.get(itemName); - } - - let identifier: string | number | undefined = ''; - if (typeof itemName === 'number') { - identifier = itemName; - } else { - const parsed = Number(itemName); - identifier = isNaN(parsed) ? cleanItemName(itemName) : parsed; - } - - const osItem = Items.get(identifier) as Item | undefined; - if (!osItem) throw new UserError(`${production ? 'That item' : identifier} doesn't exist.`); - cache.set(itemName, osItem); - return osItem; -} - -export function getItem(itemName: string | number | undefined): Item | null { - if (!itemName) return null; - try { - return getOSItem(itemName); - } catch { - return null; - } -} +export default getItemOrThrow; diff --git a/src/lib/util/getUsersTameCL.ts b/src/lib/util/getUsersTameCL.ts deleted file mode 100644 index 45c448b62c6..00000000000 --- a/src/lib/util/getUsersTameCL.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Bank } from 'oldschooljs'; -import { ItemBank } from 'oldschooljs/dist/meta/types'; - -import { prisma } from '../settings/prisma'; - -export async function getUsersTamesCollectionLog(userID: string) { - const allTames = await prisma.tame.findMany({ - where: { - user_id: userID - } - }); - let totalBank = new Bank(); - for (const tame of allTames) { - totalBank.add(tame.max_total_loot as ItemBank); - } - await prisma.userStats.upsert({ - where: { - user_id: BigInt(userID) - }, - create: { - user_id: BigInt(userID), - tame_cl_bank: totalBank.bank - }, - update: { - tame_cl_bank: totalBank.bank - } - }); - return totalBank; -} diff --git a/src/lib/util/giveaway.ts b/src/lib/util/giveaway.ts index 2a896f8e914..21af395ab0d 100644 --- a/src/lib/util/giveaway.ts +++ b/src/lib/util/giveaway.ts @@ -1,11 +1,11 @@ -import { Giveaway } from '@prisma/client'; -import { MessageEditOptions, time, userMention } from 'discord.js'; -import { debounce, noOp, randArrItem, Time } from 'e'; +import type { Giveaway } from '@prisma/client'; +import { type MessageEditOptions, time, userMention } from 'discord.js'; +import { Time, debounce, noOp, randArrItem } from 'e'; import { Bank } from 'oldschooljs'; -import { ItemBank } from 'oldschooljs/dist/meta/types'; +import type { ItemBank } from 'oldschooljs/dist/meta/types'; import { Events } from '../constants'; -import { prisma } from '../settings/prisma'; + import { channelIsSendable } from '../util'; import { logError } from './logError'; import { sendToChannelID } from './webhook'; diff --git a/src/lib/util/globalInteractions.ts b/src/lib/util/globalInteractions.ts index a71b8316ff2..bdcc5480434 100644 --- a/src/lib/util/globalInteractions.ts +++ b/src/lib/util/globalInteractions.ts @@ -1,22 +1,24 @@ import { mentionCommand } from '@oldschoolgg/toolkit'; -import { ButtonBuilder, ButtonInteraction, ButtonStyle, Interaction } from 'discord.js'; -import { removeFromArr, Time, uniqueArr } from 'e'; +import type { ButtonInteraction, Interaction } from 'discord.js'; +import { ButtonBuilder, ButtonStyle } from 'discord.js'; +import { Time, removeFromArr, uniqueArr } from 'e'; import { Bank } from 'oldschooljs'; import { getItemContractDetails, handInContract } from '../../mahoji/commands/ic'; import { cancelGEListingCommand } from '../../mahoji/lib/abstracted_commands/cancelGEListingCommand'; import { autoContract } from '../../mahoji/lib/abstracted_commands/farmingContractCommand'; import { shootingStarsCommand, starCache } from '../../mahoji/lib/abstracted_commands/shootingStarsCommand'; -import { Cooldowns } from '../../mahoji/lib/Cooldowns'; import { userStatsBankUpdate } from '../../mahoji/mahojiSettings'; import { repeatTameTrip } from '../../tasks/tames/tameTasks'; import { modifyBusyCounter } from '../busyCounterCache'; -import { ClueTier } from '../clues/clueTiers'; +import type { ClueTier } from '../clues/clueTiers'; import { BitField, PerkTier } from '../constants'; -import { prisma } from '../settings/prisma'; + +import { RateLimitManager } from '@sapphire/ratelimits'; +import { InteractionID } from '../InteractionID'; import { runCommand } from '../settings/settings'; import { toaHelpCommand } from '../simulation/toa'; -import { ItemBank } from '../types'; +import type { ItemBank } from '../types'; import { formatDuration, stringMatches } from '../util'; import { CACHED_ACTIVE_USER_IDS } from './cachedUserIDs'; import { updateGiveawayMessage } from './giveaway'; @@ -64,11 +66,13 @@ const globalInteractionActions = [ 'CHECK_TOA' ] as const; -export type GlobalInteractionAction = (typeof globalInteractionActions)[number]; +type GlobalInteractionAction = (typeof globalInteractionActions)[number]; function isValidGlobalInteraction(str: string): str is GlobalInteractionAction { return globalInteractionActions.includes(str as GlobalInteractionAction); } +const buttonRatelimiter = new RateLimitManager(Time.Second * 2, 1); + export function makeOpenCasketButton(tier: ClueTier) { const name: Uppercase = tier.name.toUpperCase() as Uppercase; const id: GlobalInteractionAction = `OPEN_${name}_CASKET`; @@ -235,15 +239,19 @@ async function giveawayButtonHandler(user: MUser, customID: string, interaction: return interactionReply(interaction, { content: 'You left the giveaway.', ephemeral: true }); } -async function repeatTripHandler(user: MUser, interaction: ButtonInteraction) { - if (user.minionIsBusy) return interactionReply(interaction, { content: 'Your minion is busy.' }); +async function repeatTripHandler(interaction: ButtonInteraction) { + if (minionIsBusy(interaction.user.id)) + return interactionReply(interaction, { content: 'Your minion is busy.', ephemeral: true }); const trips = await fetchRepeatTrips(interaction.user.id); - if (trips.length === 0) + if (trips.length === 0) { return interactionReply(interaction, { content: "Couldn't find a trip to repeat.", ephemeral: true }); + } const id = interaction.customId; const split = id.split('_'); const matchingActivity = trips.find(i => i.type === split[2]); - if (!matchingActivity) return repeatTrip(interaction, trips[0]); + if (!matchingActivity) { + return repeatTrip(interaction, trips[0]); + } return repeatTrip(interaction, matchingActivity); } @@ -403,6 +411,15 @@ async function handleGEButton(user: MUser, id: string, interaction: ButtonIntera export async function interactionHook(interaction: Interaction) { if (!interaction.isButton()) return; + const ignoredInteractionIDs = [ + 'CONFIRM', + 'CANCEL', + 'PARTY_JOIN', + ...Object.values(InteractionID.PaginatedMessage), + ...Object.values(InteractionID.Slayer) + ]; + if (ignoredInteractionIDs.includes(interaction.customId)) return; + if (['DYN_', 'LP_'].some(s => interaction.customId.startsWith(s))) return; if (globalClient.isShuttingDown) { return interactionReply(interaction, { @@ -411,17 +428,22 @@ export async function interactionHook(interaction: Interaction) { }); } - debugLog(`Interaction hook for button [${interaction.customId}]`, { - user_id: interaction.user.id, - channel_id: interaction.channelId, - guild_id: interaction.guildId - }); const id = interaction.customId; const userID = interaction.user.id; + const ratelimit = buttonRatelimiter.acquire(userID); + if (ratelimit.limited) { + return interactionReply(interaction, { + content: `You're on cooldown from clicking buttons, please wait: ${formatDuration(ratelimit.remainingTime, true)}.`, + ephemeral: true + }); + } + + if (id.includes('REPEAT_TRIP')) return repeatTripHandler(interaction); + const user = await mUserFetch(userID); + if (id.includes('GIVEAWAY_')) return giveawayButtonHandler(user, id, interaction); - if (id.includes('REPEAT_TRIP')) return repeatTripHandler(user, interaction); if (id.includes('DONATE_IC')) return donateICHandler(interaction); if (id.startsWith('GPE_')) return handleGearPresetEquip(user, id, interaction); if (id.startsWith('PTR_')) return handlePinnedTripRepeat(user, id, interaction); @@ -448,18 +470,10 @@ export async function interactionHook(interaction: Interaction) { continueDeltaMillis: null }; - const cd = Cooldowns.get(userID, 'button', Time.Second * 3); - if (cd !== null) { - return interactionReply(interaction, { - content: `You're on cooldown from clicking buttons, please wait: ${formatDuration(cd, true)}.`, - ephemeral: true - }); - } - const timeSinceMessage = Date.now() - new Date(interaction.message.createdTimestamp).getTime(); const timeLimit = reactionTimeLimit(user.perkTier()); if (timeSinceMessage > Time.Day) { - console.log( + debugLog( `${user.id} clicked Diff[${formatDuration(timeSinceMessage)}] Button[${id}] Message[${ interaction.message.id }]` @@ -475,7 +489,7 @@ export async function interactionHook(interaction: Interaction) { } async function doClue(tier: ClueTier['name']) { - runCommand({ + return runCommand({ commandName: 'clue', args: { tier }, bypassInhibitors: true, @@ -484,7 +498,7 @@ export async function interactionHook(interaction: Interaction) { } async function openCasket(tier: ClueTier['name']) { - runCommand({ + return runCommand({ commandName: 'open', args: { name: tier, @@ -639,7 +653,9 @@ export async function interactionHook(interaction: Interaction) { } case 'AUTO_FARMING_CONTRACT': { const response = await autoContract(await mUserFetch(user.id), options.channelID, user.id); - if (response) interactionReply(interaction, response); + if (response) { + return interactionReply(interaction, response); + } return; } case 'FARMING_CONTRACT_EASIER': { diff --git a/src/lib/util/handleCrateSpawns.ts b/src/lib/util/handleCrateSpawns.ts index 6f4cb033089..b0a9bb1815d 100644 --- a/src/lib/util/handleCrateSpawns.ts +++ b/src/lib/util/handleCrateSpawns.ts @@ -1,8 +1,11 @@ -import { bold } from 'discord.js'; -import { reduceNumByPercent, roll, Time } from 'e'; +import { Time, reduceNumByPercent, roll } from 'e'; import { Bank } from 'oldschooljs'; -export async function handleCrateSpawns(user: MUser, duration: number) { +import getOSItem from './getOSItem'; + +const crateItem = getOSItem('Birthday crate (s6)'); + +export function handleCrateSpawns(user: MUser, duration: number) { const accountAge = user.accountAgeInDays(); let dropratePerMinute = 50 * 60; if (accountAge) { @@ -12,7 +15,6 @@ export async function handleCrateSpawns(user: MUser, duration: number) { } } dropratePerMinute = Math.ceil(dropratePerMinute / 3); - dropratePerMinute = Math.ceil(dropratePerMinute / 5.5); dropratePerMinute = Math.ceil(dropratePerMinute / 2); if (user.isIronman) { dropratePerMinute = Math.ceil(dropratePerMinute / 3); @@ -21,13 +23,9 @@ export async function handleCrateSpawns(user: MUser, duration: number) { const loot = new Bank(); for (let i = 0; i < minutes; i++) { if (roll(dropratePerMinute)) { - loot.add('Easter crate (s5)'); + loot.add(crateItem); } } - if (loot.length > 0) { - await user.addItemsToBank({ items: loot, collectionLog: true }); - const str = bold(`You found ${loot}!`); - return str; - } - return null; + + return loot; } diff --git a/src/lib/util/handleMahojiConfirmation.ts b/src/lib/util/handleMahojiConfirmation.ts index 27e2daec00d..057a354f726 100644 --- a/src/lib/util/handleMahojiConfirmation.ts +++ b/src/lib/util/handleMahojiConfirmation.ts @@ -2,20 +2,20 @@ import { channelIsSendable } from '@oldschoolgg/toolkit'; import { ActionRowBuilder, ButtonBuilder, - ButtonInteraction, + type ButtonInteraction, ButtonStyle, - ChatInputCommandInteraction, - ComponentType, + type ChatInputCommandInteraction, + type ComponentType, InteractionResponseType, - MessageCreateOptions, + type MessageCreateOptions, Routes } from 'discord.js'; -import { noOp, Time } from 'e'; +import { Time, noOp } from 'e'; import { SILENT_ERROR } from '../constants'; import { deferInteraction, interactionReply } from './interactionReply'; -async function silentButtonAck(interaction: ButtonInteraction) { +export async function silentButtonAck(interaction: ButtonInteraction) { return globalClient.rest.post(Routes.interactionCallback(interaction.id, interaction.token), { body: { type: InteractionResponseType.DeferredMessageUpdate @@ -33,7 +33,7 @@ export async function handleMahojiConfirmation( await deferInteraction(interaction); const users = _users ?? [interaction.user.id]; - let confirmed: string[] = []; + const confirmed: string[] = []; const isConfirmed = () => confirmed.length === users.length; const confirmMessage = await channel.send({ ...(typeof str === 'string' ? { content: str } : str), diff --git a/src/lib/util/handleSpecialCoxLoot.ts b/src/lib/util/handleSpecialCoxLoot.ts index 8905e7cc956..249f02354b4 100644 --- a/src/lib/util/handleSpecialCoxLoot.ts +++ b/src/lib/util/handleSpecialCoxLoot.ts @@ -1,5 +1,5 @@ import { roll } from 'e'; -import { Bank } from 'oldschooljs'; +import type { Bank } from 'oldschooljs'; import { globalDroprates } from '../data/globalDroprates'; import { clAdjustedDroprate } from '../util'; diff --git a/src/lib/util/handleTripFinish.ts b/src/lib/util/handleTripFinish.ts index eb173ddf671..0afda35bc82 100644 --- a/src/lib/util/handleTripFinish.ts +++ b/src/lib/util/handleTripFinish.ts @@ -1,15 +1,20 @@ -import { mentionCommand } from '@oldschoolgg/toolkit'; +import { Stopwatch, channelIsSendable, mentionCommand } from '@oldschoolgg/toolkit'; import { activity_type_enum } from '@prisma/client'; -import { AttachmentBuilder, bold, ButtonBuilder, MessageCollector, MessageCreateOptions } from 'discord.js'; -import { notEmpty, randArrItem, randInt, roll, Time } from 'e'; +import { + type AttachmentBuilder, + type ButtonBuilder, + type MessageCollector, + type MessageCreateOptions, + bold +} from 'discord.js'; +import { Time, notEmpty, randArrItem, randInt, roll } from 'e'; import { Bank } from 'oldschooljs'; - import { alching } from '../../mahoji/commands/laps'; import { calculateBirdhouseDetails } from '../../mahoji/lib/abstracted_commands/birdhousesCommand'; import { canRunAutoContract } from '../../mahoji/lib/abstracted_commands/farmingContractCommand'; import { handleTriggerShootingStar } from '../../mahoji/lib/abstracted_commands/shootingStarsCommand'; import { updateClientGPTrackSetting, userStatsBankUpdate, userStatsUpdate } from '../../mahoji/mahojiSettings'; -import { chargePortentIfHasCharges, getAllPortentCharges, PortentID } from '../bso/divination'; +import { PortentID, chargePortentIfHasCharges, getAllPortentCharges } from '../bso/divination'; import { gods } from '../bso/divineDominion'; import { MysteryBoxes } from '../bsoOpenables'; import { ClueTiers } from '../clues/clueTiers'; @@ -18,7 +23,7 @@ import { combatAchievementTripEffect } from '../combat_achievements/combatAchiev import { BitField, COINS_ID, Emoji, PerkTier } from '../constants'; import { handleGrowablePetGrowth } from '../growablePets'; import { handlePassiveImplings } from '../implings'; -import { inventionBoosts, InventionID, inventionItemBoost } from '../invention/inventions'; +import { InventionID, inventionBoosts, inventionItemBoost } from '../invention/inventions'; import { mysteriousStepData } from '../mysteryTrail'; import { triggerRandomEvent } from '../randomEvents'; import { RuneTable, WilvusTable, WoodTable } from '../simulation/seedTable'; @@ -26,8 +31,8 @@ import { DougTable, PekyTable } from '../simulation/sharedTables'; import { calculateZygomiteLoot } from '../skilling/skills/farming/zygomites'; import { SkillsEnum } from '../skilling/types'; import { getUsersCurrentSlayerInfo } from '../slayer/slayerUtil'; -import { ActivityTaskData } from '../types/minions'; -import { channelIsSendable, makeComponents, perHourChance, toKMB } from '../util'; +import type { ActivityTaskData } from '../types/minions'; +import { makeComponents, toKMB } from '../util'; import { mahojiChatHead } from './chatHeadImage'; import { makeAutoContractButton, @@ -41,10 +46,11 @@ import { import { handleCrateSpawns } from './handleCrateSpawns'; import itemID from './itemID'; import { logError } from './logError'; +import { perHourChance } from './smallUtils'; import { updateBankSetting } from './updateBankSetting'; import { sendToChannelID } from './webhook'; -export const collectors = new Map(); +const collectors = new Map(); const activitiesToTrackAsPVMGPSource: activity_type_enum[] = [ 'GroupMonsterKilling', @@ -60,21 +66,29 @@ interface TripFinishEffectOptions { messages: string[]; portents?: Awaited>; } + +type TripEffectReturn = { + itemsToAddWithCL?: Bank; + itemsToRemove?: Bank; +}; + export interface TripFinishEffect { name: string; - fn: (options: TripFinishEffectOptions) => unknown; + // biome-ignore lint/suspicious/noConfusingVoidType: + fn: (options: TripFinishEffectOptions) => Promise; } const tripFinishEffects: TripFinishEffect[] = [ { name: 'Track GP Analytics', - fn: ({ data, loot }) => { + fn: async ({ data, loot }) => { if (loot && activitiesToTrackAsPVMGPSource.includes(data.type)) { const GP = loot.amount(COINS_ID); if (typeof GP === 'number') { - updateClientGPTrackSetting('gp_pvm', GP); + await updateClientGPTrackSetting('gp_pvm', GP); } } + return {}; } }, { @@ -84,9 +98,12 @@ const tripFinishEffects: TripFinishEffect[] = [ if (imp && imp.bank.length > 0) { const many = imp.bank.length > 1; messages.push(`Caught ${many ? 'some' : 'an'} impling${many ? 's' : ''}, you received: ${imp.bank}`); - userStatsBankUpdate(user.id, 'passive_implings_bank', imp.bank); - await transactItems({ userID: user.id, itemsToAdd: imp.bank, collectionLog: true }); + await userStatsBankUpdate(user, 'passive_implings_bank', imp.bank); + return { + itemsToAddWithCL: imp.bank + }; } + return {}; } }, { @@ -98,7 +115,7 @@ const tripFinishEffects: TripFinishEffect[] = [ { name: 'Random Events', fn: async ({ data, messages, user }) => { - await triggerRandomEvent(user, data.type, data.duration, messages); + return triggerRandomEvent(user, data.type, data.duration, messages); } }, { @@ -115,9 +132,14 @@ const tripFinishEffects: TripFinishEffect[] = [ const otherLoot = new Bank().add(MysteryBoxes.roll()); const bonusLoot = new Bank().add(loot).add(otherLoot); messages.push(`<:mysterybox:680783258488799277> **You received 2x loot and ${otherLoot}.**`); - userStatsBankUpdate(user.id, 'doubled_loot_bank', bonusLoot); - await user.addItemsToBank({ items: bonusLoot, collectionLog: true }); - updateBankSetting('trip_doubling_loot', bonusLoot); + + await Promise.all([ + userStatsBankUpdate(user.id, 'doubled_loot_bank', bonusLoot), + updateBankSetting('trip_doubling_loot', bonusLoot) + ]); + return { + itemsToAddWithCL: bonusLoot + }; } } }, @@ -127,7 +149,7 @@ const tripFinishEffects: TripFinishEffect[] = [ const pet = user.user.minion_equippedPet; const minutes = Math.floor(data.duration / Time.Minute); if (minutes < 5) return; - let bonusLoot = new Bank(); + const bonusLoot = new Bank(); switch (pet) { case itemID('Peky'): { for (let i = 0; i < minutes; i++) { @@ -142,7 +164,7 @@ const tripFinishEffects: TripFinishEffect[] = [ break; } case itemID('Obis'): { - let rolls = minutes / 3; + const rolls = minutes / 3; for (let i = 0; i < rolls; i++) { bonusLoot.add(RuneTable.roll()); } @@ -153,7 +175,7 @@ const tripFinishEffects: TripFinishEffect[] = [ break; } case itemID('Brock'): { - let rolls = minutes / 3; + const rolls = minutes / 3; for (let i = 0; i < rolls; i++) { bonusLoot.add(WoodTable.roll()); } @@ -164,7 +186,7 @@ const tripFinishEffects: TripFinishEffect[] = [ break; } case itemID('Wilvus'): { - let rolls = minutes / 6; + const rolls = minutes / 6; for (let i = 0; i < rolls; i++) { bonusLoot.add(WilvusTable.roll()); } @@ -207,9 +229,10 @@ const tripFinishEffects: TripFinishEffect[] = [ default: { } } - if (bonusLoot.length > 0) { - await user.addItemsToBank({ items: bonusLoot, collectionLog: true }); - } + + return { + itemsToAddWithCL: bonusLoot + }; } }, { @@ -230,15 +253,11 @@ const tripFinishEffects: TripFinishEffect[] = [ `Your Voidling couldn't do any alching because you don't own ${alchResult.bankToRemove}.` ); } - await user.transactItems({ - itemsToRemove: alchResult.bankToRemove, - itemsToAdd: alchResult.bankToAdd, - collectionLog: true - }); - updateBankSetting('magic_cost_bank', alchResult.bankToRemove); - - updateClientGPTrackSetting('gp_alch', alchResult.bankToAdd.amount('Coins')); + await Promise.all([ + updateBankSetting('magic_cost_bank', alchResult.bankToRemove), + updateClientGPTrackSetting('gp_alch', alchResult.bankToAdd.amount('Coins')) + ]); messages.push( `<:Voidling:886284972380545034> ${alchResult.maxCasts}x ${ alchResult.itemToAlch.name @@ -248,6 +267,10 @@ const tripFinishEffects: TripFinishEffect[] = [ : '' }${user.hasEquipped('Magic master cape') ? '<:Magicmastercape:1115026341314703492>⏫' : ''}` ); + return { + itemsToAddWithCL: alchResult.bankToAdd, + itemsToRemove: alchResult.bankToRemove + }; } else if (user.favAlchs(Time.Minute * 30).length !== 0) { messages.push( "Your Voidling didn't alch anything because you either don't have any nature runes or fire runes." @@ -269,11 +292,11 @@ const tripFinishEffects: TripFinishEffect[] = [ data.duration, user.skillLevel(SkillsEnum.Agility) ); - userStatsUpdate(user.id, () => ({ + await userStatsUpdate(user.id, { silverhawk_boots_passive_xp: { increment: xpToReceive } - })); + }); await user.addXP({ skillName: SkillsEnum.Agility, amount: xpToReceive, @@ -287,7 +310,7 @@ const tripFinishEffects: TripFinishEffect[] = [ }, { name: 'Message in a Bottle', - fn: async ({ data, messages, user }) => { + fn: async ({ data, messages }) => { const underwaterTrips: activity_type_enum[] = [ activity_type_enum.UnderwaterAgilityThieving, activity_type_enum.DepthsOfAtlantis @@ -296,24 +319,29 @@ const tripFinishEffects: TripFinishEffect[] = [ if (!roll(500)) return; messages.push('You found a message in a bottle!'); const bottleLoot = new Bank().add('Message in a bottle'); - await user.addItemsToBank({ items: bottleLoot, collectionLog: true }); + return { + itemsToAddWithCL: bottleLoot + }; } }, { name: 'Crate Spawns', fn: async ({ data, messages, user }) => { - const crateRes = await handleCrateSpawns(user, data.duration); - if (crateRes !== null) { - messages.push(crateRes); + const crateRes = handleCrateSpawns(user, data.duration); + if (crateRes && crateRes.length > 0) { + messages.push(bold(`You found ${crateRes}!`)); + return { + itemsToAddWithCL: crateRes + }; } } }, { name: 'God Favour', fn: async ({ data, user }) => { - if (!('monsterID' in data)) return; + if (!('mi' in data)) return; if (data.type !== 'MonsterKilling') return; - const favourableGod = gods.find(g => g.friendlyMonsters.includes(data.monsterID as number)); + const favourableGod = gods.find(g => g.friendlyMonsters.includes(data.mi as number)); if (!favourableGod) return; const unfavorableGods = gods.filter(g => g.name !== favourableGod.name); await user.addToGodFavour( @@ -340,7 +368,7 @@ const tripFinishEffects: TripFinishEffect[] = [ if (user.cl.has(stepData.loot)) return; await user.addItemsToBank({ items: stepData.loot, collectionLog: true }); } - if (previousStepData && previousStepData.clueItem && user.owns(previousStepData.clueItem.id)) { + if (previousStepData?.clueItem && user.owns(previousStepData.clueItem.id)) { await user.removeItemsFromBank(new Bank().add(previousStepData.clueItem.id)); } if (nextStep) { @@ -408,10 +436,12 @@ const tripFinishEffects: TripFinishEffect[] = [ charges: eggsReceived }); if (chargeResult.didCharge) { - await user.addItemsToBank({ items: loot, collectionLog: true }); messages.push( `You received ${loot}, your Rebirth portent has ${chargeResult.portent.charges_remaining}x charges remaining.` ); + return { + itemsToAddWithCL: loot + }; } } }, @@ -430,17 +460,17 @@ const tripFinishEffects: TripFinishEffect[] = [ console.error(`User ${user.id} doesn't ML ${cost.toString()}`); return; } - await user.transactItems({ - itemsToRemove: cost, - itemsToAdd: loot, - collectionLog: true - }); if (cost.length > 0 && loot.length === 0) { messages.push(`<:moonlightMutator:1220590471613513780> Mutated ${cost}, but all died`); } else if (loot.length > 0) { messages.push(`<:moonlightMutator:1220590471613513780> Mutated ${cost}; ${loot} survived`); } + + return { + itemsToAddWithCL: loot, + itemsToRemove: cost + }; } } } @@ -458,14 +488,33 @@ export async function handleTripFinish( ) { const message = typeof _message === 'string' ? { content: _message } : _message; if (attachment) { - !message.files ? (message.files = [attachment]) : message.files.push(attachment); + if (!message.files) { + message.files = [attachment]; + } else if (Array.isArray(message.files)) { + message.files.push(attachment); + } else { + console.warn(`Unexpected attachment type in handleTripFinish: ${typeof attachment}`); + } } const perkTier = user.perkTier(); const messages: string[] = []; - // TODO: This is called for *every* trip, even though it's used only for users with the rebirth portent. const portents = await getAllPortentCharges(user); - for (const effect of tripFinishEffects) await effect.fn({ data, user, loot, messages, portents }); + const itemsToAddWithCL = new Bank(); + const itemsToRemove = new Bank(); + for (const effect of tripFinishEffects) { + const stopwatch = new Stopwatch().start(); + const res = await effect.fn({ data, user, loot, messages, portents }); + if (res?.itemsToAddWithCL) itemsToAddWithCL.add(res.itemsToAddWithCL); + if (res?.itemsToRemove) itemsToRemove.add(res.itemsToRemove); + stopwatch.stop(); + if (stopwatch.duration > 500) { + debugLog(`Finished ${effect.name} trip effect for ${user.id} in ${stopwatch}`); + } + } + if (itemsToAddWithCL.length > 0 || itemsToRemove.length > 0) { + await user.transactItems({ itemsToAdd: itemsToAddWithCL, collectionLog: true, itemsToRemove }); + } const clueReceived = loot ? ClueTiers.filter(tier => loot.amount(tier.scrollID) > 0) : []; @@ -496,7 +545,7 @@ export async function handleTripFinish( if (casketReceived) components.push(makeOpenCasketButton(casketReceived)); if (perkTier > PerkTier.One) { components.push(...buildClueButtons(loot, perkTier, user)); - const birdHousedetails = await calculateBirdhouseDetails(user.id); + const birdHousedetails = await calculateBirdhouseDetails(user); if (birdHousedetails.isReady && !user.bitfield.includes(BitField.DisableBirdhouseRunButton)) components.push(makeBirdHouseTripButton()); @@ -534,11 +583,7 @@ export async function handleTripFinish( ]), head: 'mysteriousFigure' }); - if (message.files) { - message.files.push(...img.files); - } else { - message.files = img.files; - } + message.files = img.files; const loot = new Bank().add('Mysterious clue (1)'); await user.addItemsToBank({ items: loot, collectionLog: true }); if (user.user.bso_mystery_trail_current_step_id === null) { diff --git a/src/lib/util/interactionHelpers.ts b/src/lib/util/interactionHelpers.ts deleted file mode 100644 index f9c899aa055..00000000000 --- a/src/lib/util/interactionHelpers.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { createHash } from 'crypto'; -import { ButtonBuilder, ButtonStyle, ChatInputCommandInteraction, ComponentType } from 'discord.js'; -import { chunk, randInt, Time } from 'e'; - -import { deferInteraction, interactionReply } from './interactionReply'; - -export async function interactionReplyGetDuration( - interaction: ChatInputCommandInteraction, - prompt: string, - ...durations: { display: string; duration: number }[] -) { - await deferInteraction(interaction); - const unique = createHash('sha256') - .update(String(Date.now()) + String(randInt(10_000, 99_999))) - .digest('hex') - .slice(2, 12); - let x = 0; - const buttons = durations.map(d => ({ label: d.display, customId: `${unique}_DUR_BUTTON_${x++}` })); - buttons.push({ label: 'Cancel', customId: `${unique}_DUR_BUTTON_${x}` }); - const components = makePlainButtons(...buttons); - - const response = await interactionReply(interaction, { content: prompt, components }); - - if (response === undefined) return false; - try { - const selection = await response.awaitMessageComponent({ - filter: i => i.user.id === interaction.user.id, - time: 15 * Time.Second - }); - const id = Number(selection.customId.split('_')[3]); - if (durations[id]) { - await interaction.editReply({ content: `Selected: ${durations[id].display}`, components: [] }); - return durations[id]; - } - await interaction.editReply({ content: 'Cancelled.', components: [] }); - return false; - } catch (e) { - await interaction.editReply({ content: 'Did not choose a duration in time.', components: [] }); - return false; - } -} - -export function makePlainButtons(...buttons: { label: string; customId: string }[]) { - const components: ButtonBuilder[] = []; - for (let i = 0; i < buttons.length; i++) { - components.push( - new ButtonBuilder({ label: buttons[i].label, customId: buttons[i].customId, style: ButtonStyle.Secondary }) - ); - } - return chunk(components, 5).map(i => ({ components: i, type: ComponentType.ActionRow })); -} diff --git a/src/lib/util/interactionReply.ts b/src/lib/util/interactionReply.ts index f32c6a551de..4e001c1db89 100644 --- a/src/lib/util/interactionReply.ts +++ b/src/lib/util/interactionReply.ts @@ -1,44 +1,59 @@ -import { UserError } from '@oldschoolgg/toolkit/dist/lib/UserError'; -import { +import { UserError } from '@oldschoolgg/toolkit'; +import type { ButtonInteraction, ChatInputCommandInteraction, - DiscordAPIError, Interaction, InteractionReplyOptions, InteractionResponse, Message, - RepliableInteraction + RepliableInteraction, + StringSelectMenuInteraction } from 'discord.js'; +import { DiscordAPIError } from 'discord.js'; import { SILENT_ERROR } from '../constants'; import { logErrorForInteraction } from './logError'; export async function interactionReply(interaction: RepliableInteraction, response: string | InteractionReplyOptions) { let i: Promise | Promise | undefined = undefined; + let method = ''; + if (interaction.replied) { + method = 'followUp'; i = interaction.followUp(response); } else if (interaction.deferred) { + method = 'editReply'; i = interaction.editReply(response); } else { + method = 'reply'; i = interaction.reply(response); } try { - await i; - return i; + const result = await i; + return result; } catch (e: any) { if (e instanceof DiscordAPIError && e.code !== 10_008) { // 10_008 is unknown message, e.g. if someone deletes the message before it's replied to. - logErrorForInteraction(e, interaction); + logErrorForInteraction(e, interaction, { method, response: JSON.stringify(response).slice(0, 50) }); } return undefined; } } -export function deferInteraction(interaction: ButtonInteraction | ChatInputCommandInteraction) { - if (!interaction.deferred) { - const promise = interaction.deferReply(); - interaction.deferred = true; - return promise; +const wasDeferred = new Set(); + +export async function deferInteraction( + interaction: ButtonInteraction | ChatInputCommandInteraction | StringSelectMenuInteraction, + ephemeral = false +) { + if (wasDeferred.size > 1000) wasDeferred.clear(); + if (!interaction.deferred && !wasDeferred.has(interaction.id)) { + wasDeferred.add(interaction.id); + try { + await interaction.deferReply({ ephemeral }); + } catch (err) { + logErrorForInteraction(err, interaction); + } } } diff --git a/src/lib/util/itemID.ts b/src/lib/util/itemID.ts index 4a43d78c8f8..061ee2c6be9 100644 --- a/src/lib/util/itemID.ts +++ b/src/lib/util/itemID.ts @@ -2,6 +2,5 @@ import getOSItem from './getOSItem'; export default function itemID(name: string) { const osItem = getOSItem(name); - return osItem.id; } diff --git a/src/lib/util/linkedAccountsUtil.ts b/src/lib/util/linkedAccountsUtil.ts deleted file mode 100644 index 3d3fb9f5ac9..00000000000 --- a/src/lib/util/linkedAccountsUtil.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { User } from '@prisma/client'; - -import { mahojiUsersSettingsFetch } from '../../mahoji/mahojiSettings'; -import { MUserClass } from '../MUser'; -import { prisma } from '../settings/prisma'; - -async function syncLinkedAccountPerks(user: MUser) { - let main = user.user.main_account; - const allAccounts: string[] = [...user.user.ironman_alts]; - if (main) { - allAccounts.push(main); - } - const allUsers = await Promise.all( - allAccounts.map(a => - mahojiUsersSettingsFetch(a, { - id: true, - premium_balance_tier: true, - premium_balance_expiry_date: true, - bitfield: true, - ironman_alts: true, - main_account: true, - minion_ironman: true - }) - ) - ); - allUsers.map(u => new MUserClass(u as User)); -} - -export async function syncLinkedAccounts() { - const users = await prisma.user.findMany({ - where: { - ironman_alts: { - isEmpty: false - } - }, - select: { - id: true, - ironman_alts: true, - premium_balance_tier: true, - premium_balance_expiry_date: true, - bitfield: true - } - }); - for (const u of users) { - const mUser = new MUserClass(u as User); - await syncLinkedAccountPerks(mUser); - } -} diff --git a/src/lib/util/logError.ts b/src/lib/util/logError.ts index aee08222e2b..5917b1c75e6 100644 --- a/src/lib/util/logError.ts +++ b/src/lib/util/logError.ts @@ -1,9 +1,10 @@ +import { convertAPIOptionsToCommandOptions, deepMerge } from '@oldschoolgg/toolkit'; import { captureException } from '@sentry/node'; -import { Interaction } from 'discord.js'; -import { convertAPIOptionsToCommandOptions } from 'mahoji/dist/lib/util'; +import type { Interaction } from 'discord.js'; +import { isObject } from 'e'; import { production } from '../../config'; -import getOSItem from './getOSItem'; +import { globalConfig } from '../constants'; export function assert(condition: boolean, desc?: string, context?: Record) { if (!condition) { @@ -14,29 +15,41 @@ export function assert(condition: boolean, desc?: string, context?: Record, extra?: Record) { - console.error(context, err); - if (production) { + const metaInfo = deepMerge(context ?? {}, extra ?? {}); + debugLog(`${(err as any)?.message ?? JSON.stringify(err)}`, { + type: 'ERROR', + raw: JSON.stringify(err), + metaInfo: JSON.stringify(metaInfo) + }); + if (globalConfig.isProduction) { captureException(err, { tags: context, - extra + extra: metaInfo }); } else { console.error(err); - console.log(context); - console.log(extra); + console.log(metaInfo); } } -export function logErrorForInteraction(err: Error | unknown, interaction: Interaction) { +export function logErrorForInteraction( + err: Error | unknown, + interaction: Interaction, + extraContext?: Record +) { const context: Record = { user_id: interaction.user.id, channel_id: interaction.channelId, guild_id: interaction.guildId, interaction_id: interaction.id, - interaction_type: interaction.type + interaction_type: interaction.type, + ...extraContext, + interaction_created_at: interaction.createdTimestamp, + current_timestamp: Date.now(), + difference_ms: Date.now() - interaction.createdTimestamp, + was_deferred: interaction.isRepliable() ? interaction.deferred : 'N/A' }; if (interaction.isChatInputCommand()) { context.options = JSON.stringify( @@ -47,5 +60,12 @@ export function logErrorForInteraction(err: Error | unknown, interaction: Intera context.button_id = interaction.customId; } + if ('rawError' in interaction) { + const _err = err as any; + if ('requestBody' in _err && isObject(_err.requestBody)) { + context.request_body = JSON.stringify(_err.requestBody); + } + } + logError(err, context); } diff --git a/src/lib/util/logger.ts b/src/lib/util/logger.ts index e7b3d91dfa5..edc78d05f47 100644 --- a/src/lib/util/logger.ts +++ b/src/lib/util/logger.ts @@ -1,6 +1,6 @@ import SonicBoom from 'sonic-boom'; -import { BOT_TYPE } from '../constants'; +import { BOT_TYPE_LOWERCASE, globalConfig } from '../constants'; const today = new Date(); const year = today.getFullYear(); @@ -8,24 +8,16 @@ const month = (today.getMonth() + 1).toString().padStart(2, '0'); const day = today.getDate().toString().padStart(2, '0'); const formattedDate = `${year}-${month}-${day}`; -export const LOG_FILE_NAME = `./logs/${formattedDate}-${today.getHours()}-${today.getMinutes()}-${BOT_TYPE}-debug-logs.log`; +const LOG_FILE_NAME = globalConfig.isProduction + ? `../logs/${BOT_TYPE_LOWERCASE}.debug.log` + : `./logs/${formattedDate}-${today.getHours()}-${today.getMinutes()}-debug-logs.log`; export const sonicBoom = new SonicBoom({ fd: LOG_FILE_NAME, mkdir: true, - minLength: 4096, sync: false }); -const sqlLogger = new SonicBoom({ - fd: './logs/queries.sql', - mkdir: true, - minLength: 0, - sync: true -}); - -export const sqlLog = (str: string) => sqlLogger.write(`${str}\n`); - interface LogContext { type?: string; [key: string]: unknown; @@ -37,14 +29,7 @@ function _debugLog(str: string, context: LogContext = {}) { sonicBoom.write(`${JSON.stringify(o)}\n`); } declare global { - const debugLog: typeof _debugLog; -} -declare global { - namespace NodeJS { - interface Global { - debugLog: typeof _debugLog; - } - } + var debugLog: typeof _debugLog; } global.debugLog = _debugLog; diff --git a/src/lib/util/makeBadgeString.ts b/src/lib/util/makeBadgeString.ts new file mode 100644 index 00000000000..5a6598d358c --- /dev/null +++ b/src/lib/util/makeBadgeString.ts @@ -0,0 +1,9 @@ +import { Emoji, badges } from '../constants'; + +export function makeBadgeString(badgeIDs: number[] | null | undefined, isIronman: boolean) { + const rawBadges: string[] = (badgeIDs ?? []).map(num => badges[num]); + if (isIronman) { + rawBadges.push(Emoji.Ironman); + } + return rawBadges.join(' ').trim(); +} diff --git a/src/lib/util/makeBankImage.ts b/src/lib/util/makeBankImage.ts index eed2a272f6d..45b73e01cf0 100644 --- a/src/lib/util/makeBankImage.ts +++ b/src/lib/util/makeBankImage.ts @@ -1,7 +1,7 @@ -import { Bank } from 'oldschooljs'; +import type { Bank } from 'oldschooljs'; -import { BankFlag } from '../bankImage'; -import { Flags } from '../minions/types'; +import type { BankFlag } from '../bankImage'; +import type { Flags } from '../minions/types'; interface MakeBankImageOptions { bank: Bank; @@ -25,7 +25,7 @@ export async function makeBankImage({ flags = {}, mahojiFlags = [] }: MakeBankImageOptions) { - let realFlags: Flags = { ...flags, background: background ?? 1, nocache: 1 }; + const realFlags: Flags = { ...flags, background: background ?? 1, nocache: 1 }; if (showNewCL || previousCL !== undefined) realFlags.showNewCL = 1; const { image, isTransparent } = await bankImageGenerator.generateBankImage({ bank, diff --git a/src/lib/util/migrateUser.ts b/src/lib/util/migrateUser.ts index e522d32afae..16ec3a3befa 100644 --- a/src/lib/util/migrateUser.ts +++ b/src/lib/util/migrateUser.ts @@ -1,17 +1,21 @@ -import { UserError } from '@oldschoolgg/toolkit/dist/lib/UserError'; +import { UserError } from '@oldschoolgg/toolkit'; import { cancelUsersListings } from '../../mahoji/lib/abstracted_commands/cancelGEListingCommand'; -import { prisma } from '../settings/prisma'; + import { logError } from './logError'; export async function migrateUser(_source: string | MUser, _dest: string | MUser): Promise { const sourceUser = typeof _source === 'string' ? await mUserFetch(_source) : _source; const destUser = typeof _dest === 'string' ? await mUserFetch(_dest) : _dest; + if (sourceUser.id === destUser.id) { + throw new UserError('Destination user cannot be the same as the source!'); + } + // First check for + cancel active GE Listings: await Promise.all([cancelUsersListings(sourceUser), cancelUsersListings(destUser)]); - let transactions = []; + const transactions = []; transactions.push(prisma.$executeRaw`SET CONSTRAINTS ALL DEFERRED`); // Delete Queries @@ -219,7 +223,10 @@ export async function migrateUser(_source: string | MUser, _dest: string | MUser const robochimpTx = []; robochimpTx.push(roboChimpClient.user.deleteMany({ where: { id: BigInt(destUser.id) } })); robochimpTx.push( - roboChimpClient.user.updateMany({ where: { id: BigInt(sourceUser.id) }, data: { id: BigInt(destUser.id) } }) + roboChimpClient.user.updateMany({ + where: { id: BigInt(sourceUser.id) }, + data: { id: BigInt(destUser.id) } + }) ); // Set the migrated_user_id value to prevent duplicate robochimp migrations. robochimpTx.push( diff --git a/src/lib/util/minionStatsEmbed.ts b/src/lib/util/minionStatsEmbed.ts index a0ffebc7b8a..24bb83ecaa0 100644 --- a/src/lib/util/minionStatsEmbed.ts +++ b/src/lib/util/minionStatsEmbed.ts @@ -2,7 +2,7 @@ import { toTitleCase } from '@oldschoolgg/toolkit'; import { EmbedBuilder } from 'discord.js'; import { shuffleArr, sumArr } from 'e'; import { Bank } from 'oldschooljs'; -import { SkillsScore } from 'oldschooljs/dist/meta/types'; +import type { SkillsScore } from 'oldschooljs/dist/meta/types'; import { convertXPtoLVL, toKMB } from 'oldschooljs/dist/util'; import { ClueTiers } from '../clues/clueTiers'; @@ -14,7 +14,7 @@ import { effectiveMonsters } from '../minions/data/killableMonsters'; import { MALEDICT_MORTIMER_ID } from '../simulation/maledictMortimer'; import { courses } from '../skilling/skills/agility'; import creatures from '../skilling/skills/hunter/creatures'; -import { ItemBank, Skills } from '../types'; +import type { ItemBank, Skills } from '../types'; import { logError } from './logError'; export async function minionStatsEmbed(user: MUser): Promise { @@ -126,7 +126,7 @@ export async function minionStatsEmbed(user: MUser): Promise { name: '<:Clue_scroll:365003979840552960> Clue Scores', value: clueEntries .map(([id, qty]) => { - const clueTier = ClueTiers.find(t => t.id === parseInt(id)); + const clueTier = ClueTiers.find(t => t.id === Number.parseInt(id)); if (!clueTier) { logError(`No clueTier: ${id}`); return; @@ -167,7 +167,7 @@ export async function minionStatsEmbed(user: MUser): Promise { const lapCounts = Object.entries(userStats.laps_scores as ItemBank).sort((a, b) => a[1] - b[1]); if (lapCounts.length > 0) { const [id, score] = lapCounts[0]; - const res = courses.find(c => c.id === parseInt(id))!; + const res = courses.find(c => c.id === Number.parseInt(id))!; if (res) { otherStats.push([`${res.name} Laps`, score]); } @@ -178,7 +178,7 @@ export async function minionStatsEmbed(user: MUser): Promise { .filter(i => ![MALEDICT_MORTIMER_ID].includes(Number(i[0]))); if (monsterScores.length > 0) { const [id, score] = monsterScores[0]; - const res = effectiveMonsters.find(c => c.id === parseInt(id))!; + const res = effectiveMonsters.find(c => c.id === Number.parseInt(id))!; if (!res) { logError(`No monster found with id ${id} for stats embed`); } else { @@ -189,7 +189,7 @@ export async function minionStatsEmbed(user: MUser): Promise { const hunterScores = Object.entries(userStats.creature_scores as ItemBank).sort((a, b) => a[1] - b[1]); if (hunterScores.length > 0) { const [id, score] = hunterScores[0]; - const res = creatures.find(c => c.id === parseInt(id))!; + const res = creatures.find(c => c.id === Number.parseInt(id))!; if (res) { otherStats.push([`${res.name}'s Caught`, score]); } diff --git a/src/lib/util/minionStatus.ts b/src/lib/util/minionStatus.ts index 7a1be1e1896..d043de26489 100644 --- a/src/lib/util/minionStatus.ts +++ b/src/lib/util/minionStatus.ts @@ -1,10 +1,9 @@ -import { formatOrdinal, toTitleCase } from '@oldschoolgg/toolkit'; +import { formatDuration, formatOrdinal, toTitleCase } from '@oldschoolgg/toolkit'; import { increaseNumByPercent, reduceNumByPercent } from 'e'; import { SkillsEnum } from 'oldschooljs/dist/constants'; -import { collectables } from '../../mahoji/lib/abstracted_commands/collectCommand'; -import { quests } from '../../mahoji/lib/abstracted_commands/questCommand'; import { shades, shadesLogs } from '../../mahoji/lib/abstracted_commands/shadesOfMortonCommand'; +import { collectables } from '../../mahoji/lib/collectables'; import { bossEvents } from '../bossEvents'; import { divinationEnergies, memoryHarvestTypes } from '../bso/divination'; import { ClueTiers } from '../clues/clueTiers'; @@ -12,6 +11,7 @@ import { Emoji } from '../constants'; import { fishingLocations } from '../fishingContest'; import killableMonsters from '../minions/data/killableMonsters'; import { Planks } from '../minions/data/planks'; +import { quests } from '../minions/data/quests'; import Agility from '../skilling/skills/agility'; import Constructables from '../skilling/skills/construction/constructables'; import Cooking from '../skilling/skills/cooking/cooking'; @@ -30,7 +30,7 @@ import Runecraft from '../skilling/skills/runecraft'; import Smithing from '../skilling/skills/smithing'; import { stealables } from '../skilling/skills/thieving/stealables'; import Woodcutting from '../skilling/skills/woodcutting/woodcutting'; -import { +import type { ActivityTaskOptionsWithQuantity, AgilityActivityTaskOptions, AlchingActivityTaskOptions, @@ -40,13 +40,14 @@ import { CastingActivityTaskOptions, ClueActivityTaskOptions, CollectingOptions, + ColoTaskOptions, ConstructionActivityTaskOptions, CookingActivityTaskOptions, CraftingActivityTaskOptions, CutLeapingFishActivityTaskOptions, + DOAOptions, DarkAltarOptions, DisassembleTaskOptions, - DOAOptions, DungeoneeringOptions, EnchantingActivityTaskOptions, FarmingActivityTaskOptions, @@ -83,14 +84,14 @@ import { SmeltingActivityTaskOptions, SmithingActivityTaskOptions, SpecificQuestOptions, + TOAOptions, TheatreOfBloodTaskOptions, TiaraRunecraftActivityTaskOptions, TinkeringWorkshopOptions, - TOAOptions, WoodcuttingActivityTaskOptions, ZalcanoActivityTaskOptions } from '../types/minions'; -import { formatDuration, itemNameFromID, randomVariation, stringMatches } from '../util'; +import { itemNameFromID, randomVariation, stringMatches } from '../util'; import { getActivityOfUser } from './minionIsBusy'; export function minionStatus(user: MUser) { @@ -106,16 +107,16 @@ export function minionStatus(user: MUser) { switch (currentTask.type) { case 'MonsterKilling': { const data = currentTask as MonsterActivityTaskOptions; - const monster = killableMonsters.find(mon => mon.id === data.monsterID); + const monster = killableMonsters.find(mon => mon.id === data.mi); - return `${name} is currently killing ${data.quantity}x ${monster!.name}. ${formattedDuration}`; + return `${name} is currently killing ${data.q}x ${monster?.name}. ${formattedDuration}`; } case 'GroupMonsterKilling': { const data = currentTask as GroupMonsterActivityTaskOptions; - const monster = killableMonsters.find(mon => mon.id === data.monsterID); + const monster = killableMonsters.find(mon => mon.id === data.mi); - return `${name} is currently killing ${data.quantity}x ${monster!.name} with a party of ${ + return `${name} is currently killing ${data.q}x ${monster?.name} with a party of ${ data.users.length }. ${formattedDuration}`; } @@ -123,16 +124,16 @@ export function minionStatus(user: MUser) { case 'ClueCompletion': { const data = currentTask as ClueActivityTaskOptions; - const clueTier = ClueTiers.find(tier => tier.id === data.clueID); + const clueTier = ClueTiers.find(tier => tier.id === data.ci); - return `${name} is currently completing ${data.quantity}x ${clueTier!.name} clues. ${formattedDuration}`; + return `${name} is currently completing ${data.q}x ${clueTier?.name} clues. ${formattedDuration}`; } case 'Crafting': { const data = currentTask as CraftingActivityTaskOptions; const craftable = Crafting.Craftables.find(item => item.id === data.craftableID); - return `${name} is currently crafting ${data.quantity}x ${craftable!.name}. ${formattedDuration} Your ${ + return `${name} is currently crafting ${data.quantity}x ${craftable?.name}. ${formattedDuration} Your ${ Emoji.Crafting } Crafting level is ${user.skillLevel(SkillsEnum.Crafting)}`; } @@ -142,7 +143,7 @@ export function minionStatus(user: MUser) { const course = Agility.Courses.find(course => course.name === data.courseID); - return `${name} is currently running ${data.quantity}x ${course!.name} laps. ${formattedDuration} Your ${ + return `${name} is currently running ${data.quantity}x ${course?.name} laps. ${formattedDuration} Your ${ Emoji.Agility } Agility level is ${user.skillLevel(SkillsEnum.Agility)}`; } @@ -152,7 +153,7 @@ export function minionStatus(user: MUser) { const cookable = Cooking.Cookables.find(cookable => cookable.id === data.cookableID); - return `${name} is currently cooking ${data.quantity}x ${cookable!.name}. ${formattedDuration} Your ${ + return `${name} is currently cooking ${data.quantity}x ${cookable?.name}. ${formattedDuration} Your ${ Emoji.Cooking } Cooking level is ${user.skillLevel(SkillsEnum.Cooking)}`; } @@ -162,7 +163,7 @@ export function minionStatus(user: MUser) { const fish = Fishing.Fishes.find(fish => fish.id === data.fishID); - return `${name} is currently fishing ${data.quantity}x ${fish!.name}. ${formattedDuration} Your ${ + return `${name} is currently fishing ${data.quantity}x ${fish?.name}. ${formattedDuration} Your ${ Emoji.Fishing } Fishing level is ${user.skillLevel(SkillsEnum.Fishing)}`; } @@ -172,14 +173,14 @@ export function minionStatus(user: MUser) { const ore = Mining.Ores.find(ore => ore.id === data.oreID); - return `${name} is currently mining ${ore!.name}. ${ + return `${name} is currently mining ${ore?.name}. ${ data.fakeDurationMax === data.fakeDurationMin ? formattedDuration : `approximately ${formatDuration( randomVariation(reduceNumByPercent(durationRemaining, 25), 20) - )} **to** ${formatDuration( + )} **to** ${formatDuration( randomVariation(increaseNumByPercent(durationRemaining, 25), 20) - )} remaining.` + )} remaining.` } Your ${Emoji.Mining} Mining level is ${user.skillLevel(SkillsEnum.Mining)}`; } @@ -191,9 +192,9 @@ export function minionStatus(user: MUser) { ? formattedDuration : `approximately ${formatDuration( randomVariation(reduceNumByPercent(durationRemaining, 25), 20) - )} **to** ${formatDuration( + )} **to** ${formatDuration( randomVariation(increaseNumByPercent(durationRemaining, 25), 20) - )} remaining.` + )} remaining.` } Your ${Emoji.Mining} Mining level is ${user.skillLevel(SkillsEnum.Mining)}`; } @@ -202,7 +203,7 @@ export function minionStatus(user: MUser) { const bar = Smithing.Bars.find(bar => bar.id === data.barID); - return `${name} is currently smelting ${data.quantity}x ${bar!.name}. ${formattedDuration} Your ${ + return `${name} is currently smelting ${data.quantity}x ${bar?.name}. ${formattedDuration} Your ${ Emoji.Smithing } Smithing level is ${user.skillLevel(SkillsEnum.Smithing)}`; } @@ -212,7 +213,7 @@ export function minionStatus(user: MUser) { const SmithableItem = Smithing.SmithableItems.find(item => item.id === data.smithedBarID); - return `${name} is currently smithing ${data.quantity}x ${SmithableItem!.name}. ${formattedDuration} Your ${ + return `${name} is currently smithing ${data.quantity}x ${SmithableItem?.name}. ${formattedDuration} Your ${ Emoji.Smithing } Smithing level is ${user.skillLevel(SkillsEnum.Smithing)}`; } @@ -222,7 +223,7 @@ export function minionStatus(user: MUser) { const bones = Prayer.Bones.find(bones => bones.inputId === data.boneID); - return `${name} is currently offering ${data.quantity}x ${bones!.name}. ${formattedDuration} Your ${ + return `${name} is currently offering ${data.quantity}x ${bones?.name}. ${formattedDuration} Your ${ Emoji.Prayer } Prayer level is ${user.skillLevel(SkillsEnum.Prayer)}`; } @@ -232,7 +233,7 @@ export function minionStatus(user: MUser) { const bones = Prayer.Bones.find(bones => bones.inputId === data.boneID); - return `${name} is currently burying ${data.quantity}x ${bones!.name}. ${formattedDuration} Your ${ + return `${name} is currently burying ${data.quantity}x ${bones?.name}. ${formattedDuration} Your ${ Emoji.Prayer } Prayer level is ${user.skillLevel(SkillsEnum.Prayer)}`; } @@ -242,7 +243,7 @@ export function minionStatus(user: MUser) { const ashes = Prayer.Ashes.find(ashes => ashes.inputId === data.ashID); - return `${name} is currently scattering ${data.quantity}x ${ashes!.name}. ${formattedDuration} Your ${ + return `${name} is currently scattering ${data.quantity}x ${ashes?.name}. ${formattedDuration} Your ${ Emoji.Prayer } Prayer level is ${user.skillLevel(SkillsEnum.Prayer)}`; } @@ -252,7 +253,7 @@ export function minionStatus(user: MUser) { const burn = Firemaking.Burnables.find(burn => burn.inputLogs === data.burnableID); - return `${name} is currently lighting ${data.quantity}x ${burn!.name}. ${formattedDuration} Your ${ + return `${name} is currently lighting ${data.quantity}x ${burn?.name}. ${formattedDuration} Your ${ Emoji.Firemaking } Firemaking level is ${user.skillLevel(SkillsEnum.Firemaking)}`; } @@ -266,14 +267,14 @@ export function minionStatus(user: MUser) { const log = Woodcutting.Logs.find(log => log.id === data.logID); - return `${name} is currently chopping ${log!.name}. ${ + return `${name} is currently chopping ${log?.name}. ${ data.fakeDurationMax === data.fakeDurationMin ? formattedDuration : `approximately ${formatDuration( randomVariation(reduceNumByPercent(durationRemaining, 25), 20) - )} **to** ${formatDuration( + )} **to** ${formatDuration( randomVariation(increaseNumByPercent(durationRemaining, 25), 20) - )} remaining.` + )} remaining.` } Your ${Emoji.Woodcutting} Woodcutting level is ${user.skillLevel(SkillsEnum.Woodcutting)}`; } case 'Runecraft': { @@ -282,7 +283,7 @@ export function minionStatus(user: MUser) { const rune = Runecraft.Runes.find(_rune => _rune.id === data.runeID); return `${name} is currently turning ${data.essenceQuantity}x Essence into ${ - rune!.name + rune?.name }. ${formattedDuration} Your ${Emoji.Runecraft} Runecraft level is ${user.skillLevel( SkillsEnum.Runecraft )}`; @@ -292,7 +293,7 @@ export function minionStatus(user: MUser) { const data = currentTask as TiaraRunecraftActivityTaskOptions; const tiara = Runecraft.Tiaras.find(_tiara => _tiara.id === data.tiaraID); - return `${name} is currently crafting ${data.tiaraQuantity} ${tiara!.name}. ${formattedDuration} Your ${ + return `${name} is currently crafting ${data.tiaraQuantity} ${tiara?.name}. ${formattedDuration} Your ${ Emoji.Runecraft } Runecraft level is ${user.skillLevel(SkillsEnum.Runecraft)}`; } @@ -321,7 +322,7 @@ export function minionStatus(user: MUser) { const data = currentTask as HerbloreActivityTaskOptions; const mixable = Herblore.Mixables.find(i => i.item.id === data.mixableID); - return `${name} is currently mixing ${data.quantity}x ${mixable!.item.name}. ${formattedDuration} Your ${ + return `${name} is currently mixing ${data.quantity}x ${mixable?.item.name}. ${formattedDuration} Your ${ Emoji.Herblore } Herblore level is ${user.skillLevel(SkillsEnum.Herblore)}`; } @@ -330,11 +331,12 @@ export function minionStatus(user: MUser) { const barbarianFish = LeapingFish.find(item => item.item.id === data.id); return `${name} is currently cutting ${data.quantity}x ${ - barbarianFish!.item.name + barbarianFish?.item.name }. ${formattedDuration} Your ${Emoji.Cooking} Cooking level is ${user.skillLevel(SkillsEnum.Cooking)}`; } case 'Wintertodt': { - return `${name} is currently fighting the Wintertodt. ${formattedDuration}`; + const data = currentTask as ActivityTaskOptionsWithQuantity; + return `${name} is currently fighting Wintertodt ${data.quantity}x times. ${formattedDuration}`; } case 'Tempoross': { return `${name} is currently fighting Tempoross. ${formattedDuration}`; @@ -353,16 +355,16 @@ export function minionStatus(user: MUser) { const plants = Farming.Plants.find(plants => plants.name === data.plantsName); - return `${name} is currently farming ${data.quantity}x ${plants!.name}. ${formattedDuration} Your ${ + return `${name} is currently farming ${data.quantity}x ${plants?.name}. ${formattedDuration} Your ${ Emoji.Farming } Farming level is ${user.skillLevel(SkillsEnum.Farming)}.`; } case 'Sawmill': { const data = currentTask as SawmillActivityTaskOptions; - const plank = Planks.find(_plank => _plank.outputItem === data.plankID); + const plank = Planks.find(_plank => _plank.outputItem === data.plankID)!; return `${name} is currently creating ${data.plankQuantity}x ${itemNameFromID( - plank!.outputItem + plank.outputItem )}s. ${formattedDuration}`; } @@ -418,8 +420,8 @@ export function minionStatus(user: MUser) { case 'Pickpocket': { const data = currentTask as PickpocketActivityTaskOptions; const obj = stealables.find(_obj => _obj.id === data.monsterID); - return `${name} is currently ${obj!.type === 'pickpockable' ? 'pickpocketing' : 'stealing'} from ${ - obj!.name + return `${name} is currently ${obj?.type === 'pickpockable' ? 'pickpocketing' : 'stealing'} from ${ + obj?.name } ${data.quantity}x times. ${formattedDuration}`; } @@ -445,9 +447,9 @@ export function minionStatus(user: MUser) { stringMatches(alias, data.creatureName) || stringMatches(alias.split(' ')[0], data.creatureName) ) ); - let crystalImpling = creature?.name === 'Crystal impling'; + const crystalImpling = creature?.name === 'Crystal impling'; return `${name} is currently hunting ${ - crystalImpling ? creature!.name : `${data.quantity}x ${creature!.name}` + crystalImpling ? creature?.name : `${data.quantity}x ${creature?.name}` }. ${formattedDuration}`; } @@ -472,9 +474,9 @@ export function minionStatus(user: MUser) { case 'Butler': { const data = currentTask as ButlerActivityTaskOptions; - const plank = Planks.find(_plank => _plank.outputItem === data.plankID); + const plank = Planks.find(_plank => _plank.outputItem === data.plankID)!; return `${name} is currently creating ${data.plankQuantity}x ${itemNameFromID( - plank!.outputItem + plank.outputItem )}s. ${formattedDuration}`; } @@ -485,13 +487,13 @@ export function minionStatus(user: MUser) { case 'Enchanting': { const data = currentTask as EnchantingActivityTaskOptions; const enchantable = Enchantables.find(i => i.id === data.itemID); - return `${name} is currently enchanting ${data.quantity}x ${enchantable!.name}. ${formattedDuration}`; + return `${name} is currently enchanting ${data.quantity}x ${enchantable?.name}. ${formattedDuration}`; } case 'Casting': { const data = currentTask as CastingActivityTaskOptions; const spell = Castables.find(i => i.id === data.spellID); - return `${name} is currently casting ${data.quantity}x ${spell!.name}. ${formattedDuration}`; + return `${name} is currently casting ${data.quantity}x ${spell?.name}. ${formattedDuration}`; } case 'GloryCharging': { @@ -784,26 +786,37 @@ export function minionStatus(user: MUser) { )}.`; } case 'BirthdayCollectIngredients': { - return `${name} is currently collecting ingredients, the trip should take ${formatDuration( + return `${name} is currently collecting ingredients, the trip should take ${formatDuration(durationRemaining)}.`; + } + case 'CombatRing': { + return `${name} is currently fighting in the Combat Ring! The trip should take ${formatDuration( durationRemaining )}.`; } case 'SpecificQuest': { const data = currentTask as SpecificQuestOptions; return `${name} is currently doing the ${ - quests.find(i => i.id === data.questID)!.name + quests.find(i => i.id === data.questID)?.name }! The trip should take ${formatDuration(durationRemaining)}.`; } - case 'Mortimer': { + case 'Mortimer': return `${name} is currently fighting Maledict Mortimer! The trip should take ${formatDuration( durationRemaining )}.`; + case 'Colosseum': { + const data = currentTask as ColoTaskOptions; + const durationRemaining = data.finishDate - data.duration + data.fakeDuration - Date.now(); + + return `${name} is currently attempting the Colosseum, if they are successful, the trip should take ${formatDuration( + durationRemaining + )}.`; } - case 'GuthixianCache': { + case 'HalloweenEvent': + return `${name} is doing the Halloween event! The trip should take ${formatDuration(durationRemaining)}.`; + case 'GuthixianCache': return `${name} is currently participating in a Guthixian cache. The trip should take ${formatDuration( durationRemaining )}.`; - } case 'MemoryHarvest': { const data = currentTask as MemoryHarvestOptions; const energy = divinationEnergies.find(e => e.item.id === data.e)!; @@ -812,17 +825,14 @@ export function minionStatus(user: MUser) { method.name }. The trip should take ${formatDuration(durationRemaining)}.`; } - case 'TuraelsTrials': { + case 'TuraelsTrials': return `${name} is currently slaying monsters in Turaels Trials. The trip should take ${formatDuration( durationRemaining )}.`; - } case 'HalloweenMiniMinigame': case 'Easter': - case 'HalloweenEvent': case 'BlastFurnace': - case 'TrickOrTreat': { + case 'TrickOrTreat': throw new Error('Removed'); - } } } diff --git a/src/lib/util/minionUtils.ts b/src/lib/util/minionUtils.ts index df82d6df53f..3f009dc88ea 100644 --- a/src/lib/util/minionUtils.ts +++ b/src/lib/util/minionUtils.ts @@ -1,12 +1,13 @@ import { toTitleCase } from '@oldschoolgg/toolkit'; -import { BaseMessageOptions, escapeMarkdown, time } from 'discord.js'; +import type { BaseMessageOptions } from 'discord.js'; +import { escapeMarkdown, time } from 'discord.js'; import { Time } from 'e'; import { convertXPtoLVL } from 'oldschooljs/dist/util/util'; +import { resolveItems } from 'oldschooljs/dist/util/util'; import { Emoji } from '../constants'; -import { SkillsEnum } from '../skilling/types'; -import { Peak } from './../tickers'; -import resolveItems from './resolveItems'; +import type { SkillsEnum } from '../skilling/types'; +import type { Peak } from './../tickers'; export function skillLevel(user: MUser, skill: SkillsEnum) { const xp = Number(user.user[`skills_${skill}`]); @@ -81,7 +82,7 @@ export function minionName(user: MUser) { const prefix = isIronman ? Emoji.Ironman : ''; icon ??= Emoji.Minion; - let strPrefix = prefix ? `${prefix} ` : ''; + const strPrefix = prefix ? `${prefix} ` : ''; return name ? `${strPrefix}${icon} **${escapeMarkdown(name)}**` : `${strPrefix}${icon} Your minion`; } diff --git a/src/lib/util/parseStringBank.ts b/src/lib/util/parseStringBank.ts index b973bef564c..dacf987277e 100644 --- a/src/lib/util/parseStringBank.ts +++ b/src/lib/util/parseStringBank.ts @@ -1,7 +1,7 @@ -import { evalMathExpression } from '@oldschoolgg/toolkit/dist/util/expressionParser'; +import { evalMathExpression } from '@oldschoolgg/toolkit'; import { notEmpty } from 'e'; import { Bank, Items } from 'oldschooljs'; -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; import { itemNameMap } from 'oldschooljs/dist/structures/Items'; import { ONE_TRILLION } from '../constants'; @@ -19,7 +19,7 @@ export function parseQuantityAndItem(str = '', inputBank?: Bank): [Item[], numbe const split = str.split(' '); // If we're passed 2 numbers in a row, e.g. '1 1 coal', remove that number and recurse back. - if (!isNaN(Number(split[1])) && split.length > 2) { + if (!Number.isNaN(Number(split[1])) && split.length > 2) { split.splice(1, 1); return parseQuantityAndItem(split.join(' ')); } @@ -27,22 +27,22 @@ export function parseQuantityAndItem(str = '', inputBank?: Bank): [Item[], numbe let [potentialQty, ...potentialName] = split.length === 1 ? ['', split[0]] : split; if (isDeletedItemName(str)) return []; - if (!isNaN(Number(potentialQty)) && isDeletedItemName(potentialName.join(' '))) return []; + if (!Number.isNaN(Number(potentialQty)) && isDeletedItemName(potentialName.join(' '))) return []; - let lazyItemGet = Items.get(potentialName.join(' ')) ?? Items.get(Number(potentialName.join(' '))); + const lazyItemGet = Items.get(potentialName.join(' ')) ?? Items.get(Number(potentialName.join(' '))); if (str.includes('#') && lazyItemGet && inputBank) { potentialQty = potentialQty.replace('#', inputBank.amount(lazyItemGet.id).toString()); } let parsedQty: number | null = evalMathExpression(potentialQty.replace('x', '')); - if (parsedQty !== null && isNaN(parsedQty)) parsedQty = null; + if (parsedQty !== null && Number.isNaN(parsedQty)) parsedQty = null; const parsedName = parsedQty === null ? str : potentialName.join(' '); let osItems: Item[] = []; const nameAsInt = Number(parsedName); - if (!isNaN(nameAsInt)) { + if (!Number.isNaN(nameAsInt)) { const item = Items.get(nameAsInt); if (item) osItems.push(item); } else { @@ -54,7 +54,7 @@ export function parseQuantityAndItem(str = '', inputBank?: Bank): [Item[], numbe } if (osItems.length === 0) return []; - let quantity = floor(min(ONE_TRILLION, max(0, parsedQty ?? 0))); + const quantity = floor(min(ONE_TRILLION, max(0, parsedQty ?? 0))); return [osItems, quantity]; } @@ -66,10 +66,10 @@ export function parseStringBank(str = '', inputBank?: Bank, noDuplicateItems?: t .split(',') .filter(i => notEmpty(i) && i !== ''); if (split.length === 0) return []; - let items: [Item, number | undefined][] = []; + const items: [Item, number | undefined][] = []; const currentIDs = new Set(); for (let i = 0; i < split.length; i++) { - let [resItems, quantity] = parseQuantityAndItem(split[i], inputBank); + const [resItems, quantity] = parseQuantityAndItem(split[i], inputBank); if (resItems !== undefined) { for (const item of noDuplicateItems ? resItems.slice(0, 1) : resItems) { if (currentIDs.has(item.id)) continue; @@ -81,7 +81,7 @@ export function parseStringBank(str = '', inputBank?: Bank, noDuplicateItems?: t return items; } -export function parseBankFromFlags({ +function parseBankFromFlags({ bank, flags, excludeItems, @@ -95,7 +95,7 @@ export function parseBankFromFlags({ user?: MUser; }): Bank { const newBank = new Bank(); - const maxQuantity = Number(flags.qty) || Infinity; + const maxQuantity = Number(flags.qty) || Number.POSITIVE_INFINITY; // Add filterables const flagsKeys = Object.keys(flags); @@ -151,7 +151,7 @@ export function parseBank({ noDuplicateItems = undefined }: ParseBankOptions): Bank { if (inputStr) { - let _bank = new Bank(); + const _bank = new Bank(); const strItems = parseStringBank(inputStr, inputBank, noDuplicateItems); for (const [item, quantity] of strItems) { if (maxSize && _bank.length >= maxSize) break; @@ -160,8 +160,8 @@ export function parseBank({ !quantity ? inputBank?.amount(item.id) : inputBank === undefined - ? quantity - : Math.max(0, Math.min(quantity, inputBank.amount(item.id) ?? 1)) + ? quantity + : Math.max(0, Math.min(quantity, inputBank.amount(item.id) ?? 1)) ); } return _bank; @@ -179,45 +179,3 @@ export function parseBank({ return parseBankFromFlags({ bank: inputBank ?? new Bank(), flags, excludeItems, maxSize, user }); } - -function truncateBankToSize(bank: Bank, size: number) { - let newBank = new Bank(); - - for (const [item, qty] of bank.items()) { - if (newBank.length === size) break; - newBank.add(item.id, qty); - } - - return newBank; -} - -interface ParseInputCostBankOptions { - usersBank: Bank; - flags?: Record; - inputStr?: string; - excludeItems: readonly number[]; - user?: MUser; -} -export function parseInputCostBank({ - usersBank, - inputStr, - flags = {}, - excludeItems, - user -}: ParseInputCostBankOptions): Bank { - if (!inputStr && Object.keys(flags).length > 0) { - return truncateBankToSize(parseBankFromFlags({ bank: usersBank, flags, excludeItems, user }), 60); - } - - const baseBank = parseBankFromFlags({ bank: usersBank, flags, excludeItems, user }); - const stringInputBank = Boolean(inputStr) ? parseStringBank(inputStr, baseBank, true) : []; - - const bank = new Bank(); - for (const [item, qty] of stringInputBank) { - const amountOwned = baseBank.amount(item.id); - const maxQuantity = Number(flags.qty) || Infinity; - bank.add(item.id, Math.min(maxQuantity, amountOwned, qty || amountOwned)); - } - - return truncateBankToSize(bank, 60); -} diff --git a/src/lib/util/repairBrokenItems.ts b/src/lib/util/repairBrokenItems.ts index 3aafb0bbd39..72821976346 100644 --- a/src/lib/util/repairBrokenItems.ts +++ b/src/lib/util/repairBrokenItems.ts @@ -1,155 +1,185 @@ -import { Prisma } from '@prisma/client'; -import { notEmpty } from 'e'; +import { deepEqual, deepObjectDiff } from '@oldschoolgg/toolkit'; +import type { GearSetupType, Prisma } from '@prisma/client'; +import { deepClone, notEmpty, uniqueArr } from 'e'; import { Items } from 'oldschooljs'; import { userStatsUpdate } from '../../mahoji/mahojiSettings'; -import { GearSetup, GearSetupTypes } from '../gear'; -import { prisma } from '../settings/prisma'; -import { ItemBank } from '../types'; +import { type GearSetup, GearSetupTypes } from '../gear'; + +import type { ItemBank } from '../types'; import { moidLink } from '../util'; -export async function repairBrokenItemsFromUser(mUser: MUser): Promise<[string] | [string, any[]]> { - const { user } = mUser; - const changes: Prisma.UserUpdateArgs['data'] = {}; - const rawBank = user.bank as ItemBank; - const rawCL = user.collectionLogBank as ItemBank; - const rawTempCL = user.temp_cl as ItemBank; +type GearX = Required>; +type Changes = { + bank: ItemBank; + collectionLogBank: ItemBank; + temp_cl: ItemBank; + sacrificedBank: ItemBank; + favoriteItems: number[]; + tames: { id: number; max_total_loot: ItemBank; fed_items: ItemBank }[]; +} & GearX; - const { sacrificed_bank: rawSacLog } = (await mUser.fetchStats({ sacrificed_bank: true })) as { - sacrificed_bank: ItemBank; - }; - - const favorites = user.favoriteItems; - let rawTameBanks: [number, ItemBank][] = []; - let rawTameFeeds: [number, ItemBank][] = []; +export async function repairBrokenItemsFromUser(mUser: MUser) { + const previousUser = deepClone(mUser.user); + const { user } = mUser; const userTames = await prisma.tame.findMany({ where: { - user_id: user.id + user_id: mUser.id } }); - if (userTames.length > 0) { - for (const tame of userTames) { - rawTameBanks.push([tame.id, tame.max_total_loot as ItemBank]); - rawTameFeeds.push([tame.id, tame.fed_items as ItemBank]); - } - } - - const rawAllGear = GearSetupTypes.map(i => user[`gear_${i}`]); - const allGearItemIDs = rawAllGear - .filter(notEmpty) - .map((b: any) => - Object.values(b) + const __currentValues: Changes = { + bank: user.bank as ItemBank, + collectionLogBank: user.collectionLogBank as ItemBank, + temp_cl: user.temp_cl as ItemBank, + sacrificedBank: (await mUser.fetchStats({ sacrificed_bank: true })).sacrificed_bank as ItemBank, + favoriteItems: user.favoriteItems, + tames: userTames.map(t => ({ + id: t.id, + max_total_loot: t.max_total_loot as ItemBank, + fed_items: t.fed_items as ItemBank + })), + gear_fashion: user.gear_fashion as GearSetup | null, + gear_melee: user.gear_melee as GearSetup | null, + gear_mage: user.gear_mage as GearSetup | null, + gear_range: user.gear_range as GearSetup | null, + gear_misc: user.gear_misc as GearSetup | null, + gear_other: user.gear_other as GearSetup | null, + gear_skilling: user.gear_skilling as GearSetup | null, + gear_wildy: user.gear_wildy as GearSetup | null + }; + const currentValues: Changes = deepClone(__currentValues); + const currentGearValues = [ + currentValues.gear_fashion, + currentValues.gear_melee, + currentValues.gear_mage, + currentValues.gear_range, + currentValues.gear_misc, + currentValues.gear_other, + currentValues.gear_skilling, + currentValues.gear_wildy + ].filter(notEmpty); + + const allGearItemIDs = currentGearValues + .map(b => { + const gear = b as GearSetup | null; + if (!gear) return []; + return Object.values(gear) .filter(notEmpty) - .map((i: any) => i.item) - ) - .flat(Infinity); - + .map(i => i.item); + }) + .flat(2); + + // Find broken items + const allItemsToCheck = uniqueArr([ + ...Object.keys(currentValues.bank), + ...Object.keys(currentValues.collectionLogBank), + ...Object.keys(currentValues.temp_cl), + ...Object.keys(currentValues.sacrificedBank), + ...currentValues.favoriteItems, + ...allGearItemIDs + ]).map(id => Number(id)); const brokenBank: number[] = []; - const allItemsToCheck: [string, (number | string)[]][] = [ - ['bank', Object.keys(rawBank)], - ['cl', Object.keys(rawCL)], - ['tempcl', Object.keys(rawTempCL)], - ['sl', Object.keys(rawSacLog)], - ['favs', favorites], - ['gear', allGearItemIDs] - ]; - let newTameFeeds: [number, ItemBank][] = []; - let newTameBanks: [number, ItemBank][] = []; - for (const [tameId, tameBank] of rawTameBanks) { - allItemsToCheck.push([`tbank-${tameId}`, Object.keys(tameBank)]); - newTameBanks.push([tameId, { ...tameBank }]); - } - for (const [tameId, tameFeed] of rawTameFeeds) { - allItemsToCheck.push([`tfeed-${tameId}`, Object.keys(tameFeed)]); - newTameFeeds.push([tameId, { ...tameFeed }]); - } - - for (const [, ids] of allItemsToCheck) { - for (const id of ids.map(i => Number(i))) { - const item = Items.get(id); - if (!item) { - brokenBank.push(id); - } - } + for (const id of allItemsToCheck) { + const item = Items.get(id); + if (!item) brokenBank.push(id); } - const newFavs = favorites.filter(i => !brokenBank.includes(i)); - - const newBank = { ...rawBank }; - const newCL = { ...rawCL }; - const newTempCL = { ...rawTempCL }; - const newSacLog = { ...rawSacLog }; - + // Fix + const newValues = deepClone(currentValues); + newValues.favoriteItems = currentValues.favoriteItems.filter(i => !brokenBank.includes(i)); for (const id of brokenBank) { - delete newBank[id]; - delete newCL[id]; - delete newTempCL[id]; - delete newSacLog[id]; - for (const [, tameBank] of newTameBanks) { - delete tameBank[id]; - } - for (const [, tameFeed] of newTameFeeds) { - delete tameFeed[id]; + delete newValues.bank[id]; + delete newValues.collectionLogBank[id]; + delete newValues.temp_cl[id]; + delete newValues.sacrificedBank[id]; + for (const tame of newValues.tames) { + delete tame.fed_items[id]; + delete tame.max_total_loot[id]; } } - for (const setupType of GearSetupTypes) { - const _gear = user[`gear_${setupType}`] as GearSetup | null; + const _gear = currentValues[`gear_${setupType}`] as GearSetup | null; if (_gear === null) continue; const gear = { ..._gear }; for (const [key, value] of Object.entries(gear)) { if (value === null) continue; if (brokenBank.includes(value.item)) { - delete gear[key as keyof GearSetup]; + gear[key as keyof GearSetup] = null; } } - // @ts-ignore ??? - changes[`gear_${setupType}`] = gear; + newValues[`gear_${setupType}`] = gear; } + // If there are broken items, update the user if (brokenBank.length > 0) { - changes.favoriteItems = newFavs; - changes.bank = newBank; - changes.collectionLogBank = newCL; - changes.temp_cl = newTempCL; - if (newFavs.includes(NaN) || [newBank, newCL, newTempCL].some(i => Boolean(i['NaN']))) { - return ['Oopsie...']; - } + const changes: Prisma.UserUpdateArgs['data'] = {}; - await mUser.update(changes); - if (userTames.length > 0) { - for (const tame of userTames) { - const tameBank = newTameBanks.find(tb => tb[0] === tame.id)![1]; - const tameFeed = newTameFeeds.find(tf => tf[0] === tame.id)![1]; - await prisma.tame.update({ - where: { - id: tame.id - }, - data: { - max_total_loot: tameBank, - fed_items: tameFeed - } - }); + if (!deepEqual(currentValues.bank, newValues.bank)) { + changes.bank = newValues.bank; + } + if (!deepEqual(currentValues.collectionLogBank, newValues.collectionLogBank)) { + changes.collectionLogBank = newValues.collectionLogBank; + } + if (!deepEqual(currentValues.temp_cl, newValues.temp_cl)) { + changes.temp_cl = newValues.temp_cl; + } + if (!deepEqual(currentValues.favoriteItems, newValues.favoriteItems)) { + changes.favoriteItems = newValues.favoriteItems; + } + for (const setupType of GearSetupTypes) { + if (!deepEqual(currentValues[`gear_${setupType}`], newValues[`gear_${setupType}`])) { + changes[`gear_${setupType}`] = newValues[`gear_${setupType}`] as any as Prisma.InputJsonValue; } } - await userStatsUpdate( - mUser.id, - { - sacrificed_bank: newSacLog - }, - {} - ); - - return [ - `You had ${ - brokenBank.length - } broken items in your bank/collection log/favorites/gear/tame, they were removed. ${moidLink( - brokenBank - ).slice(0, 500)}`, - Object.keys(brokenBank) - ]; + + if (Object.values(changes).length > 0) { + debugLog(`${mUser.logName} repair bank: +Broken Items: ${brokenBank.join(', ')} +Changes: ${JSON.stringify(changes, null, ' ')} +Previous User: ${JSON.stringify(previousUser)} +`); + + await mUser.update(changes); + await mUser.sync(); + + debugLog(`${mUser.logName} repair bank: +New User: ${JSON.stringify(mUser.user)} +`); + } + + for (const tame of newValues.tames) { + await prisma.tame.update({ + where: { + id: tame.id + }, + data: { + max_total_loot: tame.max_total_loot, + fed_items: tame.fed_items + } + }); + } + + if (!deepEqual(currentValues.sacrificedBank, newValues.sacrificedBank)) { + debugLog( + `${mUser.logName} repair bank sacrifice bank changes: ${JSON.stringify(deepObjectDiff(currentValues.sacrificedBank, newValues.sacrificedBank))}` + ); + await userStatsUpdate( + mUser.id, + { + sacrificed_bank: newValues.sacrificedBank + }, + {} + ); + } + + return `You had ${ + brokenBank.length + } broken items in your bank/collection log/favorites/gear/tame, they were removed. ${moidLink(brokenBank).slice( + 0, + 500 + )}`; } - return ['You have no broken items on your account!']; + return 'You have no broken items on your account!'; } diff --git a/src/lib/util/repeatStoredTrip.ts b/src/lib/util/repeatStoredTrip.ts index d3d6f63b4a4..f37d4d1ea9c 100644 --- a/src/lib/util/repeatStoredTrip.ts +++ b/src/lib/util/repeatStoredTrip.ts @@ -1,5 +1,7 @@ -import { Activity, activity_type_enum, Prisma } from '@prisma/client'; -import { ButtonBuilder, ButtonInteraction, ButtonStyle } from 'discord.js'; +import type { Activity, Prisma } from '@prisma/client'; +import { activity_type_enum } from '@prisma/client'; +import type { ButtonInteraction } from 'discord.js'; +import { ButtonBuilder, ButtonStyle } from 'discord.js'; import { Time } from 'e'; import { autocompleteMonsters } from '../../mahoji/commands/k'; @@ -8,7 +10,6 @@ import type { PvMMethod } from '../constants'; import { kibbles } from '../data/kibble'; import { SlayerActivityConstants } from '../minions/data/combatConstants'; import { darkAltarRunes } from '../minions/functions/darkAltarCommand'; -import { prisma } from '../settings/prisma'; import { runCommand } from '../settings/settings'; import type { ActivityTaskOptionsWithQuantity, @@ -25,9 +26,9 @@ import type { CookingActivityTaskOptions, CraftingActivityTaskOptions, CutLeapingFishActivityTaskOptions, + DOAOptions, DarkAltarOptions, DisassembleTaskOptions, - DOAOptions, DungeoneeringOptions, EnchantingActivityTaskOptions, FarmingActivityTaskOptions, @@ -61,18 +62,20 @@ import type { ShadesOfMortonOptions, SmeltingActivityTaskOptions, SmithingActivityTaskOptions, + TOAOptions, TempleTrekkingActivityTaskOptions, TheatreOfBloodTaskOptions, TiaraRunecraftActivityTaskOptions, TinkeringWorkshopOptions, - TOAOptions, TuraelsTrialsOptions, - WoodcuttingActivityTaskOptions + UndoneChangesMonsterOptions, + WoodcuttingActivityTaskOptions, + ZalcanoActivityTaskOptions } from '../types/minions'; import { itemNameFromID } from '../util'; import { giantsFoundryAlloys } from './../../mahoji/lib/abstracted_commands/giantsFoundryCommand'; -import { NightmareZoneActivityTaskOptions, UnderwaterAgilityThievingTaskOptions } from './../types/minions'; -import { deferInteraction } from './interactionReply'; +import type { NightmareZoneActivityTaskOptions, UnderwaterAgilityThievingTaskOptions } from './../types/minions'; +import { interactionReply } from './interactionReply'; export const taskCanBeRepeated = (activity: Activity) => { return !( @@ -90,7 +93,8 @@ export const taskCanBeRepeated = (activity: Activity) => { activity_type_enum.BalthazarsBigBonanza, activity_type_enum.GuthixianCache, activity_type_enum.Birdhouse, - activity_type_enum.StrongholdOfSecurity + activity_type_enum.StrongholdOfSecurity, + activity_type_enum.CombatRing ] as activity_type_enum[] ).includes(activity.type); }; @@ -133,6 +137,10 @@ export const tripHandlers = { commandName: 'm', args: () => ({}) }, + [activity_type_enum.CombatRing]: { + commandName: 'm', + args: () => ({}) + }, [activity_type_enum.TearsOfGuthix]: { commandName: 'm', args: () => ({}) @@ -161,10 +169,6 @@ export const tripHandlers = { commandName: 'm', args: () => ({}) }, - [activity_type_enum.HalloweenEvent]: { - commandName: 'm', - args: () => ({}) - }, [activity_type_enum.Revenants]: { commandName: 'm', args: () => ({}) @@ -271,7 +275,10 @@ export const tripHandlers = { }, [activity_type_enum.Cooking]: { commandName: 'cook', - args: (data: CookingActivityTaskOptions) => ({ name: itemNameFromID(data.cookableID), quantity: data.quantity }) + args: (data: CookingActivityTaskOptions) => ({ + name: itemNameFromID(data.cookableID), + quantity: data.quantity + }) }, [activity_type_enum.Crafting]: { commandName: 'craft', @@ -318,7 +325,7 @@ export const tripHandlers = { data.autoFarmed ? { auto_farm: {} - } + } : {} }, [activity_type_enum.FightCaves]: { @@ -359,7 +366,7 @@ export const tripHandlers = { [activity_type_enum.GroupMonsterKilling]: { commandName: 'mass', args: (data: GroupMonsterActivityTaskOptions) => ({ - monster: autocompleteMonsters.find(i => i.id === data.monsterID)?.name ?? data.monsterID.toString() + monster: autocompleteMonsters.find(i => i.id === data.mi)?.name ?? data.mi.toString() }) }, [activity_type_enum.Herblore]: { @@ -431,12 +438,13 @@ export const tripHandlers = { let method: PvMMethod = 'none'; if (data.usingCannon) method = 'cannon'; if (data.chinning) (method as string) = 'chinning'; - else if (data.burstOrBarrage === SlayerActivityConstants.IceBarrage) method = 'barrage'; - else if (data.burstOrBarrage === SlayerActivityConstants.IceBurst) method = 'burst'; + else if (data.bob === SlayerActivityConstants.IceBarrage) method = 'barrage'; + else if (data.bob === SlayerActivityConstants.IceBurst) method = 'burst'; return { - name: autocompleteMonsters.find(i => i.id === data.monsterID)?.name ?? data.monsterID.toString(), + name: autocompleteMonsters.find(i => i.id === data.mi)?.name ?? data.mi?.toString(), quantity: data.iQty, - method + method, + wilderness: data.isInWilderness }; } }, @@ -451,8 +459,9 @@ export const tripHandlers = { }, [activity_type_enum.Zalcano]: { commandName: 'k', - args: () => ({ - name: 'zalcano' + args: (data: ZalcanoActivityTaskOptions) => ({ + name: 'zalcano', + quantity: data.quantity }) }, [activity_type_enum.Tempoross]: { @@ -463,8 +472,9 @@ export const tripHandlers = { }, [activity_type_enum.Wintertodt]: { commandName: 'k', - args: () => ({ - name: 'wintertodt' + args: (data: ActivityTaskOptionsWithQuantity) => ({ + name: 'wintertodt', + quantity: data.quantity }) }, [activity_type_enum.Nightmare]: { @@ -592,7 +602,7 @@ export const tripHandlers = { }, [activity_type_enum.VasaMagus]: { commandName: 'k', - args: (data: MonsterActivityTaskOptions) => ({ + args: (data: UndoneChangesMonsterOptions) => ({ name: 'vasa', quantity: data.quantity }) @@ -688,7 +698,7 @@ export const tripHandlers = { }, [activity_type_enum.ClueCompletion]: { commandName: 'clue', - args: (data: ClueActivityTaskOptions) => ({ tier: data.clueID, quantity: data.quantity }) + args: (data: ClueActivityTaskOptions) => ({ tier: data.ci, quantity: data.q }) }, [activity_type_enum.FistOfGuthix]: { commandName: 'bsominigames', @@ -819,6 +829,12 @@ export const tripHandlers = { } } }) + }, + [activity_type_enum.Colosseum]: { + commandName: 'k', + args: () => ({ + name: 'colosseum' + }) } } as const; @@ -876,7 +892,9 @@ export async function repeatTrip( interaction: ButtonInteraction, data: { data: Prisma.JsonValue; type: activity_type_enum } ) { - await deferInteraction(interaction); + if (!data || !data.data || !data.type) { + return interactionReply(interaction, { content: "Couldn't find any trip to repeat.", ephemeral: true }); + } const handler = tripHandlers[data.type]; return runCommand({ commandName: handler.commandName, diff --git a/src/lib/util/resolveItems.ts b/src/lib/util/resolveItems.ts index 6df0d1ebf0b..d1e11701be9 100644 --- a/src/lib/util/resolveItems.ts +++ b/src/lib/util/resolveItems.ts @@ -42,7 +42,3 @@ export function deepResolveItems(itemArray: ArrayItemsResolvable): ArrayItemsRes return newArray; } - -export function resolveOSItems(...args: Parameters) { - return resolveItems(...args).map(i => Items.get(i)!); -} diff --git a/src/lib/util/setCustomMonster.ts b/src/lib/util/setCustomMonster.ts index c1fbf9c2d3b..2534250f90a 100644 --- a/src/lib/util/setCustomMonster.ts +++ b/src/lib/util/setCustomMonster.ts @@ -1,6 +1,6 @@ import { Bank, Monsters } from 'oldschooljs'; -import LootTable from 'oldschooljs/dist/structures/LootTable'; -import Monster from 'oldschooljs/dist/structures/Monster'; +import type LootTable from 'oldschooljs/dist/structures/LootTable'; +import type Monster from 'oldschooljs/dist/structures/Monster'; export function makeKillTable(table: LootTable) { return { diff --git a/src/lib/util/slayerMaskLeaderboard.ts b/src/lib/util/slayerMaskLeaderboard.ts index 9ab6ba697f7..6505151a738 100644 --- a/src/lib/util/slayerMaskLeaderboard.ts +++ b/src/lib/util/slayerMaskLeaderboard.ts @@ -1,16 +1,14 @@ import { Bank } from 'oldschooljs'; import { slayerMaskHelms } from '../data/slayerMaskHelms'; -import { prisma } from '../settings/prisma'; -import { ItemBank } from '../types'; + +import type { ItemBank } from '../types'; export const slayerMaskLeaderboardCache = new Map(); export const allSlayerMaskHelmsAndMasks = new Set(slayerMaskHelms.flatMap(i => [i.mask.id, i.helm.id])); export async function syncSlayerMaskLeaderboardCache() { - const result = await prisma.$queryRawUnsafe< - { user_id: string; on_task_with_mask_monster_scores: ItemBank }[] - >(`SELECT user_id::text, on_task_with_mask_monster_scores + const result = await prisma.$queryRawUnsafe<{ user_id: string; on_task_with_mask_monster_scores: ItemBank }[]>(`SELECT user_id::text, on_task_with_mask_monster_scores FROM user_stats WHERE on_task_with_mask_monster_scores IS NOT NULL AND on_task_with_mask_monster_scores::text != '{}';`); @@ -18,7 +16,7 @@ WHERE on_task_with_mask_monster_scores IS NOT NULL AND on_task_with_mask_monster for (const user of result) { const kcBank = new Bank(user.on_task_with_mask_monster_scores); const maskKCBank = new Bank(); - let parsedUser = { userID: user.user_id, maskKCBank }; + const parsedUser = { userID: user.user_id, maskKCBank }; for (const { mask, monsters } of slayerMaskHelms) { for (const mon of monsters) { maskKCBank.add(mask.id, kcBank.amount(mon)); diff --git a/src/lib/util/smallUtils.ts b/src/lib/util/smallUtils.ts index ef4f7b862f1..8a702239706 100644 --- a/src/lib/util/smallUtils.ts +++ b/src/lib/util/smallUtils.ts @@ -1,26 +1,19 @@ -import { exec } from 'node:child_process'; -import { createHash } from 'node:crypto'; -import { readFileSync } from 'node:fs'; - -import { miniID, toTitleCase } from '@oldschoolgg/toolkit'; +import { deepMerge, md5sum, miniID, toTitleCase } from '@oldschoolgg/toolkit'; +import type { CommandResponse } from '@oldschoolgg/toolkit'; import type { Prisma } from '@prisma/client'; import { AlignmentEnum, AsciiTable3 } from 'ascii-table3'; -import deepmerge from 'deepmerge'; -import { ButtonBuilder, ButtonStyle, InteractionReplyOptions, time } from 'discord.js'; -import { clamp, objectEntries, roll, Time } from 'e'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; -import { Bank, Items, LootTable } from 'oldschooljs'; -import { ItemBank } from 'oldschooljs/dist/meta/types'; +import type { InteractionReplyOptions } from 'discord.js'; +import { ButtonBuilder, ButtonStyle } from 'discord.js'; +import { clamp, objectEntries, roll } from 'e'; +import { type Bank, Items, LootTable } from 'oldschooljs'; +import type { ItemBank } from 'oldschooljs/dist/meta/types'; +import type { ArrayItemsResolved } from 'oldschooljs/dist/util/util'; import { MersenneTwister19937, shuffle } from 'random-js'; import { skillEmoji } from '../data/emojis'; -import type { ArrayItemsResolved, Skills } from '../types'; +import type { Skills } from '../types'; import getOSItem from './getOSItem'; -export function md5sum(str: string) { - return createHash('md5').update(str).digest('hex'); -} - export function itemNameFromID(itemID: number | string) { return Items.get(itemID)?.name; } @@ -45,7 +38,7 @@ export function formatItemBoosts(items: ItemBank[]) { const bonusStr = []; for (const [itemID, boostAmount] of itemEntries) { - bonusStr.push(`${boostAmount}% for ${itemNameFromID(parseInt(itemID))}`); + bonusStr.push(`${boostAmount}% for ${itemNameFromID(Number.parseInt(itemID))}`); } if (multiple) { @@ -57,33 +50,8 @@ export function formatItemBoosts(items: ItemBank[]) { return str.join(', '); } -export function calcPerHour(value: number, duration: number) { - return (value / (duration / Time.Minute)) * 60; -} - -export function formatDuration(ms: number, short = false) { - if (ms < 0) ms = -ms; - const time = { - day: Math.floor(ms / 86_400_000), - hour: Math.floor(ms / 3_600_000) % 24, - minute: Math.floor(ms / 60_000) % 60, - second: Math.floor(ms / 1000) % 60 - }; - const shortTime = { - d: Math.floor(ms / 86_400_000), - h: Math.floor(ms / 3_600_000) % 24, - m: Math.floor(ms / 60_000) % 60, - s: Math.floor(ms / 1000) % 60 - }; - let nums = Object.entries(short ? shortTime : time).filter(val => val[1] !== 0); - if (nums.length === 0) return '1 second'; - return nums - .map(([key, val]) => `${val}${short ? '' : ' '}${key}${val === 1 || short ? '' : 's'}`) - .join(short ? '' : ', '); -} - export function formatSkillRequirements(reqs: Record, emojis = true) { - let arr = []; + const arr = []; for (const [name, num] of objectEntries(reqs)) { arr.push(`${emojis ? ` ${(skillEmoji as any)[name]} ` : ''}**${num}** ${toTitleCase(name)}`); } @@ -102,43 +70,14 @@ export function pluraliseItemName(name: string): string { return name + (name.endsWith('s') ? '' : 's'); } -/** - * Scale percentage exponentially - * - * @param decay Between 0.01 and 0.05; bigger means more penalty. - * @param percent The percent to scale - * @returns percent - */ -export function exponentialPercentScale(percent: number, decay = 0.021) { - return 100 * Math.pow(Math.E, -decay * (100 - percent)); -} - -export function normal(mu = 0, sigma = 1, nsamples = 6) { - let run_total = 0; - - for (let i = 0; i < nsamples; i++) { - run_total += Math.random(); - } - - return (sigma * (run_total - nsamples / 2)) / (nsamples / 2) + mu; -} - export function shuffleRandom(input: number, arr: readonly T[]): T[] { const engine = MersenneTwister19937.seed(input); return shuffle(engine, [...arr]); } -export function averageBank(bank: Bank, kc: number) { - let newBank = new Bank(); - for (const [item, qty] of bank.items()) { - newBank.add(item.id, Math.floor(qty / kc)); - } - return newBank; -} - export function calcBabyYagaHouseDroprate(xpBeingReceived: number, cl: Bank) { let rate = 1 / (((xpBeingReceived / 30) * 30) / 50_000_000); - let amountInCl = cl.amount('Baby yaga house'); + const amountInCl = cl.amount('Baby yaga house'); if (amountInCl > 1) rate *= amountInCl; return Math.floor(rate); } @@ -185,18 +124,6 @@ export const SQL_sumOfAllCLItems = (clItems: number[]) => export const generateGrandExchangeID = () => miniID(6).toLowerCase(); -export function tailFile(fileName: string, numLines: number): Promise { - return new Promise((resolve, reject) => { - exec(`tail -n ${numLines} ${fileName}`, (error, stdout) => { - if (error) { - reject(error); - } else { - resolve(stdout); - } - }); - }); -} - export function getToaKCs(toaRaidLevelsBank: Prisma.JsonValue) { let entryKC = 0; let normalKC = 0; @@ -215,30 +142,6 @@ export function getToaKCs(toaRaidLevelsBank: Prisma.JsonValue) { } return { entryKC, normalKC, expertKC, totalKC: entryKC + normalKC + expertKC }; } -export const alphabeticalSort = (a: string, b: string) => a.localeCompare(b); - -export function dateFm(date: Date) { - return `${time(date, 'T')} (${time(date, 'R')})`; -} - -export function getInterval(intervalHours: number) { - const currentTime = new Date(); - const currentHour = currentTime.getHours(); - - // Find the nearest interval start hour (0, intervalHours, 2*intervalHours, etc.) - const startHour = currentHour - (currentHour % intervalHours); - const startInterval = new Date(currentTime); - startInterval.setHours(startHour, 0, 0, 0); - - const endInterval = new Date(startInterval); - endInterval.setHours(startHour + intervalHours); - - return { - start: startInterval, - end: endInterval, - nextResetStr: dateFm(endInterval) - }; -} export function calculateSimpleMonsterDeathChance({ hardness, @@ -255,10 +158,10 @@ export function calculateSimpleMonsterDeathChance({ }): number { if (!currentKC) currentKC = 1; currentKC = Math.max(1, currentKC); - let baseDeathChance = Math.min(highestDeathChance, (100 * hardness) / steepness); + const baseDeathChance = Math.min(highestDeathChance, (100 * hardness) / steepness); const maxScalingKC = 5 + (75 * hardness) / steepness; - let reductionFactor = Math.min(1, currentKC / maxScalingKC); - let deathChance = baseDeathChance - reductionFactor * (baseDeathChance - lowestDeathChance); + const reductionFactor = Math.min(1, currentKC / maxScalingKC); + const deathChance = baseDeathChance - reductionFactor * (baseDeathChance - lowestDeathChance); return clamp(deathChance, lowestDeathChance, highestDeathChance); } @@ -293,34 +196,6 @@ export function perHourChance( } } -export function perTimeUnitChance( - durationMilliseconds: number, - oneInXPerTimeUnitChance: number, - timeUnitInMilliseconds: number, - successFunction: () => unknown -) { - const unitsPassed = Math.floor(durationMilliseconds / timeUnitInMilliseconds); - const perUnitChance = oneInXPerTimeUnitChance / (timeUnitInMilliseconds / 60_000); - - for (let i = 0; i < unitsPassed; i++) { - if (roll(perUnitChance)) { - successFunction(); - } - } -} - -export function addBanks(banks: ItemBank[]): Bank { - const bank = new Bank(); - for (const _bank of banks) { - bank.add(_bank); - } - return bank; -} - -export function isValidDiscordSnowflake(snowflake: string): boolean { - return /^\d{17,19}$/.test(snowflake); -} - const TOO_LONG_STR = 'The result was too long (over 2000 characters), please read the attached file.'; export function returnStringOrFile( @@ -339,7 +214,7 @@ export function returnStringOrFile( } if (string.content && (string.content.length > 2000 || forceFile)) { const hash = md5sum(string.content).slice(0, 5); - return deepmerge( + return deepMerge( string, { content: TOO_LONG_STR, @@ -351,35 +226,6 @@ export function returnStringOrFile( return string; } -const wordBlacklistBase64 = readFileSync('./src/lib/data/wordBlacklist.txt', 'utf-8'); -const wordBlacklist = Buffer.from(wordBlacklistBase64.trim(), 'base64') - .toString('utf8') - .split('\n') - .map(word => word.trim().toLowerCase()); - -export function containsBlacklistedWord(str: string): boolean { - const lowerCaseStr = str.toLowerCase(); - for (const word of wordBlacklist) { - if (lowerCaseStr.includes(word)) { - return true; - } - } - return false; -} - -export function calculateAverageTimeForSuccess(probabilityPercent: number, timeFrameMilliseconds: number): number { - let probabilityOfSuccess = probabilityPercent / 100; - let averageTimeUntilSuccessMilliseconds = timeFrameMilliseconds / probabilityOfSuccess; - return averageTimeUntilSuccessMilliseconds; -} - -export function ellipsize(str: string, maxLen: number = 2000) { - if (str.length > maxLen) { - return `${str.substring(0, maxLen - 3)}...`; - } - return str; -} - export function makeTable(headers: string[], rows: unknown[][]) { return new AsciiTable3() .setHeading(...headers) diff --git a/src/lib/util/statsEmbed.ts b/src/lib/util/statsEmbed.ts deleted file mode 100644 index d61269fae43..00000000000 --- a/src/lib/util/statsEmbed.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { toTitleCase } from '@oldschoolgg/toolkit'; -import { EmbedBuilder } from 'discord.js'; -import { Player } from 'oldschooljs'; -import { CluesScore, SkillScore, SkillsScore } from 'oldschooljs/dist/meta/types'; - -import { skillEmoji } from '../data/emojis'; - -export function statsEmbed({ - username, - color, - player, - key = 'level', - showExtra = true, - postfix -}: { - username: string; - color: number; - player: Player; - key?: keyof SkillScore; - showExtra?: boolean; - postfix?: string; -}) { - const skillCell = (skill: string) => - `${skillEmoji[skill as keyof typeof skillEmoji] as keyof SkillsScore} ${player.skills[ - skill as keyof SkillsScore - ][key].toLocaleString()}`; - - const embed = new EmbedBuilder() - .setColor(color) - .setTitle( - `${globalClient._badgeCache.get(username.toLowerCase()) || ''} ${toTitleCase(username)}${postfix ?? ''}` - ) - .addFields({ - name: '\u200b', - value: ['attack', 'strength', 'defence', 'ranged', 'prayer', 'magic', 'runecraft', 'construction'] - .map(skillCell) - .join('\n'), - inline: true - }) - .addFields({ - name: '\u200b', - value: ['hitpoints', 'agility', 'herblore', 'thieving', 'crafting', 'fletching', 'slayer', 'hunter'] - .map(skillCell) - .join('\n'), - inline: true - }) - .addFields({ - name: '\u200b', - value: ['mining', 'smithing', 'fishing', 'cooking', 'firemaking', 'woodcutting', 'farming', 'overall'] - .map(skillCell) - .join('\n'), - inline: true - }); - - if (showExtra) { - embed - .addFields({ - name: `${skillEmoji.total} Overall`, - value: `**Rank:** ${player.skills.overall.rank.toLocaleString()} -**Level:** ${player.skills.overall.level} -**XP:** ${player.skills.overall.xp.toLocaleString()} -**Combat Level:** ${player.combatLevel}`, - inline: true - }) - .addFields({ - name: '<:minigame_icon:630400565070921761> Minigame Scores', - value: `**BH:** ${player.minigames.bountyHunter.score.toLocaleString()} -**BH-Rogue:** ${player.minigames.bountyHunterRogue.score.toLocaleString()} -**LMS:** ${player.minigames.LMS.score.toLocaleString()} -`, - inline: true - }) - .addFields({ - name: '<:Clue_scroll:365003979840552960> Clue Scores', - value: Object.keys(player.clues) - .slice(1) - .map( - tier => - `**${toTitleCase(tier)}:** ${player.clues[tier as keyof CluesScore].score.toLocaleString()}` - ) - .join('\n'), - inline: true - }); - } - return embed; -} diff --git a/src/lib/util/syncDisabledCommands.ts b/src/lib/util/syncDisabledCommands.ts new file mode 100644 index 00000000000..bb92a347b19 --- /dev/null +++ b/src/lib/util/syncDisabledCommands.ts @@ -0,0 +1,20 @@ +import { DISABLED_COMMANDS, globalConfig } from '../constants'; + +export async function syncDisabledCommands() { + const disabledCommands = await prisma.clientStorage.upsert({ + where: { + id: globalConfig.clientID + }, + select: { disabled_commands: true }, + create: { + id: globalConfig.clientID + }, + update: {} + }); + + if (disabledCommands.disabled_commands) { + for (const command of disabledCommands.disabled_commands) { + DISABLED_COMMANDS.add(command); + } + } +} diff --git a/src/lib/util/tameUtil.ts b/src/lib/util/tameUtil.ts index 60aa41e4055..7e3d54959e1 100644 --- a/src/lib/util/tameUtil.ts +++ b/src/lib/util/tameUtil.ts @@ -1,14 +1,22 @@ -import { Tame, tame_growth, TameActivity, User } from '@prisma/client'; +import { type Tame, type TameActivity, type User, tame_growth } from '@prisma/client'; import { round } from 'e'; import { Items } from 'oldschooljs'; import { mahojiUsersSettingsFetch } from '../../mahoji/mahojiSettings'; import { ClueTiers } from '../clues/clueTiers'; import { getSimilarItems } from '../data/similarItems'; -import { prisma } from '../settings/prisma'; -import { seaMonkeySpells, Species, tameKillableMonsters, tameSpecies, TameTaskOptions, TameType } from '../tames'; -import { ItemBank } from '../types'; -import { formatDuration, itemNameFromID } from './smallUtils'; + +import { formatDuration } from '@oldschoolgg/toolkit'; +import { + type Species, + type TameTaskOptions, + TameType, + seaMonkeySpells, + tameKillableMonsters, + tameSpecies +} from '../tames'; +import type { ItemBank } from '../types'; +import { itemNameFromID } from './smallUtils'; export async function tameLastFinishedActivity(user: MUser) { const tameID = user.user.selected_tame; diff --git a/src/lib/util/taskGroupFromActivity.ts b/src/lib/util/taskGroupFromActivity.ts index ebf8e321a57..1ab48292c71 100644 --- a/src/lib/util/taskGroupFromActivity.ts +++ b/src/lib/util/taskGroupFromActivity.ts @@ -1,4 +1,4 @@ -import { activity_type_enum } from '@prisma/client'; +import type { activity_type_enum } from '@prisma/client'; import { ActivityGroup } from '../constants'; diff --git a/src/lib/util/tradePlayerItems.ts b/src/lib/util/tradePlayerItems.ts index 5b13639ef49..e07a82482ec 100644 --- a/src/lib/util/tradePlayerItems.ts +++ b/src/lib/util/tradePlayerItems.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import { modifyBusyCounter } from '../busyCounterCache'; -import { prisma } from '../settings/prisma'; + import { logError } from './logError'; import { userQueueFn } from './userQueues'; diff --git a/src/lib/util/transactItemsFromBank.ts b/src/lib/util/transactItemsFromBank.ts index 615f6bc2dad..8dbfdcd5e8a 100644 --- a/src/lib/util/transactItemsFromBank.ts +++ b/src/lib/util/transactItemsFromBank.ts @@ -1,11 +1,11 @@ -import { Prisma } from '@prisma/client'; +import type { Prisma } from '@prisma/client'; import { Bank } from 'oldschooljs'; import { findBingosWithUserParticipating } from '../../mahoji/lib/bingo/BingoManager'; -import { handleNewCLItems } from '../handleNewCLItems'; import { mahojiUserSettingsUpdate } from '../MUser'; +import { handleNewCLItems } from '../handleNewCLItems'; import { filterLootReplace } from '../slayer/slayerUtil'; -import { ItemBank } from '../types'; +import type { ItemBank } from '../types'; import { logError } from './logError'; import { userQueueFn } from './userQueues'; @@ -21,17 +21,11 @@ export interface TransactItemsArgs { } declare global { - const transactItems: typeof transactItemsFromBank; -} -declare global { - namespace NodeJS { - interface Global { - transactItems: typeof transactItemsFromBank; - } - } + var transactItems: typeof transactItemsFromBank; } + global.transactItems = transactItemsFromBank; -export async function transactItemsFromBank({ +async function transactItemsFromBank({ userID, collectionLog = false, filterLoot = true, @@ -39,9 +33,9 @@ export async function transactItemsFromBank({ ...options }: TransactItemsArgs) { let itemsToAdd = options.itemsToAdd ? options.itemsToAdd.clone() : undefined; - let itemsToRemove = options.itemsToRemove ? options.itemsToRemove.clone() : undefined; + const itemsToRemove = options.itemsToRemove ? options.itemsToRemove.clone() : undefined; - return userQueueFn(userID, async () => { + return userQueueFn(userID, async function transactItemsInner() { const settings = await mUserFetch(userID); const gpToRemove = (itemsToRemove?.amount('Coins') ?? 0) - (itemsToAdd?.amount('Coins') ?? 0); diff --git a/src/lib/util/updateBankSetting.ts b/src/lib/util/updateBankSetting.ts index 504e51456f0..8ff8e69b117 100644 --- a/src/lib/util/updateBankSetting.ts +++ b/src/lib/util/updateBankSetting.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { ItemBank } from '../types'; +import type { ItemBank } from '../types'; import { mahojiClientSettingsFetch, mahojiClientSettingsUpdate } from './clientSettings'; export type ClientBankKey = @@ -89,7 +89,9 @@ export type ClientBankKey = | 'toa_loot' | 'doa_cost' | 'doa_loot' - | 'xmas_ironman_food_bank'; + | 'xmas_ironman_food_bank' + | 'colo_cost' + | 'colo_loot'; export async function updateBankSetting(key: ClientBankKey, bankToAdd: Bank) { if (bankToAdd === undefined || bankToAdd === null) throw new Error(`Gave null bank for ${key}`); diff --git a/src/lib/util/userEvents.ts b/src/lib/util/userEvents.ts index 95f3660146e..a80e986844e 100644 --- a/src/lib/util/userEvents.ts +++ b/src/lib/util/userEvents.ts @@ -1,9 +1,9 @@ -import { Prisma, UserEvent, UserEventType, xp_gains_skill_enum } from '@prisma/client'; +import { dateFm } from '@oldschoolgg/toolkit'; +import type { Prisma, UserEvent, xp_gains_skill_enum } from '@prisma/client'; +import { UserEventType } from '@prisma/client'; import { MAX_LEVEL, MAX_TOTAL_LEVEL } from '../constants'; import { allCollectionLogsFlat } from '../data/Collections'; -import { prisma } from '../settings/prisma'; -import { dateFm } from './smallUtils'; export function userEventsToMap(_events: UserEvent[] | null) { if (_events === null) return new Map(); diff --git a/src/lib/util/userQueues.ts b/src/lib/util/userQueues.ts index 89edd772395..1d0cc83f2a4 100644 --- a/src/lib/util/userQueues.ts +++ b/src/lib/util/userQueues.ts @@ -1,25 +1,26 @@ import PromiseQueue from 'p-queue'; -export const userQueues: Map = new Map(); -export function getUserUpdateQueue(userID: string) { - let currentQueue = userQueues.get(userID); +const userQueues: Map = new Map(); +function getUserUpdateQueue(userID: string) { + const currentQueue = userQueues.get(userID); if (!currentQueue) { - let queue = new PromiseQueue({ concurrency: 1 }); + const queue = new PromiseQueue({ concurrency: 1 }); userQueues.set(userID, queue); return queue; } return currentQueue; } -export async function userQueueFn(userID: string, fn: () => Promise) { +export async function userQueueFn(userID: string, fn: () => Promise): Promise { const queue = getUserUpdateQueue(userID); - return queue.add(async () => { - const error = new Error(); - try { - return await fn(); - } catch (e) { - error.message = (e as Error).message; - throw error; - } + return new Promise((resolve, reject) => { + queue.add(() => { + return fn() + .then(resolve) + .catch(e => { + console.error(e); + reject(e); + }); + }); }); } diff --git a/src/lib/util/webhook.ts b/src/lib/util/webhook.ts index d0f2572fbc3..617c2792537 100644 --- a/src/lib/util/webhook.ts +++ b/src/lib/util/webhook.ts @@ -1,21 +1,13 @@ import { splitMessage } from '@oldschoolgg/toolkit'; -import { - AttachmentBuilder, - BaseMessageOptions, - EmbedBuilder, - Message, - PartialGroupDMChannel, - PermissionsBitField, - WebhookClient -} from 'discord.js'; -import PQueue from 'p-queue'; +import type { AttachmentBuilder, BaseMessageOptions, EmbedBuilder, Message } from 'discord.js'; +import { PartialGroupDMChannel, PermissionsBitField, WebhookClient } from 'discord.js'; import { production } from '../../config'; -import { prisma } from '../settings/prisma'; + import { channelIsSendable } from '../util'; import { logError } from './logError'; -export async function resolveChannel(channelID: string): Promise { +async function resolveChannel(channelID: string): Promise { const channel = globalClient.channels.cache.get(channelID); if (!channel || channel instanceof PartialGroupDMChannel) return undefined; if (channel.isDMBased()) return channel; @@ -31,8 +23,8 @@ export async function resolveChannel(channelID: string): Promise => { const clueTier = ClueTiers.find(tier => tier.id === clueTierID)!; - let loot = clueTier.table.open(quantity, { cl: new Bank() } as MUser); + const loot = clueTier.table.open(quantity, { cl: new Bank() } as MUser); for (let i = 0; i < quantity; i++) { const qty = randInt(1, 3); diff --git a/src/lib/workers/finish.worker.ts b/src/lib/workers/finish.worker.ts index e8a24a0fac1..be388859010 100644 --- a/src/lib/workers/finish.worker.ts +++ b/src/lib/workers/finish.worker.ts @@ -4,11 +4,15 @@ import '../data/itemAliases'; import { removeFromArr } from 'e'; import { Bank } from 'oldschooljs'; -import getOSItem from '../util/getOSItem'; import type { FinishWorkerArgs, FinishWorkerReturn } from '.'; +import getOSItem from '../util/getOSItem'; + +if (global.prisma) { + throw new Error('Prisma is loaded in the finish worker!'); +} export default async ({ name, tertiaries }: FinishWorkerArgs): FinishWorkerReturn => { - const { finishables } = await import('../finishables'); + const { finishables } = await import('../finishables.js'); const val = finishables.find(i => i.name === name)!; let finishCL = [...val.cl]; if (val.tertiaryDrops && !tertiaries) { @@ -17,7 +21,7 @@ export default async ({ name, tertiaries }: FinishWorkerArgs): FinishWorkerRetur } } const cost = new Bank(); - let loot = new Bank(); + const loot = new Bank(); const kcBank = new Bank(); let kc = 0; const maxAttempts = val.maxAttempts ?? 100_000; diff --git a/src/lib/workers/index.ts b/src/lib/workers/index.ts index 2ff2b4d969e..880417e7ebd 100644 --- a/src/lib/workers/index.ts +++ b/src/lib/workers/index.ts @@ -1,10 +1,10 @@ import { resolve } from 'node:path'; -import { Bank } from 'oldschooljs'; +import type { Bank } from 'oldschooljs'; import Piscina from 'piscina'; import { production } from '../../config'; -import { ItemBank } from '../types'; +import type { ItemBank } from '../types'; export interface CasketWorkerArgs { clueTierID: number; @@ -44,9 +44,27 @@ export type FinishWorkerReturn = Promise< const maxThreads = production ? 3 : 1; -export const finishWorker = new Piscina({ filename: resolve(__dirname, 'finish.worker.js'), maxThreads }); -export const killWorker = new Piscina({ filename: resolve(__dirname, 'kill.worker.js'), maxThreads }); -export const casketWorker = new Piscina({ filename: resolve(__dirname, 'casket.worker.js'), maxThreads }); +let dirName = __dirname.replace('src/lib', 'dist/lib'); +if (dirName.endsWith('dist')) { + dirName = resolve(dirName, 'lib', 'workers'); +} + +const finishWorkerPath = resolve(dirName, 'finish.worker.js'); +const killWorkerPath = resolve(dirName, 'kill.worker.js'); +const casketWorkerPath = resolve(dirName, 'casket.worker.js'); + +const finishWorker = new Piscina({ + filename: finishWorkerPath, + maxThreads +}); +const killWorker = new Piscina({ + filename: killWorkerPath, + maxThreads +}); +const casketWorker = new Piscina({ + filename: casketWorkerPath, + maxThreads +}); export const Workers = { casketOpen: (args: CasketWorkerArgs): Promise<[Bank, string]> => casketWorker.run(args), diff --git a/src/lib/workers/kill.worker.ts b/src/lib/workers/kill.worker.ts index 87903cd2762..0f00bdf3135 100644 --- a/src/lib/workers/kill.worker.ts +++ b/src/lib/workers/kill.worker.ts @@ -4,12 +4,15 @@ import '../data/itemAliases'; import { stringMatches } from '@oldschoolgg/toolkit'; import { Bank, Monsters } from 'oldschooljs'; +import type { KillWorkerArgs, KillWorkerReturn } from '.'; import { production } from '../../config'; import { YETI_ID } from '../constants'; import killableMonsters from '../minions/data/killableMonsters/index'; import { simulatedKillables } from '../simulation/simulatedKillables'; -import type { KillWorkerArgs, KillWorkerReturn } from '.'; +if (global.prisma) { + throw new Error('Prisma is loaded in the kill worker!'); +} export default async ({ quantity, bossName, @@ -26,9 +29,7 @@ export default async ({ } if (quantity > limit) { return { - error: - `The quantity you gave exceeds your limit of ${limit.toLocaleString()}! ` + - '*You can increase your limit by up to 1 million by becoming a patron at ' + error: `The quantity you gave exceeds your limit of ${limit.toLocaleString()}! *You can increase your limit by up to 1 million by becoming a patron at ` }; } @@ -43,7 +44,7 @@ export default async ({ }; const killableMonster = killableMonsters.find(mon => mon.id === osjsMonster.id); - if (killableMonster && killableMonster.specialLoot) { + if (killableMonster?.specialLoot) { killableMonster.specialLoot({ ownedItems: result.bank, loot: result.bank, quantity, cl: new Bank() }); } @@ -54,9 +55,7 @@ export default async ({ if (simulatedKillable) { if (quantity > limit) { return { - error: - `The quantity you gave exceeds your limit of ${limit.toLocaleString()}! ` + - '*You can increase your limit by up to 1 million by becoming a patron at ' + error: `The quantity you gave exceeds your limit of ${limit.toLocaleString()}! *You can increase your limit by up to 1 million by becoming a patron at ` }; } diff --git a/src/mahoji/commands/activities.ts b/src/mahoji/commands/activities.ts index c164c129e21..0afcabb6a55 100644 --- a/src/mahoji/commands/activities.ts +++ b/src/mahoji/commands/activities.ts @@ -1,11 +1,10 @@ -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; - -import { - UNDERWATER_AGILITY_THIEVING_TRAINING_SKILL, - UnderwaterAgilityThievingTrainingSkill -} from '../../lib/constants'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType, type User } from 'discord.js'; +import type { UnderwaterAgilityThievingTrainingSkill } from '../../lib/constants'; +import { UNDERWATER_AGILITY_THIEVING_TRAINING_SKILL } from '../../lib/constants'; import { Planks } from '../../lib/minions/data/planks'; import Potions from '../../lib/minions/data/potions'; +import { quests } from '../../lib/minions/data/quests'; import birdhouses from '../../lib/skilling/skills/hunter/birdHouseTrapping'; import { Castables } from '../../lib/skilling/skills/magic/castables'; import { Enchantables } from '../../lib/skilling/skills/magic/enchantables'; @@ -21,7 +20,7 @@ import { championsChallengeCommand } from '../lib/abstracted_commands/championsC import { chargeGloriesCommand } from '../lib/abstracted_commands/chargeGloriesCommand'; import { chargeWealthCommand } from '../lib/abstracted_commands/chargeWealthCommand'; import { chompyHuntClaimCommand, chompyHuntCommand } from '../lib/abstracted_commands/chompyHuntCommand'; -import { collectables, collectCommand } from '../lib/abstracted_commands/collectCommand'; +import { collectCommand } from '../lib/abstracted_commands/collectCommand'; import { decantCommand } from '../lib/abstracted_commands/decantCommand'; import { driftNetCommand } from '../lib/abstracted_commands/driftNetCommand'; import { enchantCommand } from '../lib/abstracted_commands/enchantCommand'; @@ -29,13 +28,14 @@ import { fightCavesCommand } from '../lib/abstracted_commands/fightCavesCommand' import { infernoStartCommand, infernoStatsCommand } from '../lib/abstracted_commands/infernoCommand'; import { otherActivities, otherActivitiesCommand } from '../lib/abstracted_commands/otherActivitiesCommand'; import puroOptions, { puroPuroStartCommand } from '../lib/abstracted_commands/puroPuroCommand'; -import { questCommand, quests } from '../lib/abstracted_commands/questCommand'; +import { questCommand } from '../lib/abstracted_commands/questCommand'; import { sawmillCommand } from '../lib/abstracted_commands/sawmillCommand'; import { scatterCommand } from '../lib/abstracted_commands/scatterCommand'; import { underwaterAgilityThievingCommand } from '../lib/abstracted_commands/underwaterCommand'; import { warriorsGuildCommand } from '../lib/abstracted_commands/warriorsGuildCommand'; +import { collectables } from '../lib/collectables'; import { ownedItemOption } from '../lib/mahojiCommandOptions'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const activitiesCommand: OSBMahojiCommand = { name: 'activities', @@ -181,7 +181,15 @@ export const activitiesCommand: OSBMahojiCommand = { type: ApplicationCommandOptionType.String, name: 'name', description: 'The name of the quest (optional).', - choices: quests.map(i => ({ name: i.name, value: i.name })), + autocomplete: async (_value: string, user: User) => { + const mUser = await mUserFetch(user.id); + let list = quests + .filter(i => !mUser.user.finished_quest_ids.includes(i.id)) + .map(i => ({ name: i.name, value: i.name })); + if (list.length === 0) + list = quests.map(i => ({ name: `${i.name} (completed)`, value: i.name })); + return list; + }, required: false } ] @@ -558,7 +566,7 @@ export const activitiesCommand: OSBMahojiCommand = { return decantCommand(user, options.decant.potion_name, options.decant.dose); } if (options.inferno?.action === 'stats') return infernoStatsCommand(user); - if (options.birdhouses?.action === 'check') return birdhouseCheckCommand(user.user); + if (options.birdhouses?.action === 'check') return birdhouseCheckCommand(user); // Minion must be free const isBusy = user.minionIsBusy; diff --git a/src/mahoji/commands/admin.ts b/src/mahoji/commands/admin.ts index 5dc7f6c69d1..0dfaf11bfde 100644 --- a/src/mahoji/commands/admin.ts +++ b/src/mahoji/commands/admin.ts @@ -1,73 +1,44 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { execSync } from 'node:child_process'; -import { inspect } from 'node:util'; - -import { ClientStorage, economy_transaction_type } from '@prisma/client'; -import { Stopwatch } from '@sapphire/stopwatch'; +import { type CommandRunOptions, bulkUpdateCommands, convertBankToPerHourStats, dateFm } from '@oldschoolgg/toolkit'; +import type { MahojiUserOption } from '@oldschoolgg/toolkit'; +import type { ClientStorage } from '@prisma/client'; +import { economy_transaction_type } from '@prisma/client'; import { Duration } from '@sapphire/time-utilities'; -import { isThenable } from '@sentry/utils'; -import { - AttachmentBuilder, - codeBlock, - escapeCodeBlock, - InteractionReplyOptions, - Message, - TextChannel, - userMention -} from 'discord.js'; -import { calcPercentOfNum, calcWhatPercent, noOp, notEmpty, randArrItem, roll, sleep, Time, uniqueArr } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; -import { MahojiUserOption } from 'mahoji/dist/lib/types'; -import { bulkUpdateCommands } from 'mahoji/dist/lib/util'; +import type { InteractionReplyOptions, Message, TextChannel } from 'discord.js'; +import { AttachmentBuilder, userMention } from 'discord.js'; +import { ApplicationCommandOptionType } from 'discord.js'; +import { Time, calcPercentOfNum, calcWhatPercent, noOp, notEmpty, randArrItem, roll, sleep, uniqueArr } from 'e'; import { Bank } from 'oldschooljs'; -import { ItemBank } from 'oldschooljs/dist/meta/types'; +import type { ItemBank } from 'oldschooljs/dist/meta/types'; -import { ADMIN_IDS, OWNER_IDS, production, SupportServer } from '../../config'; +import { ADMIN_IDS, OWNER_IDS, SupportServer, production } from '../../config'; +import { mahojiUserSettingsUpdate } from '../../lib/MUser'; import { BLACKLISTED_GUILDS, BLACKLISTED_USERS, syncBlacklists } from '../../lib/blacklists'; import { boxFrenzy } from '../../lib/boxFrenzy'; import { - badges, BadgesEnum, BitField, BitFieldData, - BOT_TYPE, - Channel, COINS_ID, + Channel, DISABLED_COMMANDS, - globalConfig, - META_CONSTANTS + META_CONSTANTS, + badges, + globalConfig } from '../../lib/constants'; import { slayerMaskHelms } from '../../lib/data/slayerMaskHelms'; import { addToDoubleLootTimer, syncDoubleLoot } from '../../lib/doubleLoot'; import { economyLog } from '../../lib/economyLogs'; -import { generateGearImage } from '../../lib/gear/functions/generateGearImage'; -import { GearSetup } from '../../lib/gear/types'; +import type { GearSetup } from '../../lib/gear/types'; import { GrandExchange } from '../../lib/grandExchange'; -import { mahojiUserSettingsUpdate } from '../../lib/MUser'; -import { patreonTask } from '../../lib/patreon'; -import { runRolesTask } from '../../lib/rolesTask'; -import { countUsersWithItemInCl, prisma } from '../../lib/settings/prisma'; +import { countUsersWithItemInCl } from '../../lib/settings/prisma'; import { cancelTask, minionActivityCacheDelete } from '../../lib/settings/settings'; import { sorts } from '../../lib/sorts'; -import { Gear } from '../../lib/structures/Gear'; -import { - calcPerHour, - cleanString, - convertBankToPerHourStats, - dateFm, - formatDuration, - sanitizeBank, - stringMatches, - toKMB -} from '../../lib/util'; +import { calcPerHour, cleanString, formatDuration, sanitizeBank, stringMatches, toKMB } from '../../lib/util'; import { memoryAnalysis } from '../../lib/util/cachedUserIDs'; import { mahojiClientSettingsFetch, mahojiClientSettingsUpdate } from '../../lib/util/clientSettings'; import getOSItem, { getItem } from '../../lib/util/getOSItem'; import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; import { deferInteraction, interactionReply } from '../../lib/util/interactionReply'; -import { syncLinkedAccounts } from '../../lib/util/linkedAccountsUtil'; -import { logError } from '../../lib/util/logError'; import { makeBankImage } from '../../lib/util/makeBankImage'; import { parseBank } from '../../lib/util/parseStringBank'; import { slayerMaskLeaderboardCache } from '../../lib/util/slayerMaskLeaderboard'; @@ -76,7 +47,8 @@ import { LampTable } from '../../lib/xpLamps'; import { Cooldowns } from '../lib/Cooldowns'; import { syncCustomPrices } from '../lib/events'; import { itemOption } from '../lib/mahojiCommandOptions'; -import { allAbstractCommands, OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; +import { allAbstractCommands } from '../lib/util'; import { mahojiUsersSettingsFetch } from '../mahojiSettings'; import { getLotteryBank } from './lottery'; @@ -86,68 +58,8 @@ export const gifs = [ 'https://tenor.com/view/monkey-monito-mask-gif-23036908' ]; -async function unsafeEval({ userID, code }: { userID: string; code: string }) { - if (!OWNER_IDS.includes(userID)) return { content: 'Unauthorized' }; - code = code.replace(/[“”]/g, '"').replace(/[‘’]/g, "'"); - const stopwatch = new Stopwatch(); - let syncTime = '?'; - let asyncTime = '?'; - let result = null; - let thenable = false; - // eslint-disable-next-line @typescript-eslint/init-declarations - try { - code = `\nconst {Gear} = require('../../lib/structures/Gear')\n${code};`; - code = `\nconst {Bank} = require('oldschooljs');\n${code}`; - // eslint-disable-next-line no-eval - result = eval(code); - syncTime = stopwatch.toString(); - if (isThenable(result)) { - thenable = true; - stopwatch.restart(); - result = await result; - asyncTime = stopwatch.toString(); - } - } catch (error: any) { - if (!syncTime) syncTime = stopwatch.toString(); - if (thenable && !asyncTime) asyncTime = stopwatch.toString(); - if (error && error.stack) logError(error); - result = error; - } - - stopwatch.stop(); - if (result instanceof Bank) { - return { files: [(await makeBankImage({ bank: result })).file] }; - } - if (result instanceof Gear) { - const image = await generateGearImage(await mUserFetch(userID), result, null, null); - return { files: [image] }; - } - - if (Buffer.isBuffer(result)) { - return { - content: 'The result was a buffer.', - files: [result] - }; - } - - if (typeof result !== 'string') { - result = inspect(result, { - depth: 1, - showHidden: false - }); - } - - return { - content: `${codeBlock(escapeCodeBlock(result))} -**Time:** ${asyncTime ? `⏱ ${asyncTime}<${syncTime}>` : `⏱ ${syncTime}`} -` - }; -} - async function allEquippedPets() { - const pets = await prisma.$queryRawUnsafe< - { pet: number; qty: number }[] - >(`SELECT "minion.equippedPet" AS pet, COUNT("minion.equippedPet")::int AS qty + const pets = await prisma.$queryRawUnsafe<{ pet: number; qty: number }[]>(`SELECT "minion.equippedPet" AS pet, COUNT("minion.equippedPet")::int AS qty FROM users WHERE "minion.equippedPet" IS NOT NULL GROUP BY "minion.equippedPet" @@ -159,25 +71,6 @@ ORDER BY qty DESC;`); return bank; } -async function evalCommand(userID: string, code: string): CommandResponse { - try { - if (!OWNER_IDS.includes(userID)) { - return "You don't have permission to use this command."; - } - const res = await unsafeEval({ code, userID }); - - if (res.content && res.content.length > 2000) { - return { - files: [{ attachment: Buffer.from(res.content), name: 'output.txt' }] - }; - } - - return res; - } catch (err: any) { - return err.message ?? err; - } -} - async function getAllTradedItems(giveUniques = false) { const economyTrans = await prisma.economyTransaction.findMany({ where: { @@ -192,11 +85,11 @@ async function getAllTradedItems(giveUniques = false) { } }); - let total = new Bank(); + const total = new Bank(); if (giveUniques) { for (const trans of economyTrans) { - let bank = new Bank().add(trans.items_received as ItemBank).add(trans.items_sent as ItemBank); + const bank = new Bank().add(trans.items_received as ItemBank).add(trans.items_sent as ItemBank); for (const item of bank.items()) { total.add(item[0].id); @@ -252,10 +145,9 @@ AND ("gear.melee" IS NOT NULL OR const bank = new Bank(); for (const user of res) { for (const gear of Object.values(user) - .map(i => (i === null ? [] : Object.values(i))) - .flat() + .flatMap(i => (i === null ? [] : Object.values(i))) .filter(notEmpty)) { - let item = getItem(gear.item); + const item = getItem(gear.item); if (item) { bank.add(gear.item, gear.quantity); } @@ -325,9 +217,7 @@ AND ("gear.melee" IS NOT NULL OR name: 'Economy Bank', run: async () => { const [blowpipeRes, totalGP, result] = await prisma.$transaction([ - prisma.$queryRawUnsafe< - { scales: number; dart: number; qty: number }[] - >(`SELECT (blowpipe->>'scales')::int AS scales, (blowpipe->>'dartID')::int AS dart, (blowpipe->>'dartQuantity')::int AS qty + prisma.$queryRawUnsafe<{ scales: number; dart: number; qty: number }[]>(`SELECT (blowpipe->>'scales')::int AS scales, (blowpipe->>'dartID')::int AS dart, (blowpipe->>'dartQuantity')::int AS qty FROM users WHERE blowpipe iS NOT NULL and (blowpipe->>'dartQuantity')::int != 0;`), prisma.$queryRawUnsafe<{ sum: number }[]>('SELECT SUM("GP") FROM users;'), @@ -355,7 +245,9 @@ WHERE blowpipe iS NOT NULL and (blowpipe->>'dartQuantity')::int != 0;`), return { files: [ (await makeBankImage({ bank: economyBank })).file, - new AttachmentBuilder(Buffer.from(JSON.stringify(economyBank.bank, null, 4)), { name: 'bank.json' }) + new AttachmentBuilder(Buffer.from(JSON.stringify(economyBank.bank, null, 4)), { + name: 'bank.json' + }) ] }; } @@ -480,18 +372,14 @@ LIMIT { name: 'Sell GP Sources', run: async () => { - const result = await prisma.$queryRawUnsafe< - { item_id: number; gp: number }[] - >(`select item_id, sum(gp_received) as gp + const result = await prisma.$queryRawUnsafe<{ item_id: number; gp: number }[]>(`select item_id, sum(gp_received) as gp from bot_item_sell group by item_id order by gp desc limit 80; `); - const totalGPGivenOut = await prisma.$queryRawUnsafe< - { total_gp_given_out: number }[] - >(`select sum(gp_received) as total_gp_given_out + const totalGPGivenOut = await prisma.$queryRawUnsafe<{ total_gp_given_out: number }[]>(`select sum(gp_received) as total_gp_given_out from bot_item_sell;`); return { @@ -555,19 +443,6 @@ export const adminCommand: OSBMahojiCommand = { name: 'reboot', description: 'Reboot the bot.' }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'eval', - description: 'Eval.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'code', - description: 'Code', - required: true - } - ] - }, { type: ApplicationCommandOptionType.Subcommand, name: 'sync_commands', @@ -631,16 +506,6 @@ export const adminCommand: OSBMahojiCommand = { } ] }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'sync_roles', - description: 'Sync roles' - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'sync_patreon', - description: 'Sync patreon' - }, { type: ApplicationCommandOptionType.Subcommand, name: 'badges', @@ -800,11 +665,6 @@ export const adminCommand: OSBMahojiCommand = { } ] }, - // { - // type: ApplicationCommandOptionType.Subcommand, - // name: 'wipe_bingo_temp_cls', - // description: 'Wipe all temp cls of bingo users' - // }, { type: ApplicationCommandOptionType.Subcommand, name: 'give_items', @@ -875,15 +735,11 @@ export const adminCommand: OSBMahojiCommand = { }: CommandRunOptions<{ reboot?: {}; shut_down?: {}; - debug_patreon?: {}; - eval?: { code: string }; sync_commands?: {}; item_stats?: { item: string }; sync_blacklist?: {}; loot_track?: { name: string }; cancel_task?: { user: MahojiUserOption }; - sync_roles?: {}; - sync_patreon?: {}; badges?: { user: MahojiUserOption; add?: string; remove?: string }; bypass_age?: { user: MahojiUserOption }; command?: { enable?: string; disable?: string }; @@ -892,7 +748,6 @@ export const adminCommand: OSBMahojiCommand = { ltc?: { item?: string }; double_loot?: { reset?: boolean; add?: string }; view?: { thing: string }; - wipe_bingo_temp_cls?: {}; give_items?: { user: MahojiUserOption; items: string; reason?: string }; box_frenzy?: { amount: number }; lamp_frenzy?: { amount: number }; @@ -919,24 +774,6 @@ export const adminCommand: OSBMahojiCommand = { minionActivityCacheDelete(user.id); return 'Done.'; } - if (options.sync_roles) { - try { - const result = await runRolesTask(); - if (result.length < 2000) return result; - return { - content: 'The result was too big! Check the file.', - files: [new AttachmentBuilder(Buffer.from(result), { name: 'roles.txt' })] - }; - } catch (err: any) { - logError(err); - return `Failed to run roles task. ${err.message}`; - } - } - if (options.sync_patreon) { - await patreonTask.run(); - syncLinkedAccounts(); - return 'Finished syncing patrons.'; - } if (options.badges) { if ((!options.badges.remove && !options.badges.add) || (options.badges.add && options.badges.remove)) { @@ -1064,7 +901,7 @@ export const adminCommand: OSBMahojiCommand = { .map(entry => `**${entry[0]}:** ${entry[1]?.name}`) .join('\n'); } - const bit = parseInt(bitEntry[0]); + const bit = Number.parseInt(bitEntry[0]); if ( !bit || @@ -1109,22 +946,25 @@ export const adminCommand: OSBMahojiCommand = { ${META_CONSTANTS.RENDERED_STR}` }).catch(noOp); - process.exit(); + import('exit-hook').then(({ gracefulExit }) => gracefulExit(1)); + return 'Turning off...'; } if (options.shut_down) { + debugLog('SHUTTING DOWN'); globalClient.isShuttingDown = true; - let timer = production ? Time.Second * 30 : Time.Second * 5; + const timer = production ? Time.Second * 30 : Time.Second * 5; await interactionReply(interaction, { content: `Shutting down in ${dateFm(new Date(Date.now() + timer))}.` }); await economyLog('Flushing economy log due to shutdown', true); - await Promise.all([sleep(timer), GrandExchange.queue.onEmpty()]); + await Promise.all([sleep(timer), GrandExchange.queue.onIdle()]); await sendToChannelID(Channel.GeneralChannel, { content: `I am shutting down! Goodbye :( ${META_CONSTANTS.RENDERED_STR}` }).catch(noOp); - execSync(`pm2 stop ${BOT_TYPE === 'OSB' ? 'osb' : 'bso'}`); + import('exit-hook').then(({ gracefulExit }) => gracefulExit(0)); + return 'Turning off...'; } if (options.sync_blacklist) { @@ -1144,7 +984,7 @@ Guilds Blacklisted: ${BLACKLISTED_GUILDS.size}`; if (options.sync_commands) { const global = Boolean(production); - const totalCommands = globalClient.mahojiClient.commands.values; + const totalCommands = Array.from(globalClient.mahojiClient.commands.values()); const globalCommands = totalCommands.filter(i => !i.guildID); const guildCommands = totalCommands.filter(i => Boolean(i.guildID)); if (global) { @@ -1212,13 +1052,6 @@ ${guildCommands.length} Guild commands`; return `Gave ${items} to ${user.mention}`; } - if (options.debug_patreon) { - const result = await patreonTask.fetchPatrons(); - return { - files: [{ attachment: Buffer.from(JSON.stringify(result, null, 4)), name: 'patreon.txt' }] - }; - } - /** * * Owner Only Commands @@ -1228,9 +1061,6 @@ ${guildCommands.length} Guild commands`; return randArrItem(gifs); } - if (options.eval) { - return evalCommand(userID.toString(), options.eval.code); - } if (options.item_stats) { const item = getItem(options.item_stats.item); if (!item) return 'Invalid item.'; @@ -1239,7 +1069,7 @@ ${guildCommands.length} Guild commands`; FROM users WHERE bank->>'${item.id}' IS NOT NULL;`); return `There are ${ownedResult[0].qty.toLocaleString()} ${item.name} owned by everyone. -There are ${await countUsersWithItemInCl(item.id, isIron)} ${isIron ? 'ironmen' : 'people'} with atleast 1 ${ +There are ${await countUsersWithItemInCl(item.id, isIron)} ${isIron ? 'ironmen' : 'people'} with at least 1 ${ item.name } in their collection log.`; } @@ -1320,7 +1150,7 @@ There are ${await countUsersWithItemInCl(item.id, isIron)} ${isIron ? 'ironmen' const marketValueLoot = Math.round(loot.value()); const ratio = marketValueLoot / marketValueCost; - if (!marketValueCost || !marketValueLoot || ratio === Infinity) continue; + if (!marketValueCost || !marketValueLoot || ratio === Number.POSITIVE_INFINITY) continue; str += `${[ res.id, @@ -1352,7 +1182,7 @@ There are ${await countUsersWithItemInCl(item.id, isIron)} ${isIron ? 'ironmen' taxedBank.add('Coins', qty); continue; } - let fivePercent = Math.ceil(calcPercentOfNum(5, qty)); + const fivePercent = Math.ceil(calcPercentOfNum(5, qty)); taxedBank.add(item, Math.max(fivePercent, 1)); } diff --git a/src/mahoji/commands/allCommands.ts b/src/mahoji/commands/allCommands.ts new file mode 100644 index 00000000000..86043d79941 --- /dev/null +++ b/src/mahoji/commands/allCommands.ts @@ -0,0 +1,200 @@ +import { production } from '../../config'; +import type { OSBMahojiCommand } from '../lib/util'; +import { activitiesCommand } from './activities'; +import { adminCommand } from './admin'; +import { askCommand } from './ask'; +import { bankCommand } from './bank'; +import { bingoCommand } from './bingo'; +import { bossrecordCommand } from './bossrecords'; +import { bsCommand } from './bs'; +import { bsoMinigamesCommand } from './bsominigames'; +import { buildCommand } from './build'; +import { buyCommand } from './buy'; +import { caCommand } from './ca'; +import { casketCommand } from './casket'; +import { chooseCommand } from './choose'; +import { chopCommand } from './chop'; +import { collectionLogCommand } from './cl'; +import { claimCommand } from './claim'; +import { clueCommand } from './clue'; +import { completionCommand } from './completion'; +import { configCommand } from './config'; +import { cookCommand } from './cook'; +import { craftCommand } from './craft'; +import { createCommand } from './create'; +import { dataCommand } from './data'; +import { dgCommand } from './dg'; +import { divinationCommand } from './divination'; +import { dropCommand } from './drop'; +import { dropRatesCommand } from './droprates'; +import { fakeCommand } from './fake'; +import { fakepmCommand } from './fakepm'; +import { farmingCommand } from './farming'; +import { finishCommand } from './finish'; +import { fishCommand } from './fish'; +import { fletchCommand } from './fletch'; +import { gambleCommand } from './gamble'; +import { geCommand } from './ge'; +import { gearCommand } from './gear'; +import { gearPresetsCommand } from './gearpresets'; +import { giftCommand } from './gift'; +import { giveawayCommand } from './giveaway'; +import { gpCommand } from './gp'; +import { helpCommand } from './help'; +import { huntCommand } from './hunt'; +import { icCommand } from './ic'; +import { inventionCommand } from './invention'; +import { inviteCommand } from './invite'; +import { minionKCommand } from './k'; +import { kcCommand } from './kc'; +import { kibbleCommand } from './kibble'; +import { killCommand } from './kill'; +import { lapsCommand } from './laps'; +import { leaderboardCommand } from './leaderboard'; +import { bsoLeaguesCommand } from './leagues'; +import { lightCommand } from './light'; +import { lootCommand } from './loot'; +import { lotteryCommand } from './lottery'; +import { mCommand } from './m'; +import { massCommand } from './mass'; +import { megaDuckCommand } from './megaduck'; +import { mineCommand } from './mine'; +import { minigamesCommand } from './minigames'; +import { minionCommand } from './minion'; +import { mixCommand } from './mix'; +import { nurseryCommand } from './nursery'; +import { offerCommand } from './offer'; +import { openCommand } from './open'; +import { paintCommand } from './paint'; +import { patreonCommand } from './patreon'; +import { payCommand } from './pay'; +import { pohCommand } from './poh'; +import { pollCommand } from './poll'; +import { priceCommand } from './price'; +import { raidCommand } from './raid'; +import { ratesCommand } from './rates'; +import { redeemCommand } from './redeem'; +import { rollCommand } from './roll'; +import { rpCommand } from './rp'; +import { runecraftCommand } from './runecraft'; +import { sacrificeCommand } from './sacrifice'; +import { sellCommand } from './sell'; +import { simulateCommand } from './simulate'; +import { slayerCommand } from './slayer'; +import { smeltingCommand } from './smelt'; +import { smithCommand } from './smith'; +import { stealCommand } from './steal'; +import { tamesCommand } from './tames'; +import { testerShopCommand } from './testershop'; +import { testPotatoCommand } from './testpotato'; +import { tksCommand } from './tokkulshop'; +import { toolsCommand } from './tools'; +import { tradeCommand } from './trade'; +import { triviaCommand } from './trivia'; +import { mahojiUseCommand } from './use'; + +export const allCommands: OSBMahojiCommand[] = [ + adminCommand, + askCommand, + bsCommand, + buildCommand, + buyCommand, + caCommand, + chooseCommand, + chopCommand, + cookCommand, + clueCommand, + configCommand, + claimCommand, + mCommand, + gpCommand, + payCommand, + craftCommand, + fishCommand, + farmingCommand, + dropCommand, + createCommand, + activitiesCommand, + dataCommand, + fakeCommand, + fakepmCommand, + fletchCommand, + gambleCommand, + gearCommand, + giveawayCommand, + helpCommand, + huntCommand, + giftCommand, + inviteCommand, + kcCommand, + minionKCommand, + lapsCommand, + leaderboardCommand, + lightCommand, + mineCommand, + massCommand, + minigamesCommand, + minionCommand, + simulateCommand, + sellCommand, + sacrificeCommand, + rollCommand, + runecraftCommand, + raidCommand, + pollCommand, + pohCommand, + priceCommand, + openCommand, + offerCommand, + mixCommand, + lootCommand, + smeltingCommand, + slayerCommand, + redeemCommand, + patreonCommand, + smithCommand, + stealCommand, + tradeCommand, + triviaCommand, + toolsCommand, + tksCommand, + mahojiUseCommand, + bingoCommand, + bankCommand, + bossrecordCommand, + casketCommand, + finishCommand, + killCommand, + geCommand, + rpCommand, + collectionLogCommand, + gearPresetsCommand, + bsoMinigamesCommand, + completionCommand, + dgCommand, + divinationCommand, + dropRatesCommand, + icCommand, + inventionCommand, + kibbleCommand, + lotteryCommand, + megaDuckCommand, + nurseryCommand, + paintCommand, + ratesCommand, + tamesCommand, + testerShopCommand, + bsoLeaguesCommand +]; + +if (!production && testPotatoCommand) { + allCommands.push(testPotatoCommand); +} + +const names = new Set(); +for (const cmd of allCommands) { + if (names.has(cmd.name)) { + throw new Error(`Duplicate command name: ${cmd.name}`); + } + names.add(cmd.name); +} diff --git a/src/mahoji/commands/ask.ts b/src/mahoji/commands/ask.ts index 6bb25be7c67..464306e37b1 100644 --- a/src/mahoji/commands/ask.ts +++ b/src/mahoji/commands/ask.ts @@ -1,7 +1,8 @@ +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { randArrItem } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const askCommand: OSBMahojiCommand = { name: 'ask', diff --git a/src/mahoji/commands/bank.ts b/src/mahoji/commands/bank.ts index 917c3055655..f688b1c2484 100644 --- a/src/mahoji/commands/bank.ts +++ b/src/mahoji/commands/bank.ts @@ -1,19 +1,22 @@ -import { codeBlock, EmbedBuilder } from 'discord.js'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { EmbedBuilder, codeBlock } from 'discord.js'; +import { ApplicationCommandOptionType } from 'discord.js'; import { chunk } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; -import { Bank } from 'oldschooljs'; +import type { Bank } from 'oldschooljs'; -import { BankFlag, bankFlags } from '../../lib/bankImage'; -import { Emoji, PerkTier } from '../../lib/constants'; -import { Flags } from '../../lib/minions/types'; import { PaginatedMessage } from '../../lib/PaginatedMessage'; -import { BankSortMethod, BankSortMethods } from '../../lib/sorts'; +import type { BankFlag } from '../../lib/bankImage'; +import { bankFlags } from '../../lib/bankImage'; +import { Emoji, PerkTier } from '../../lib/constants'; +import type { Flags } from '../../lib/minions/types'; +import type { BankSortMethod } from '../../lib/sorts'; +import { BankSortMethods } from '../../lib/sorts'; import { channelIsSendable, makePaginatedMessage } from '../../lib/util'; import { deferInteraction } from '../../lib/util/interactionReply'; import { makeBankImage } from '../../lib/util/makeBankImage'; import { parseBank } from '../../lib/util/parseStringBank'; import { filterOption, itemOption } from '../lib/mahojiCommandOptions'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; const bankFormats = ['json', 'text_paged', 'text_full'] as const; const bankItemsPerPage = 10; @@ -191,7 +194,7 @@ export const bankCommand: OSBMahojiCommand = { return `${codeBlock('json', json)}`; } - let flags: Flags = { + const flags: Flags = { page: options.page - 1 }; if (options.sort) flags.sort = options.sort; diff --git a/src/mahoji/commands/bingo.ts b/src/mahoji/commands/bingo.ts index 5b7f2a78512..9404f0ccd4d 100644 --- a/src/mahoji/commands/bingo.ts +++ b/src/mahoji/commands/bingo.ts @@ -1,26 +1,37 @@ -import { formatOrdinal, mentionCommand, stringMatches, truncateString } from '@oldschoolgg/toolkit'; -import { Prisma } from '@prisma/client'; -import { bold, ChatInputCommandInteraction, User, userMention } from 'discord.js'; -import { chunk, noOp, notEmpty, Time, uniqueArr } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; -import { MahojiUserOption } from 'mahoji/dist/lib/types'; +import { dateFm, formatOrdinal, mentionCommand, stringMatches, truncateString } from '@oldschoolgg/toolkit'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import type { CommandResponse } from '@oldschoolgg/toolkit'; +import type { MahojiUserOption } from '@oldschoolgg/toolkit'; +import type { Prisma } from '@prisma/client'; +import type { ChatInputCommandInteraction, User } from 'discord.js'; +import { bold, userMention } from 'discord.js'; +import { ApplicationCommandOptionType } from 'discord.js'; +import { Time, chunk, noOp, notEmpty, uniqueArr } from 'e'; import { Bank } from 'oldschooljs'; -import { ItemBank } from 'oldschooljs/dist/meta/types'; +import type { ItemBank } from 'oldschooljs/dist/meta/types'; import { production } from '../../config'; import { BLACKLISTED_USERS } from '../../lib/blacklists'; import { clImageGenerator } from '../../lib/collectionLogTask'; -import { BOT_TYPE, Emoji, usernameCache } from '../../lib/constants'; -import { prisma } from '../../lib/settings/prisma'; -import { channelIsSendable, dateFm, isValidDiscordSnowflake, isValidNickname, md5sum, toKMB } from '../../lib/util'; +import { BOT_TYPE, Emoji } from '../../lib/constants'; + +import { + channelIsSendable, + getUsername, + getUsernameSync, + isValidDiscordSnowflake, + isValidNickname, + md5sum, + toKMB +} from '../../lib/util'; import { getItem } from '../../lib/util/getOSItem'; import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; import { parseBank } from '../../lib/util/parseStringBank'; import { BingoManager, BingoTrophies } from '../lib/bingo/BingoManager'; -import { generateTileName, getAllTileItems, isGlobalTile, StoredBingoTile } from '../lib/bingo/bingoUtil'; +import type { StoredBingoTile } from '../lib/bingo/bingoUtil'; +import { generateTileName, getAllTileItems, isGlobalTile } from '../lib/bingo/bingoUtil'; import { globalBingoTiles } from '../lib/bingo/globalTiles'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; import { doMenu, getPos } from './leaderboard'; const bingoAutocomplete = async (value: string, user: User) => { @@ -83,7 +94,7 @@ async function bingoTeamLeaderboard( .map( (team, j) => `${getPos(i, j)}** ${`${team.trophy?.emoji} ` ?? ''}${team.participants - .map(pt => usernameCache.get(pt.user_id) ?? '?') + .map(pt => getUsernameSync(pt.user_id)) .join(', ')}:** ${team.tilesCompletedCount.toLocaleString()}` ) .join('\n') @@ -244,10 +255,10 @@ async function getBingoFromUserInput(input: string) { const where = Number.isNaN(Number(input)) ? { title: input - } + } : { id: Number(input) - }; + }; const bingo = await prisma.bingo.findFirst({ where }); @@ -495,8 +506,7 @@ export const bingoCommand: OSBMahojiCommand = { } }); return bingos - .map(b => new BingoManager(b).bingoTiles) - .flat() + .flatMap(b => new BingoManager(b).bingoTiles) .filter(b => b.name.toLowerCase().includes(value.toLowerCase())) .map(b => ({ name: truncateString(b.name, 100), @@ -589,7 +599,7 @@ export const bingoCommand: OSBMahojiCommand = { if (options.items) { const bingoID = Number(options.items.bingo); - if (isNaN(bingoID)) { + if (Number.isNaN(bingoID)) { return 'Invalid bingo.'; } const bingoParticipant = await prisma.bingoParticipant.findFirst({ @@ -664,7 +674,7 @@ export const bingoCommand: OSBMahojiCommand = { const fee = BOT_TYPE === 'OSB' ? 20_000_000 : 50_000_000; const creationCost = new Bank().add('Coins', fee); if (user.GP < creationCost.amount('Coins')) { - return `You need atleast ${creationCost} to create a bingo.`; + return `You need at least ${creationCost} to create a bingo.`; } const channel = globalClient.channels.cache.get(options.create_bingo.notifications_channel_id); @@ -702,9 +712,9 @@ export const bingoCommand: OSBMahojiCommand = { return 'Team size must be between 1 and 5.'; } - // Start date must be atleast 3 hours into the future + // Start date must be at least 3 hours into the future if (createOptions.start_date.getTime() < Date.now() + Time.Minute * 3) { - return 'Start date must be atleast 3 minutes into the future.'; + return 'Start date must be at least 3 minutes into the future.'; } // Start date cannot be more than 31 days into the future @@ -802,9 +812,7 @@ The creator of the bingo (${userMention( teams .map(team => [ - team.participants - .map(u => usernameCache.get(u.user_id) ?? u.user_id) - .join(','), + team.participants.map(u => getUsernameSync(u.user_id)).join(','), team.tilesCompletedCount, team.trophy?.item.name ?? 'No Trophy' ].join('\t') @@ -827,7 +835,7 @@ The creator of the bingo (${userMention( if (bingo.isActive()) { return "You can't add tiles to a bingo after it has started."; } - const globalTile = globalBingoTiles.find(t => stringMatches(t.id, options.manage_bingo!.add_tile)); + const globalTile = globalBingoTiles.find(t => stringMatches(t.id, options.manage_bingo?.add_tile)); let tileToAdd: StoredBingoTile | null = null; if (globalTile) { tileToAdd = { global: globalTile.id }; @@ -858,7 +866,7 @@ Example: \`add_tile:Coal|Trout|Egg\` is a tile where you have to receive a coal return "You can't remove tiles to a bingo after it has started."; } let newTiles = [...bingo.rawBingoTiles]; - const globalTile = globalBingoTiles.find(t => stringMatches(t.id, options.manage_bingo!.remove_tile)); + const globalTile = globalBingoTiles.find(t => stringMatches(t.id, options.manage_bingo?.remove_tile)); let tileName = ''; if (globalTile) { newTiles = newTiles.filter( @@ -867,11 +875,11 @@ Example: \`add_tile:Coal|Trout|Egg\` is a tile where you have to receive a coal tileName = generateTileName(globalTile); } else { const tileToRemove = newTiles.find( - t => md5sum(generateTileName(t)) === options.manage_bingo!.remove_tile + t => md5sum(generateTileName(t)) === options.manage_bingo?.remove_tile ); if (tileToRemove) { newTiles = newTiles.filter( - t => md5sum(generateTileName(t)) !== options.manage_bingo!.remove_tile! + t => md5sum(generateTileName(t)) !== options.manage_bingo?.remove_tile! ); tileName = generateTileName(tileToRemove); } @@ -899,13 +907,13 @@ Example: \`add_tile:Coal|Trout|Egg\` is a tile where you have to receive a coal if (options.manage_bingo.add_extra_gp) { const amount = Number(options.manage_bingo.add_extra_gp); - if (isNaN(amount) || amount < 1) { + if (Number.isNaN(amount) || amount < 1) { return 'Invalid amount.'; } const cost = new Bank().add('Coins', amount); if (user.GP < cost.amount('Coins')) { - return `You need atleast ${cost} to add that much GP to the prize pool.`; + return `You need at least ${cost} to add that much GP to the prize pool.`; } await handleMahojiConfirmation( @@ -944,22 +952,20 @@ Example: \`add_tile:Coal|Trout|Egg\` is a tile where you have to receive a coal ); for (const userID of team.participants.map(t => t.user_id)) { - const reclaimableItems: Prisma.ReclaimableItemCreateManyInput[] = trophiesToReceive.map( - trophy => ({ + const reclaimableItems: Prisma.ReclaimableItemCreateManyInput[] = await Promise.all( + trophiesToReceive.map(async trophy => ({ name: `Bingo Trophy (${trophy.item.name})`, quantity: 1, key: `bso-bingo-2-${trophy.item.id}`, item_id: trophy.item.id, description: `Awarded for placing in the top ${trophy.percentile}% of ${ bingo.title - }. Your team (${team.participants - .map(t => usernameCache.get(t.user_id) ?? t.user_id) - .join(', ')}) placed ${formatOrdinal(team.rank)} with ${ + }. Your team (${(await Promise.all(team.participants.map(async t => await getUsername(t.user_id)))).join(', ')}) placed ${formatOrdinal(team.rank)} with ${ team.tilesCompletedCount } tiles completed.`, date: bingo.endDate.toISOString(), user_id: userID - }) + })) ); toInsert.push(...reclaimableItems); } @@ -1003,9 +1009,7 @@ ${yourTeam.bingoTableStr}` } } - let str = `**${bingo.title}** ${teams.length} teams, ${toKMB( - await bingo.countTotalGPInPrizePool() - )} GP Prize Pool + const str = `**${bingo.title}** ${teams.length} teams, ${toKMB(await bingo.countTotalGPInPrizePool())} GP Prize Pool **Start:** ${dateFm(bingo.startDate)} **Finish:** ${dateFm(bingo.endDate)} ${progressString} diff --git a/src/mahoji/commands/bossrecords.ts b/src/mahoji/commands/bossrecords.ts index ca41241f3b5..f6466eeafb8 100644 --- a/src/mahoji/commands/bossrecords.ts +++ b/src/mahoji/commands/bossrecords.ts @@ -1,15 +1,17 @@ import { toTitleCase } from '@oldschoolgg/toolkit'; -import { EmbedBuilder, MessageEditOptions } from 'discord.js'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import type { MessageEditOptions } from 'discord.js'; +import { EmbedBuilder } from 'discord.js'; +import { ApplicationCommandOptionType } from 'discord.js'; import { chunk } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; import { Hiscores } from 'oldschooljs'; import { bossNameMap } from 'oldschooljs/dist/constants'; -import { BossRecords } from 'oldschooljs/dist/meta/types'; +import type { BossRecords } from 'oldschooljs/dist/meta/types'; import pets from '../../lib/data/pets'; import { channelIsSendable, makePaginatedMessage } from '../../lib/util'; import { deferInteraction } from '../../lib/util/interactionReply'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; // Emojis for bosses with no pets const miscEmojis = { @@ -29,7 +31,7 @@ function getEmojiForBoss(key: MiscEmojisKeys | string) { return miscEmojis[key as MiscEmojisKeys]; } - const pet = pets.find(_pet => _pet.bossKeys && _pet.bossKeys.includes(key)); + const pet = pets.find(_pet => _pet.bossKeys?.includes(key)); if (pet) return pet.emoji; } diff --git a/src/mahoji/commands/bs.ts b/src/mahoji/commands/bs.ts index 548ae45b1f2..d3f53ab971a 100644 --- a/src/mahoji/commands/bs.ts +++ b/src/mahoji/commands/bs.ts @@ -1,6 +1,7 @@ -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; import { bankCommand } from './bank'; const bankFormats = ['json', 'text_paged', 'text_full'] as const; diff --git a/src/mahoji/commands/bsominigames.ts b/src/mahoji/commands/bsominigames.ts index 8e8271bf880..967655e8682 100644 --- a/src/mahoji/commands/bsominigames.ts +++ b/src/mahoji/commands/bsominigames.ts @@ -1,19 +1,21 @@ import { toTitleCase } from '@oldschoolgg/toolkit'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { BathhouseOres, - bathHouseTiers, BathwaterMixtures, + bathHouseTiers, baxBathHelpStr, baxBathSim, baxtorianBathhousesStartCommand } from '../../lib/baxtorianBathhouses'; import { allGodlyItems, divineDominionCheck, divineDominionSacrificeCommand } from '../../lib/bso/divineDominion'; import { joinGuthixianCache } from '../../lib/bso/guthixianCache'; -import { TuraelsTrialsMethod, TuraelsTrialsMethods, turaelsTrialsStartCommand } from '../../lib/bso/turaelsTrials'; +import { type TuraelsTrialsMethod, TuraelsTrialsMethods, turaelsTrialsStartCommand } from '../../lib/bso/turaelsTrials'; import { fishingLocations } from '../../lib/fishingContest'; -import { MaterialType } from '../../lib/invention'; +import type { MaterialType } from '../../lib/invention'; +import { itemNameFromID } from '../../lib/util'; import { bonanzaCommand } from '../lib/abstracted_commands/bonanzaCommand'; import { fishingContestStartCommand, @@ -22,17 +24,17 @@ import { import { fistOfGuthixCommand } from '../lib/abstracted_commands/fistOfGuthix'; import { monkeyRumbleCommand, monkeyRumbleStatsCommand } from '../lib/abstracted_commands/monkeyRumbleCommand'; import { + OuraniaBuyables, odsBuyCommand, odsStartCommand, - odsStatsCommand, - OuraniaBuyables + odsStatsCommand } from '../lib/abstracted_commands/odsCommand'; import { stealingCreationCommand } from '../lib/abstracted_commands/stealingCreation'; import { tinkeringWorkshopCommand } from '../lib/abstracted_commands/tinkeringWorkshopCommand'; import { itemOption, ownedMaterialOption } from '../lib/mahojiCommandOptions'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; -export const minigamesCommand: OSBMahojiCommand = { +export const bsoMinigamesCommand: OSBMahojiCommand = { name: 'bsominigames', description: 'Send your minion to do various bso minigames.', options: [ @@ -79,7 +81,7 @@ export const minigamesCommand: OSBMahojiCommand = { description: 'The herbs you want to use for your water mixture.', required: true, choices: BathwaterMixtures.map(i => ({ - name: `${i.name} (${i.items.map(i => i.name).join(', ')})`, + name: `${i.name} (${i.items.map(itemNameFromID).join(', ')})`, value: i.name })) } diff --git a/src/mahoji/commands/build.ts b/src/mahoji/commands/build.ts index 61d830fbdc8..43efd01632b 100644 --- a/src/mahoji/commands/build.ts +++ b/src/mahoji/commands/build.ts @@ -1,18 +1,17 @@ -import { stringMatches } from '@oldschoolgg/toolkit'; -import { User } from 'discord.js'; -import { reduceNumByPercent, round, Time } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import { type CommandRunOptions, stringMatches } from '@oldschoolgg/toolkit'; import { Bank } from 'oldschooljs'; -import { inventionBoosts, InventionID, inventionItemBoost } from '../../lib/invention/inventions'; +import { ApplicationCommandOptionType, type User } from 'discord.js'; +import { Time, reduceNumByPercent, round } from 'e'; +import { InventionID, inventionBoosts, inventionItemBoost } from '../../lib/invention/inventions'; import Constructables from '../../lib/skilling/skills/construction/constructables'; -import { Skills } from '../../lib/types'; -import { ConstructionActivityTaskOptions } from '../../lib/types/minions'; +import type { Skills } from '../../lib/types'; +import type { ConstructionActivityTaskOptions } from '../../lib/types/minions'; import { formatDuration, hasSkillReqs } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; import { updateBankSetting } from '../../lib/util/updateBankSetting'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; const ds2Requirements: Skills = { magic: 75, @@ -107,7 +106,7 @@ export const buildCommand: OSBMahojiCommand = { const maxTripLength = calcMaxTripLength(user, 'Construction'); const maxForMaterials = planksHas / planksQtyCost; - let boosts: string[] = []; + const boosts: string[] = []; const boostedActionTime = reduceNumByPercent( timeToBuildSingleObject, @@ -132,7 +131,7 @@ export const buildCommand: OSBMahojiCommand = { } const maxForTime = Math.floor(maxTripLength / timeToBuildSingleObject); - let defaultQuantity = Math.floor(Math.min(maxForTime, Math.max(maxForMaterials, 1))); + const defaultQuantity = Math.floor(Math.min(maxForTime, Math.max(maxForMaterials, 1))); let { quantity } = options; if (!quantity) quantity = defaultQuantity; diff --git a/src/mahoji/commands/buy.ts b/src/mahoji/commands/buy.ts index b626692f3d1..6ba46e1b620 100644 --- a/src/mahoji/commands/buy.ts +++ b/src/mahoji/commands/buy.ts @@ -1,13 +1,15 @@ -import { formatOrdinal } from '@oldschoolgg/toolkit'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; import { bold } from 'discord.js'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import { ApplicationCommandOptionType } from 'discord.js'; import { Bank } from 'oldschooljs'; -import { ItemBank } from 'oldschooljs/dist/meta/types'; +import type { ItemBank } from 'oldschooljs/dist/meta/types'; +import { formatOrdinal } from '@oldschoolgg/toolkit'; import { Events } from '../../lib/constants'; import Buyables from '../../lib/data/buyables/buyables'; -import { getMinigameScore, Minigames } from '../../lib/settings/minigames'; -import { countUsersWithItemInCl, prisma } from '../../lib/settings/prisma'; +import { quests } from '../../lib/minions/data/quests'; +import { Minigames, getMinigameScore } from '../../lib/settings/minigames'; +import { countUsersWithItemInCl } from '../../lib/settings/prisma'; import { isElligibleForPresent } from '../../lib/settings/settings'; import { MUserStats } from '../../lib/structures/MUserStats'; import { formatSkillRequirements, itemID, itemNameFromID, stringMatches } from '../../lib/util'; @@ -17,9 +19,8 @@ import { deferInteraction } from '../../lib/util/interactionReply'; import { updateBankSetting } from '../../lib/util/updateBankSetting'; import { buyFossilIslandNotes } from '../lib/abstracted_commands/buyFossilIslandNotes'; import { buyKitten } from '../lib/abstracted_commands/buyKitten'; -import { quests } from '../lib/abstracted_commands/questCommand'; -import { OSBMahojiCommand } from '../lib/util'; -import { mahojiParseNumber, multipleUserStatsBankUpdate } from '../mahojiSettings'; +import type { OSBMahojiCommand } from '../lib/util'; +import { mahojiParseNumber, userStatsUpdate } from '../mahojiSettings'; const allBuyablesAutocomplete = [...Buyables, { name: 'Kitten' }, { name: 'Fossil Island Notes' }]; @@ -57,9 +58,7 @@ export const buyCommand: OSBMahojiCommand = { } const buyable = Buyables.find( - item => - stringMatches(name, item.name) || - (item.aliases && item.aliases.some(alias => stringMatches(alias, name))) + item => stringMatches(name, item.name) || item.aliases?.some(alias => stringMatches(alias, name)) ); if (!buyable) return "That's not a valid item you can buy."; @@ -114,7 +113,7 @@ export const buyCommand: OSBMahojiCommand = { } if (kc < req) { return `You need ${req} KC in ${ - Minigames.find(i => i.column === key)!.name + Minigames.find(i => i.column === key)?.name } to buy this, you only have ${kc} KC.`; } } @@ -147,12 +146,12 @@ export const buyCommand: OSBMahojiCommand = { return `You don't have the required items to purchase this. You need: ${totalCost}.`; } - let singleOutput: Bank = + const singleOutput: Bank = buyable.outputItems === undefined ? new Bank().add(buyable.name) : buyable.outputItems instanceof Bank - ? buyable.outputItems - : buyable.outputItems(user); + ? buyable.outputItems + : buyable.outputItems(user); const outItems = singleOutput.clone().multiply(quantity); @@ -166,7 +165,7 @@ export const buyCommand: OSBMahojiCommand = { buyable.globalAnnouncementOnFirstBuy && !user.cl.has(buyable.name) ) { - let [count, ironCount] = await Promise.all([ + const [count, ironCount] = await Promise.all([ countUsersWithItemInCl(itemID(buyable.name), false), countUsersWithItemInCl(itemID(buyable.name), true) ]); @@ -194,12 +193,13 @@ export const buyCommand: OSBMahojiCommand = { .remove('Coins', totalCost.amount('Coins')).bank; if (Object.keys(costBankExcludingGP).length === 0) costBankExcludingGP = undefined; + const currentStats = await user.fetchStats({ buy_cost_bank: true, buy_loot_bank: true }); await Promise.all([ updateBankSetting('buy_cost_bank', totalCost), updateBankSetting('buy_loot_bank', outItems), - multipleUserStatsBankUpdate(user.id, { - buy_cost_bank: totalCost, - buy_loot_bank: outItems + userStatsUpdate(user.id, { + buy_cost_bank: totalCost.clone().add(currentStats.buy_cost_bank as ItemBank).bank, + buy_loot_bank: outItems.clone().add(currentStats.buy_loot_bank as ItemBank).bank }), prisma.buyCommandTransaction.create({ data: { diff --git a/src/mahoji/commands/ca.ts b/src/mahoji/commands/ca.ts index eb1c298a1c4..508bd74c1eb 100644 --- a/src/mahoji/commands/ca.ts +++ b/src/mahoji/commands/ca.ts @@ -1,19 +1,21 @@ import { mentionCommand } from '@oldschoolgg/toolkit'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { calcWhatPercent, objectEntries } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; import { Bank } from 'oldschooljs'; import { buildCombatAchievementsResult } from '../../lib/combat_achievements/caUtils'; +import type { CombatAchievement } from '../../lib/combat_achievements/combatAchievements'; import { + CombatAchievements, allCAMonsterNames, allCombatAchievementTasks, caToPlayerString, - CombatAchievement, - CombatAchievements, nextCATier } from '../../lib/combat_achievements/combatAchievements'; +import { Requirements } from '../../lib/structures/Requirements'; import { deferInteraction } from '../../lib/util/interactionReply'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; const viewTypes = ['all', 'incomplete', 'complete'] as const; @@ -93,11 +95,10 @@ export const caCommand: OSBMahojiCommand = { .filter(i => !('rng' in i)); const completedTasks: CombatAchievement[] = []; + const reqData = await Requirements.fetchRequiredData(user); for (const task of tasksToCheck) { - if ('rng' in task) { - continue; - } else if ('requirements' in task) { - const { hasAll } = await task.requirements.check(user); + if ('requirements' in task) { + const { hasAll } = task.requirements.check(reqData); if (hasAll) { completedTasks.push(task); } @@ -135,12 +136,12 @@ export const caCommand: OSBMahojiCommand = { } if (options.view) { - let selectedMonster = options.view.name; - let tasksView: CAViewType = options.view.type !== undefined ? options.view.type : 'all'; + const selectedMonster = options.view.name; + const tasksView: CAViewType = options.view.type !== undefined ? options.view.type : 'all'; if (selectedMonster) { const tasksForSelectedMonster = allCombatAchievementTasks.filter( - task => task.monster.toLowerCase() === selectedMonster!.toLowerCase() + task => task.monster.toLowerCase() === selectedMonster?.toLowerCase() ); if (tasksForSelectedMonster.length === 0) diff --git a/src/mahoji/commands/casket.ts b/src/mahoji/commands/casket.ts index 1efde20f811..cb1356df959 100644 --- a/src/mahoji/commands/casket.ts +++ b/src/mahoji/commands/casket.ts @@ -1,4 +1,5 @@ -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { Bank } from 'oldschooljs'; import { toKMB } from 'oldschooljs/dist/util'; @@ -9,7 +10,7 @@ import { calcDropRatesFromBankWithoutUniques } from '../../lib/util/calcDropRate import { deferInteraction } from '../../lib/util/interactionReply'; import { makeBankImage } from '../../lib/util/makeBankImage'; import { Workers } from '../../lib/workers'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; function determineLimit(user: MUser) { const perkTier = user.perkTier(); @@ -73,9 +74,7 @@ export const casketCommand: OSBMahojiCommand = { return { content: `You opened ${options.quantity} ${clueTier.name} caskets. **Bot Value:** ${toKMB(loot.value())} (Average of ${toKMB(loot.value() / options.quantity)} per casket) -**Market Value:** ${toKMB(marketPriceOfBank(loot))} (Average of ${toKMB( - marketPriceOfBank(loot) / options.quantity - )} per casket) +**Market Value:** ${toKMB(marketPriceOfBank(loot))} (Average of ${toKMB(marketPriceOfBank(loot) / options.quantity)} per casket) **Droprates:** ${calcDropRatesFromBankWithoutUniques(loot, options.quantity).slice(0, 20).join(', ')}`, files: [image.file] diff --git a/src/mahoji/commands/choose.ts b/src/mahoji/commands/choose.ts index dc7607bf0d9..cb69b5e43c4 100644 --- a/src/mahoji/commands/choose.ts +++ b/src/mahoji/commands/choose.ts @@ -1,8 +1,9 @@ +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; import { inlineCode } from 'discord.js'; +import { ApplicationCommandOptionType } from 'discord.js'; import { randArrItem } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const chooseCommand: OSBMahojiCommand = { name: 'choose', diff --git a/src/mahoji/commands/chop.ts b/src/mahoji/commands/chop.ts index 895654f2f73..df7d8dc8be2 100644 --- a/src/mahoji/commands/chop.ts +++ b/src/mahoji/commands/chop.ts @@ -1,19 +1,20 @@ +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { increaseNumByPercent, reduceNumByPercent } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; -import { IVY_MAX_TRIP_LENGTH_BOOST, TwitcherGloves, TWITCHERS_GLOVES } from '../../lib/constants'; +import { IVY_MAX_TRIP_LENGTH_BOOST, TWITCHERS_GLOVES, type TwitcherGloves } from '../../lib/constants'; import { InventionID, inventionItemBoost } from '../../lib/invention/inventions'; import { determineWoodcuttingTime } from '../../lib/skilling/functions/determineWoodcuttingTime'; import Woodcutting from '../../lib/skilling/skills/woodcutting/woodcutting'; -import { WoodcuttingActivityTaskOptions } from '../../lib/types/minions'; +import type { WoodcuttingActivityTaskOptions } from '../../lib/types/minions'; import { formatDuration, itemNameFromID, randomVariation, stringMatches } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import itemID from '../../lib/util/itemID'; import { minionName } from '../../lib/util/minionUtils'; import resolveItems from '../../lib/util/resolveItems'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; -export const axes = [ +const axes = [ { id: itemID('Dwarven greataxe'), multiplier: 8, @@ -164,6 +165,7 @@ export const chopCommand: OSBMahojiCommand = { let wcLvl = skills.woodcutting; const farmingLvl = user.skillsAsLevels.farming; + const pekyBoost = user.usingPet('Peky'); // Ivy, Redwood logs, Logs, Sulliuscep, Farming patches, Woodcutting guild don't spawn forestry events if ( @@ -188,7 +190,11 @@ export const chopCommand: OSBMahojiCommand = { } } } else { - boosts.push('Participating in Forestry events'); + boosts.push( + `Participating in Forestry events${ + pekyBoost ? " (uniques are 5x as common thanks to Peky's help)" : '' + }` + ); } // Default bronze axe, last in the array @@ -196,7 +202,7 @@ export const chopCommand: OSBMahojiCommand = { boosts.push(`**${axeMultiplier}x** success multiplier for Bronze axe`); if (user.hasEquippedOrInBank(['Drygore axe'])) { - let [predeterminedTotalTime] = determineWoodcuttingTime({ + const [predeterminedTotalTime] = determineWoodcuttingTime({ quantity, user, log, @@ -261,7 +267,7 @@ export const chopCommand: OSBMahojiCommand = { } // Calculate the time it takes to chop specific quantity or as many as possible - let [timeToChop, newQuantity] = determineWoodcuttingTime({ + const [timeToChop, newQuantity] = determineWoodcuttingTime({ quantity, user, log, diff --git a/src/mahoji/commands/cl.ts b/src/mahoji/commands/cl.ts index 0271c0b3960..4df8a175bc2 100644 --- a/src/mahoji/commands/cl.ts +++ b/src/mahoji/commands/cl.ts @@ -1,16 +1,13 @@ import { toTitleCase } from '@oldschoolgg/toolkit'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; -import { - clImageGenerator, - CollectionLogFlags, - CollectionLogType, - collectionLogTypes -} from '../../lib/collectionLogTask'; +import type { CollectionLogType } from '../../lib/collectionLogTask'; +import { CollectionLogFlags, clImageGenerator, collectionLogTypes } from '../../lib/collectionLogTask'; import { allCollectionLogs } from '../../lib/data/Collections'; -import { fetchStatsForCL } from '../../lib/util'; +import { MUserStats } from '../../lib/structures/MUserStats'; import { deferInteraction } from '../../lib/util/interactionReply'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const collectionLogCommand: OSBMahojiCommand = { name: 'cl', @@ -48,21 +45,30 @@ export const collectionLogCommand: OSBMahojiCommand = { name: 'type', description: 'The type of log you want to see.', required: false, - choices: collectionLogTypes.map(i => ({ name: `${toTitleCase(i.name)} (${i.description})`, value: i.name })) + choices: collectionLogTypes.map(i => ({ + name: `${toTitleCase(i.name)} (${i.description})`, + value: i.name + })) }, { type: ApplicationCommandOptionType.String, name: 'flag', description: 'The flag you want to pass.', required: false, - choices: CollectionLogFlags.map(i => ({ name: `${toTitleCase(i.name)} (${i.description})`, value: i.name })) + choices: CollectionLogFlags.map(i => ({ + name: `${toTitleCase(i.name)} (${i.description})`, + value: i.name + })) }, { type: ApplicationCommandOptionType.String, name: 'flag_extra', description: 'An additional flag you want to pass.', required: false, - choices: CollectionLogFlags.map(i => ({ name: `${toTitleCase(i.name)} (${i.description})`, value: i.name })) + choices: CollectionLogFlags.map(i => ({ + name: `${toTitleCase(i.name)} (${i.description})`, + value: i.name + })) }, { type: ApplicationCommandOptionType.Boolean, @@ -84,7 +90,7 @@ export const collectionLogCommand: OSBMahojiCommand = { }>) => { await deferInteraction(interaction); const user = await mUserFetch(userID); - let flags: Record = {}; + const flags: Record = {}; if (options.flag) flags[options.flag] = options.flag; if (options.flag_extra) flags[options.flag_extra] = options.flag_extra; if (options.all) flags.all = 'all'; @@ -93,7 +99,7 @@ export const collectionLogCommand: OSBMahojiCommand = { type: options.type ?? 'collection', flags, collection: options.name, - stats: await fetchStatsForCL(user) + stats: await MUserStats.fromID(user.id) }); return result; } diff --git a/src/mahoji/commands/claim.ts b/src/mahoji/commands/claim.ts index 5fcffe4a836..e6ad7c3265c 100644 --- a/src/mahoji/commands/claim.ts +++ b/src/mahoji/commands/claim.ts @@ -1,14 +1,14 @@ -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import { type CommandRunOptions, dateFm, stringMatches } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { Bank } from 'oldschooljs'; -import { BitField, BSO_MAX_TOTAL_LEVEL, Channel } from '../../lib/constants'; +import { BSO_MAX_TOTAL_LEVEL, BitField, Channel } from '../../lib/constants'; import { getReclaimableItemsOfUser } from '../../lib/reclaimableItems'; import { roboChimpUserFetch } from '../../lib/roboChimp'; -import { prisma } from '../../lib/settings/prisma'; -import { dateFm, stringMatches } from '../../lib/util'; + import getOSItem from '../../lib/util/getOSItem'; import { sendToChannelID } from '../../lib/util/webhook'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; const claimables = [ { diff --git a/src/mahoji/commands/clue.ts b/src/mahoji/commands/clue.ts index 45e1548b50c..a578e432f96 100644 --- a/src/mahoji/commands/clue.ts +++ b/src/mahoji/commands/clue.ts @@ -1,19 +1,20 @@ -import { clamp, increaseNumByPercent, randInt, Time } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import { type CommandRunOptions, formatDuration, isWeekend, stringMatches } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; +import { Time, clamp, increaseNumByPercent, randInt } from 'e'; import { Bank } from 'oldschooljs'; -import { Item, ItemBank } from 'oldschooljs/dist/meta/types'; +import type { Item, ItemBank } from 'oldschooljs/dist/meta/types'; -import { ClueTier, ClueTiers } from '../../lib/clues/clueTiers'; +import { type ClueTier, ClueTiers } from '../../lib/clues/clueTiers'; import { clueHunterOutfit } from '../../lib/data/CollectionsExport'; import { getPOHObject } from '../../lib/poh'; -import { ClueActivityTaskOptions } from '../../lib/types/minions'; -import { calcClueScores, formatDuration, isWeekend, stringMatches } from '../../lib/util'; +import type { ClueActivityTaskOptions } from '../../lib/types/minions'; +import { calcClueScores } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; import { checkElderClueRequirements } from '../../lib/util/elderClueRequirements'; import getOSItem from '../../lib/util/getOSItem'; import { getPOH } from '../lib/abstracted_commands/pohCommand'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; import { getMahojiBank, mahojiUsersSettingsFetch } from '../mahojiSettings'; function reducedClueTime(clueTier: ClueTier, score: number) { @@ -138,7 +139,7 @@ ${reqs.unmetRequirements.map(str => `- ${str}`).join('\n')}`; const boosts = []; if (user.hasEquippedOrInBank('Clue bag')) { - let boostPercent = 20; + const boostPercent = 20; maxTripLength = increaseNumByPercent(maxTripLength, boostPercent); boosts.push(`${boostPercent}% longer trip length for Clue bag`); } @@ -377,10 +378,10 @@ ${reqs.unmetRequirements.map(str => `- ${str}`).join('\n')}`; duration += (randomAddedDuration * duration) / 100; await addSubTaskToActivityTask({ - clueID: clueTier.id, + ci: clueTier.id, userID: user.id, channelID: channelID.toString(), - quantity, + q: quantity, duration, type: 'ClueCompletion' }); diff --git a/src/mahoji/commands/completion.ts b/src/mahoji/commands/completion.ts index 0e6eb7f2de3..da3dab05e5a 100644 --- a/src/mahoji/commands/completion.ts +++ b/src/mahoji/commands/completion.ts @@ -1,9 +1,10 @@ +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; import { AttachmentBuilder } from 'discord.js'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import { ApplicationCommandOptionType } from 'discord.js'; import { calculateCompCapeProgress } from '../../lib/bso/calculateCompCapeProgress'; import { generateAllCompCapeTasksList } from '../../lib/compCape'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const completionCommand: OSBMahojiCommand = { name: 'completion', diff --git a/src/mahoji/commands/config.ts b/src/mahoji/commands/config.ts index c693c14d89e..7aecc4f3001 100644 --- a/src/mahoji/commands/config.ts +++ b/src/mahoji/commands/config.ts @@ -1,29 +1,23 @@ -import { hasBanMemberPerms, miniID } from '@oldschoolgg/toolkit'; -import { activity_type_enum } from '@prisma/client'; -import { - bold, - ChatInputCommandInteraction, - EmbedBuilder, - Guild, - HexColorString, - inlineCode, - resolveColor, - User -} from 'discord.js'; -import { clamp, removeFromArr, Time, uniqueArr } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; +import { channelIsSendable, hasBanMemberPerms, miniID } from '@oldschoolgg/toolkit'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import type { CommandResponse } from '@oldschoolgg/toolkit'; +import type { activity_type_enum } from '@prisma/client'; +import type { ChatInputCommandInteraction, Guild, HexColorString, User } from 'discord.js'; +import { EmbedBuilder, bold, inlineCode, resolveColor } from 'discord.js'; +import { ApplicationCommandOptionType } from 'discord.js'; +import { Time, clamp, removeFromArr, uniqueArr } from 'e'; import { Bank } from 'oldschooljs'; -import { ItemBank } from 'oldschooljs/dist/meta/types'; +import type { ItemBank } from 'oldschooljs/dist/meta/types'; import { production } from '../../config'; -import { BitField, ItemIconPacks, ParsedCustomEmojiWithGroups, PerkTier, secretItems } from '../../lib/constants'; +import { mahojiUserSettingsUpdate } from '../../lib/MUser'; +import { BitField, ItemIconPacks, ParsedCustomEmojiWithGroups, PerkTier } from '../../lib/constants'; import { Eatables } from '../../lib/data/eatables'; import { gearImages } from '../../lib/gear/functions/generateGearImage'; import { Inventions } from '../../lib/invention/inventions'; import { CombatOptionsArray, CombatOptionsEnum } from '../../lib/minions/data/combatConstants'; -import { mahojiUserSettingsUpdate } from '../../lib/MUser'; -import { prisma } from '../../lib/settings/prisma'; + +import { DynamicButtons } from '../../lib/DynamicButtons'; import { birdhouseSeeds } from '../../lib/skilling/skills/hunter/birdHouseTrapping'; import { autoslayChoices, slayerMasterChoices } from '../../lib/slayer/constants'; import { setDefaultAutoslay, setDefaultSlayerMaster } from '../../lib/slayer/slayerUtil'; @@ -31,12 +25,13 @@ import { BankSortMethods } from '../../lib/sorts'; import { formatDuration, isValidNickname, itemNameFromID, stringMatches } from '../../lib/util'; import { emojiServers } from '../../lib/util/cachedUserIDs'; import { getItem } from '../../lib/util/getOSItem'; -import { interactionReplyGetDuration } from '../../lib/util/interactionHelpers'; +import { deferInteraction } from '../../lib/util/interactionReply'; import { makeBankImage } from '../../lib/util/makeBankImage'; import { parseBank } from '../../lib/util/parseStringBank'; import { mahojiGuildSettingsFetch, mahojiGuildSettingsUpdate } from '../guildSettings'; import { itemOption } from '../lib/mahojiCommandOptions'; -import { allAbstractCommands, OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; +import { allAbstractCommands } from '../lib/util'; import { mahojiUsersSettingsFetch, patronMsg } from '../mahojiSettings'; interface UserConfigToggle { @@ -101,32 +96,39 @@ const toggles: UserConfigToggle[] = [ return { result: true, message: 'Your Gambling lockout time has expired.' }; } else if (interaction) { const durations = [ - { display: '24 hours', duration: Time.Day }, + { display: '1 day', duration: Time.Day }, { display: '7 days', duration: Time.Day * 7 }, - { display: '2 weeks', duration: Time.Day * 14 }, { display: '1 month', duration: Time.Month }, - { display: '3 months', duration: Time.Month * 3 }, { display: '6 months', duration: Time.Month * 6 }, - { display: '1 year', duration: Time.Year }, - { display: '3 years', duration: Time.Year * 3 }, - { display: '5 years', duration: Time.Year * 5 } + { display: '1 year', duration: Time.Year } ]; - if (!production) { - durations.push({ display: '30 seconds', duration: Time.Second * 30 }); - durations.push({ display: '1 minute', duration: Time.Minute }); - durations.push({ display: '5 minutes', duration: Time.Minute * 5 }); + const channel = globalClient.channels.cache.get(interaction.channelId); + if (!channelIsSendable(channel)) return { result: false, message: 'Could not find channel.' }; + await deferInteraction(interaction); + const buttons = new DynamicButtons({ + channel: channel, + usersWhoCanInteract: [user.id], + deleteAfterConfirm: true + }); + for (const dur of durations) { + buttons.add({ + name: dur.display + }); } - const lockoutDuration = await interactionReplyGetDuration( - interaction, - `${user}, This will lockout your ability to gamble for the specified time. Choose carefully!`, - ...durations - ); + const pickedButton = await buttons.render({ + messageOptions: { + content: `${user}, This will lockout your ability to gamble for the specified time. Choose carefully!` + }, + isBusy: false + }); - if (lockoutDuration !== false) { - await user.update({ gambling_lockout_expiry: new Date(Date.now() + lockoutDuration.duration) }); + const pickedDuration = durations.find(d => stringMatches(d.display, pickedButton?.name ?? '')); + + if (pickedDuration) { + await user.update({ gambling_lockout_expiry: new Date(Date.now() + pickedDuration.duration) }); return { result: true, - message: `Locking out gambling for ${formatDuration(lockoutDuration.duration)}` + message: `Locking out gambling for ${formatDuration(pickedDuration.duration)}` }; } return { result: false, message: 'Cancelled.' }; @@ -204,7 +206,7 @@ async function favFoodConfig( const currentItems = `Your current favorite food is: ${ currentFavorites.length === 0 ? 'None' : currentFavorites.map(itemNameFromID).join(', ') }.`; - if (!item || secretItems.includes(item.id)) return currentItems; + if (!item || item.customItemData?.isSecret) return currentItems; if (!Eatables.some(i => i.id === item.id || i.raw === item.id)) return "That's not a valid item."; if (itemToAdd) { @@ -233,11 +235,11 @@ async function favItemConfig( const currentFavorites = user.user.favoriteItems; const item = getItem(itemToAdd ?? itemToRemove); const currentItems = `Your current favorite items are: ${ - currentFavorites.length === 0 ? 'None' : currentFavorites.map(itemNameFromID).join(', ') + currentFavorites.length === 0 ? 'None' : currentFavorites.map(itemNameFromID).join(', ').slice(0, 1500) }.`; - if (!item || secretItems.includes(item.id)) return currentItems; + if (!item || item.customItemData?.isSecret) return currentItems; if (itemToAdd) { - let limit = (user.perkTier() + 1) * 100; + const limit = (user.perkTier() + 1) * 100; if (currentFavorites.length >= limit) { return `You can't favorite anymore items, you can favorite a maximum of ${limit}.`; } @@ -293,7 +295,7 @@ async function favAlchConfig( if (!item.highalch) return "That item isn't alchable."; - const action = Boolean(removeItem) ? 'remove' : 'add'; + const action = removeItem ? 'remove' : 'add'; const isAlreadyFav = currentFavorites.includes(item.id); if (action === 'remove') { @@ -554,23 +556,18 @@ async function handleCombatOptions(user: MUser, command: 'add' | 'remove' | 'lis const settings = await mahojiUsersSettingsFetch(user.id, { combat_options: true }); if (!command || (command && command === 'list')) { // List enabled combat options: - const cbOpts = settings.combat_options.map(o => CombatOptionsArray.find(coa => coa!.id === o)!.name); + const cbOpts = settings.combat_options.map(o => CombatOptionsArray.find(coa => coa?.id === o)?.name); return `Your current combat options are:\n${cbOpts.join('\n')}\n\nTry: \`/config user combat_options help\``; } if (command === 'help' || !option || !['add', 'remove'].includes(command)) { - return ( - 'Changes your Combat Options. Usage: `/config user combat_options [add/remove/list] always cannon`' + - `\n\nList of possible options:\n${CombatOptionsArray.map(coa => `**${coa!.name}**: ${coa!.desc}`).join( - '\n' - )}` - ); + return `Changes your Combat Options. Usage: \`/config user combat_options [add/remove/list] always cannon\`\n\nList of possible options:\n${CombatOptionsArray.map( + coa => `**${coa?.name}**: ${coa?.desc}` + ).join('\n')}`; } const newcbopt = CombatOptionsArray.find( - item => - stringMatches(option, item.name) || - (item.aliases && item.aliases.some(alias => stringMatches(alias, option))) + item => stringMatches(option, item.name) || item.aliases?.some(alias => stringMatches(alias, option)) ); if (!newcbopt) return 'Cannot find matching option. Try: `/config user combat_options help`'; @@ -635,13 +632,13 @@ export async function pinTripCommand( ) { if (!tripId) return 'Invalid trip.'; const id = Number(tripId); + if (!id || Number.isNaN(id)) return 'Invalid trip.'; const trip = await prisma.activity.findFirst({ where: { id, user_id: BigInt(user.id) } }); if (!trip) return 'Invalid trip.'; if (emoji) { const res = ParsedCustomEmojiWithGroups.exec(emoji); if (!res || !res[3]) return "That's not a valid emoji."; - // eslint-disable-next-line prefer-destructuring emoji = res[3]; const cachedEmoji = globalClient.emojis.cache.get(emoji); @@ -752,7 +749,7 @@ export const configCommand: OSBMahojiCommand = { { type: ApplicationCommandOptionType.String, name: 'choice', - description: 'Enable or disable JMod Reddit comments for this server.', + description: 'Whether you want to enable or disable this command.', required: true, choices: [ { name: 'Enable', value: 'enable' }, @@ -1096,12 +1093,12 @@ export const configCommand: OSBMahojiCommand = { description: 'The trip you want to pin.', required: false, autocomplete: async (_, user) => { - let res = await prisma.$queryRawUnsafe< + const res = await prisma.$queryRawUnsafe< { type: activity_type_enum; data: object; id: number; finish_date: string }[] >(` SELECT DISTINCT ON (activity.type) activity.type, activity.data, activity.id, activity.finish_date FROM activity -WHERE finish_date::date > now() - INTERVAL '31 days' +WHERE finish_date > now() - INTERVAL '14 days' AND user_id = '${user.id}'::bigint ORDER BY activity.type, finish_date DESC LIMIT 20; @@ -1172,7 +1169,10 @@ LIMIT 20; name: 'name', description: 'The icon pack you want to use.', required: true, - choices: ['Default', ...ItemIconPacks.map(i => i.name)].map(i => ({ name: i, value: i })) + choices: ['Default', ...ItemIconPacks.map(i => i.name)].map(i => ({ + name: i, + value: i + })) } ] } @@ -1189,7 +1189,6 @@ LIMIT 20; server?: { channel?: { choice: 'enable' | 'disable' }; pet_messages?: { choice: 'enable' | 'disable' }; - jmod_comments?: { choice: 'enable' | 'disable' }; command?: { command: string; choice: 'enable' | 'disable' }; }; user?: { diff --git a/src/mahoji/commands/cook.ts b/src/mahoji/commands/cook.ts index d4ced752d9e..abaae8faa54 100644 --- a/src/mahoji/commands/cook.ts +++ b/src/mahoji/commands/cook.ts @@ -1,16 +1,17 @@ +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { Time } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; import { Bank } from 'oldschooljs'; import { KourendKebosDiary, userhasDiaryTier } from '../../lib/diaries'; import Cooking, { Cookables } from '../../lib/skilling/skills/cooking/cooking'; import LeapingFish from '../../lib/skilling/skills/cooking/leapingFish'; -import { CookingActivityTaskOptions } from '../../lib/types/minions'; +import type { CookingActivityTaskOptions } from '../../lib/types/minions'; import { formatDuration, itemID, stringMatches } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; import { cutLeapingFishCommand } from '../lib/abstracted_commands/cutLeapingFishCommand'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const cookCommand: OSBMahojiCommand = { name: 'cook', @@ -91,7 +92,7 @@ export const cookCommand: OSBMahojiCommand = { if (hasRemy) timeToCookSingleCookable /= 1.5; } else { let cookingBoost = 1; - let cookingBoostItems: string[] = []; + const cookingBoostItems: string[] = []; if (user.hasEquippedOrInBank('Cooking master cape')) { cookingBoostItems.push('Cooking master cape'); cookingBoost += 2.5; diff --git a/src/mahoji/commands/craft.ts b/src/mahoji/commands/craft.ts index 52a1cde5a31..81047238597 100644 --- a/src/mahoji/commands/craft.ts +++ b/src/mahoji/commands/craft.ts @@ -1,17 +1,17 @@ -import { reduceNumByPercent, Time } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import { type CommandRunOptions, formatDuration, stringMatches } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; +import { Time, reduceNumByPercent } from 'e'; import { FaladorDiary, userhasDiaryTier } from '../../lib/diaries'; -import { inventionBoosts, InventionID, inventionItemBoost } from '../../lib/invention/inventions'; +import { InventionID, inventionBoosts, inventionItemBoost } from '../../lib/invention/inventions'; import { Craftables } from '../../lib/skilling/skills/crafting/craftables'; import Tanning from '../../lib/skilling/skills/crafting/craftables/tanning'; import { SkillsEnum } from '../../lib/skilling/types'; -import { CraftingActivityTaskOptions } from '../../lib/types/minions'; -import { formatDuration, stringMatches } from '../../lib/util'; +import type { CraftingActivityTaskOptions } from '../../lib/types/minions'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; import { updateBankSetting } from '../../lib/util/updateBankSetting'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const craftCommand: OSBMahojiCommand = { name: 'craft', diff --git a/src/mahoji/commands/create.ts b/src/mahoji/commands/create.ts index 9c1e681a01d..ba48cb2950d 100644 --- a/src/mahoji/commands/create.ts +++ b/src/mahoji/commands/create.ts @@ -1,24 +1,24 @@ +import { readFileSync } from 'node:fs'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { isFunction, reduceNumByPercent } from 'e'; -import { readFileSync } from 'fs'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; import { Bank } from 'oldschooljs'; - +import type { SkillsEnum } from 'oldschooljs/dist/constants'; import Createables from '../../lib/data/createables'; -import { IMaterialBank } from '../../lib/invention'; -import { transactMaterialsFromUser } from '../../lib/invention/inventions'; +import type { IMaterialBank } from '../../lib/invention'; import { MaterialBank } from '../../lib/invention/MaterialBank'; -import { SkillsEnum } from '../../lib/skilling/types'; -import { SlayerTaskUnlocksEnum } from '../../lib/slayer/slayerUnlocks'; +import { transactMaterialsFromUser } from '../../lib/invention/inventions'; +import type { SlayerTaskUnlocksEnum } from '../../lib/slayer/slayerUnlocks'; import { hasSlayerUnlock } from '../../lib/slayer/slayerUtil'; import { stringMatches } from '../../lib/util'; import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; import { updateBankSetting } from '../../lib/util/updateBankSetting'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; import { mahojiUsersSettingsFetch, userStatsBankUpdate } from '../mahojiSettings'; const creatablesTable = readFileSync('./src/lib/data/creatablesTable.txt', 'utf8'); -let content = 'Theses are the items that you can create:'; +const content = 'Theses are the items that you can create:'; const allCreatablesTable = { content, files: [{ attachment: Buffer.from(creatablesTable), name: 'Creatables.txt' }] @@ -64,7 +64,7 @@ export const createCommand: OSBMahojiCommand = { }: CommandRunOptions<{ item: string; quantity?: number; showall?: boolean }>) => { const user = await mUserFetch(userID.toString()); - const itemName = options.item.toLowerCase(); + const itemName = options.item?.toLowerCase(); let { quantity } = options; if (options.showall) { return allCreatablesTable; @@ -96,7 +96,7 @@ export const createCommand: OSBMahojiCommand = { } } if (createableItem.requiredSlayerUnlocks) { - let mySlayerUnlocks = user.user.slayer_unlocks; + const mySlayerUnlocks = user.user.slayer_unlocks; const { success, errors } = hasSlayerUnlock( mySlayerUnlocks as SlayerTaskUnlocksEnum[], @@ -146,8 +146,7 @@ export const createCommand: OSBMahojiCommand = { : null; if ( - materialCost && - materialCost.has('wooden') && + materialCost?.has('wooden') && createableItem.name === 'Potion of light' && user.skillsAsXP.firemaking >= 500_000_000 ) { @@ -245,8 +244,8 @@ export const createCommand: OSBMahojiCommand = { await updateBankSetting('create_cost', inItems); await updateBankSetting('create_loot', outItems); - await userStatsBankUpdate(user.id, 'create_cost_bank', inItems); - await userStatsBankUpdate(user.id, 'create_loot_bank', outItems); + await userStatsBankUpdate(user, 'create_cost_bank', inItems); + await userStatsBankUpdate(user, 'create_loot_bank', outItems); if (action === 'revert') { return `You reverted ${inItems} into ${outItems}.${extraMessage}`; diff --git a/src/mahoji/commands/data.ts b/src/mahoji/commands/data.ts index d9253fe85f9..4e1d5c88d40 100644 --- a/src/mahoji/commands/data.ts +++ b/src/mahoji/commands/data.ts @@ -1,8 +1,9 @@ -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { deferInteraction } from '../../lib/util/interactionReply'; import { dataPoints, statsCommand } from '../lib/abstracted_commands/statCommand'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const dataCommand: OSBMahojiCommand = { name: 'data', @@ -29,7 +30,7 @@ export const dataCommand: OSBMahojiCommand = { ], run: async ({ interaction, options, userID }: CommandRunOptions<{ name: string }>) => { const user = await mUserFetch(userID); - deferInteraction(interaction); + await deferInteraction(interaction); return statsCommand(user, options.name); } }; diff --git a/src/mahoji/commands/dg.ts b/src/mahoji/commands/dg.ts index 73343adce27..8546c125ad2 100644 --- a/src/mahoji/commands/dg.ts +++ b/src/mahoji/commands/dg.ts @@ -1,6 +1,7 @@ import { formatOrdinal } from '@oldschoolgg/toolkit'; -import { reduceNumByPercent, Time } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; +import { Time, reduceNumByPercent } from 'e'; import { setupParty } from '../../lib/party'; import { @@ -16,13 +17,13 @@ import { numberOfGorajanOutfitsEquipped } from '../../lib/skilling/skills/dung/dungDbFunctions'; import { SkillsEnum } from '../../lib/skilling/types'; -import { MakePartyOptions } from '../../lib/types'; -import { DungeoneeringOptions } from '../../lib/types/minions'; +import type { MakePartyOptions } from '../../lib/types'; +import type { DungeoneeringOptions } from '../../lib/types/minions'; import { channelIsSendable, formatDuration, formatSkillRequirements, stringMatches } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; import { deferInteraction } from '../../lib/util/interactionReply'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; // Max people in a party: const maxTeamSize = 20; @@ -33,7 +34,7 @@ const boostPerPlayer = 5; async function startCommand(channelID: string, user: MUser, floor: string | undefined, solo: boolean | undefined) { const isSolo = Boolean(solo); - let floorToDo = Boolean(floor) ? Number(floor) : calcMaxFloorUserCanDo(user); + const floorToDo = floor ? Number(floor) : calcMaxFloorUserCanDo(user); if (!isValidFloor(floorToDo)) { return "That's an invalid floor."; @@ -47,7 +48,7 @@ async function startCommand(channelID: string, user: MUser, floor: string | unde let quantity = Math.floor(calcMaxTripLength(user, 'Dungeoneering') / dungeonLength); let duration = quantity * dungeonLength; - let message = `${user.usernameOrMention} has created a Dungeoneering party! Use the buttons below to join/leave. + const message = `${user.usernameOrMention} has created a Dungeoneering party! Use the buttons below to join/leave. **Floor:** ${floorToDo} **Duration:** ${formatDuration(duration)} **Min. Quantity:** ${quantity} @@ -122,14 +123,14 @@ async function startCommand(channelID: string, user: MUser, floor: string | unde y += 5; } - let x = y / users.length; + const x = y / users.length; duration = reduceNumByPercent(duration, x); boosts.push(`${x.toFixed(2)}% from ${user.usernameOrMention}`); } const numGora = numberOfGorajanOutfitsEquipped(user); if (numGora > 0) { - let x = (numGora * 6) / users.length; + const x = (numGora * 6) / users.length; duration = reduceNumByPercent(duration, x); boosts.push(`${x.toFixed(2)}% from ${user.usernameOrMention}'s Gorajan`); } diff --git a/src/mahoji/commands/divination.ts b/src/mahoji/commands/divination.ts index 52dfabaafe7..5e238631ada 100644 --- a/src/mahoji/commands/divination.ts +++ b/src/mahoji/commands/divination.ts @@ -1,26 +1,26 @@ -import { mentionCommand } from '@oldschoolgg/toolkit'; -import { increaseNumByPercent, removeFromArr, Time } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import { formatDuration, mentionCommand } from '@oldschoolgg/toolkit'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; +import { Time, increaseNumByPercent, removeFromArr } from 'e'; import { Bank } from 'oldschooljs'; import { + MemoryHarvestType, basePortentCost, divinationEnergies, getAllPortentCharges, - MemoryHarvestType, memoryHarvestTypes, portents } from '../../lib/bso/divination'; -import { inventionBoosts, InventionID, inventionItemBoost } from '../../lib/invention/inventions'; -import { prisma } from '../../lib/settings/prisma'; -import { MemoryHarvestOptions } from '../../lib/types/minions'; +import { InventionID, inventionBoosts, inventionItemBoost } from '../../lib/invention/inventions'; + +import type { MemoryHarvestOptions } from '../../lib/types/minions'; import { assert } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; -import { formatDuration } from '../../lib/util/smallUtils'; import { memoryHarvestResult, totalTimePerRound } from '../../tasks/minions/bso/memoryHarvestActivity'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const divinationCommand: OSBMahojiCommand = { name: 'divination', diff --git a/src/mahoji/commands/drop.ts b/src/mahoji/commands/drop.ts index 7821288b854..33329945854 100644 --- a/src/mahoji/commands/drop.ts +++ b/src/mahoji/commands/drop.ts @@ -1,4 +1,5 @@ -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { ClueTiers } from '../../lib/clues/clueTiers'; import { ellipsize, itemNameFromID, returnStringOrFile } from '../../lib/util'; @@ -6,7 +7,7 @@ import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmatio import { parseBank } from '../../lib/util/parseStringBank'; import { updateBankSetting } from '../../lib/util/updateBankSetting'; import { filterOption } from '../lib/mahojiCommandOptions'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const dropCommand: OSBMahojiCommand = { name: 'drop', @@ -60,7 +61,7 @@ export const dropCommand: OSBMahojiCommand = { } const favs = user.user.favoriteItems; - let itemsToDoubleCheck = [ + const itemsToDoubleCheck = [ ...favs, ...ClueTiers.map(c => [c.id, c.scrollID]), ...user.bank diff --git a/src/mahoji/commands/droprates.ts b/src/mahoji/commands/droprates.ts index 8ee8488720b..4b30ebc45ca 100644 --- a/src/mahoji/commands/droprates.ts +++ b/src/mahoji/commands/droprates.ts @@ -1,8 +1,9 @@ +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { Time } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; import { Bank } from 'oldschooljs'; -import { herbertDroprate, MAX_XP, MIN_LENGTH_FOR_PET } from '../../lib/constants'; +import { MAX_XP, MIN_LENGTH_FOR_PET, herbertDroprate } from '../../lib/constants'; import { globalDroprates } from '../../lib/data/globalDroprates'; import { slayerMaskHelms } from '../../lib/data/slayerMaskHelms'; import Constructables from '../../lib/skilling/skills/construction/constructables'; @@ -14,7 +15,7 @@ import { makeTable, stringMatches } from '../../lib/util'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; interface GlobalDroprate { name: string; @@ -56,8 +57,8 @@ const droprates: GlobalDroprate[] = [ const rows = []; for (const pot of Potions) { - let dropratePerMinute = herbertDroprate(1, pot.level); - let dropratePerMinuteAtMax = herbertDroprate(MAX_XP, pot.level); + const dropratePerMinute = herbertDroprate(1, pot.level); + const dropratePerMinuteAtMax = herbertDroprate(MAX_XP, pot.level); rows.push([pot.item.name, 1 / (60 / dropratePerMinute), 1 / (60 / dropratePerMinuteAtMax)]); } diff --git a/src/mahoji/commands/fake.ts b/src/mahoji/commands/fake.ts index 296a6d1108f..fc93c8261a4 100644 --- a/src/mahoji/commands/fake.ts +++ b/src/mahoji/commands/fake.ts @@ -1,9 +1,11 @@ -import { Canvas, SKRSContext2D } from '@napi-rs/canvas'; +import type { SKRSContext2D } from '@napi-rs/canvas'; +import { Canvas } from '@napi-rs/canvas'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { randInt } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; import { loadAndCacheLocalImage, measureTextWidth } from '../../lib/util/canvasUtil'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; const bg = loadAndCacheLocalImage('./src/lib/resources/images/tob-bg.png'); diff --git a/src/mahoji/commands/fakepm.ts b/src/mahoji/commands/fakepm.ts index 70b89a4a6cd..9b4f3927f68 100644 --- a/src/mahoji/commands/fakepm.ts +++ b/src/mahoji/commands/fakepm.ts @@ -1,8 +1,9 @@ import { Canvas } from '@napi-rs/canvas'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { loadAndCacheLocalImage } from '../../lib/util/canvasUtil'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; const bg = loadAndCacheLocalImage('./src/lib/resources/images/pm-bg.png'); diff --git a/src/mahoji/commands/farming.ts b/src/mahoji/commands/farming.ts index eb4468e557d..d2eecfdf936 100644 --- a/src/mahoji/commands/farming.ts +++ b/src/mahoji/commands/farming.ts @@ -1,12 +1,15 @@ -import { AutoFarmFilterEnum, CropUpgradeType } from '@prisma/client'; -import { User } from 'discord.js'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import type { CropUpgradeType } from '@prisma/client'; +import { AutoFarmFilterEnum } from '@prisma/client'; +import type { User } from 'discord.js'; +import { ApplicationCommandOptionType } from 'discord.js'; import TitheFarmBuyables from '../../lib/data/buyables/titheFarmBuyables'; import { superCompostables } from '../../lib/data/filterables'; -import { ContractOption, ContractOptions } from '../../lib/minions/farming/types'; +import type { ContractOption } from '../../lib/minions/farming/types'; +import { ContractOptions } from '../../lib/minions/farming/types'; import { autoFarm } from '../../lib/minions/functions/autoFarm'; -import { getFarmingInfo } from '../../lib/skilling/functions/getFarmingInfo'; +import { getFarmingInfoFromUser } from '../../lib/skilling/functions/getFarmingInfo'; import Farming, { CompostTiers } from '../../lib/skilling/skills/farming'; import { stringMatches } from '../../lib/util'; import { farmingPatchNames, userGrowingProgressStr } from '../../lib/util/farmingHelpers'; @@ -14,7 +17,7 @@ import { deferInteraction } from '../../lib/util/interactionReply'; import { compostBinCommand, farmingPlantCommand, harvestCommand } from '../lib/abstracted_commands/farmingCommand'; import { farmingContractCommand } from '../lib/abstracted_commands/farmingContractCommand'; import { titheFarmCommand, titheFarmShopCommand } from '../lib/abstracted_commands/titheFarmCommand'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; const autoFarmFilterTexts: Record = { AllFarm: 'All crops will be farmed with the highest available seed', @@ -201,7 +204,7 @@ export const farmingCommand: OSBMahojiCommand = { }>) => { await deferInteraction(interaction); const klasaUser = await mUserFetch(userID); - const { patchesDetailed } = await getFarmingInfo(userID); + const { patchesDetailed } = getFarmingInfoFromUser(klasaUser.user); if (options.auto_farm) { return autoFarm(klasaUser, patchesDetailed, channelID); @@ -214,7 +217,7 @@ export const farmingCommand: OSBMahojiCommand = { return `'Always pay farmers' is now ${!isEnabled ? 'enabled' : 'disabled'}.`; } if (options.default_compost) { - const tier = CompostTiers.find(i => stringMatches(i.name, options.default_compost!.compost)); + const tier = CompostTiers.find(i => stringMatches(i.name, options.default_compost?.compost)); if (!tier) return 'Invalid tier.'; await klasaUser.update({ minion_defaultCompostToUse: tier.name @@ -223,7 +226,7 @@ export const farmingCommand: OSBMahojiCommand = { } if (options.auto_farm_filter) { const autoFarmFilterString = Object.values(AutoFarmFilterEnum).find( - i => i === options.auto_farm_filter!.auto_farm_filter_data + i => i === options.auto_farm_filter?.auto_farm_filter_data ); if (!autoFarmFilterString) return 'Invalid auto farm filter.'; const autoFarmFilter = autoFarmFilterString as AutoFarmFilterEnum; diff --git a/src/mahoji/commands/finish.ts b/src/mahoji/commands/finish.ts index c870a5aea35..5f6e51f3244 100644 --- a/src/mahoji/commands/finish.ts +++ b/src/mahoji/commands/finish.ts @@ -1,6 +1,7 @@ +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; import { AttachmentBuilder } from 'discord.js'; +import { ApplicationCommandOptionType } from 'discord.js'; import { notEmpty } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; import { Bank } from 'oldschooljs'; import { finishables } from '../../lib/finishables'; @@ -9,7 +10,7 @@ import { stringMatches } from '../../lib/util'; import { deferInteraction } from '../../lib/util/interactionReply'; import { makeBankImage } from '../../lib/util/makeBankImage'; import { Workers } from '../../lib/workers'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const finishCommand: OSBMahojiCommand = { name: 'finish', diff --git a/src/mahoji/commands/fish.ts b/src/mahoji/commands/fish.ts index fc1a163d81a..3d7fc716c4d 100644 --- a/src/mahoji/commands/fish.ts +++ b/src/mahoji/commands/fish.ts @@ -1,16 +1,18 @@ -import { calcPercentOfNum, randInt, reduceNumByPercent, Time } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import { type CommandRunOptions, formatDuration, stringMatches } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; +import { Time, calcPercentOfNum, randInt, reduceNumByPercent } from 'e'; import { Bank } from 'oldschooljs'; import TzTokJad from 'oldschooljs/dist/simulation/monsters/special/TzTokJad'; +import { itemID } from 'oldschooljs/dist/util'; -import { inventionBoosts, InventionID, inventionItemBoost } from '../../lib/invention/inventions'; +import { InventionID, inventionBoosts, inventionItemBoost } from '../../lib/invention/inventions'; import Fishing from '../../lib/skilling/skills/fishing'; import { SkillsEnum } from '../../lib/skilling/types'; -import { FishingActivityTaskOptions } from '../../lib/types/minions'; -import { formatDuration, itemID, itemNameFromID, stringMatches } from '../../lib/util'; +import type { FishingActivityTaskOptions } from '../../lib/types/minions'; +import { itemNameFromID } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const fishCommand: OSBMahojiCommand = { name: 'fish', @@ -76,7 +78,7 @@ export const fishCommand: OSBMahojiCommand = { return 'You are not worthy JalYt. Before you can fish Infernal Eels, you need to have defeated the mighty TzTok-Jad!'; } } - const anglerOutfit = Object.keys(Fishing.anglerItems).map(i => itemNameFromID(parseInt(i))); + const anglerOutfit = Object.keys(Fishing.anglerItems).map(i => itemNameFromID(Number.parseInt(i))); if (fish.name === 'Minnow' && anglerOutfit.some(test => !user.hasEquippedOrInBank(test!))) { return 'You need to own the Angler Outfit to fish for Minnows.'; } @@ -155,7 +157,7 @@ export const fishCommand: OSBMahojiCommand = { ]; for (let i = 0; i < tackleBoxes.length; i++) { if (user.hasEquippedOrInBank([tackleBoxes[i]])) { - let num = Time.Minute * (tackleBoxes.length - i); + const num = Time.Minute * (tackleBoxes.length - i); maxTripLength += num; boosts.push(`${formatDuration(num)} for ${tackleBoxes[i]}`); break; diff --git a/src/mahoji/commands/fletch.ts b/src/mahoji/commands/fletch.ts index d0179107538..6036f3d374f 100644 --- a/src/mahoji/commands/fletch.ts +++ b/src/mahoji/commands/fletch.ts @@ -1,16 +1,17 @@ import { stringMatches } from '@oldschoolgg/toolkit'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { Time } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; import Fletching from '../../lib/skilling/skills/fletching'; import { Fletchables } from '../../lib/skilling/skills/fletching/fletchables'; -import { SlayerTaskUnlocksEnum } from '../../lib/slayer/slayerUnlocks'; +import type { SlayerTaskUnlocksEnum } from '../../lib/slayer/slayerUnlocks'; import { hasSlayerUnlock } from '../../lib/slayer/slayerUtil'; -import { FletchingActivityTaskOptions } from '../../lib/types/minions'; +import type { FletchingActivityTaskOptions } from '../../lib/types/minions'; import { formatDuration } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const fletchCommand: OSBMahojiCommand = { name: 'fletch', @@ -58,7 +59,7 @@ export const fletchCommand: OSBMahojiCommand = { } if (fletchable.requiredSlayerUnlocks) { - let mySlayerUnlocks = user.user.slayer_unlocks; + const mySlayerUnlocks = user.user.slayer_unlocks; const { success, errors } = hasSlayerUnlock( mySlayerUnlocks as SlayerTaskUnlocksEnum[], diff --git a/src/mahoji/commands/gamble.ts b/src/mahoji/commands/gamble.ts index 6d307f49a65..4d553cf0cf2 100644 --- a/src/mahoji/commands/gamble.ts +++ b/src/mahoji/commands/gamble.ts @@ -1,10 +1,11 @@ +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import type { MahojiUserOption } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { randArrItem } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; -import { MahojiUserOption } from 'mahoji/dist/lib/types'; import { Bank } from 'oldschooljs'; import { BitField } from '../../lib/constants'; -import { prisma } from '../../lib/settings/prisma'; + import { isSuperUntradeable } from '../../lib/util'; import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; import { capeGambleCommand, capeGambleStatsCommand } from '../lib/abstracted_commands/capegamble'; @@ -13,7 +14,7 @@ import { duelCommand } from '../lib/abstracted_commands/duelCommand'; import { hotColdCommand } from '../lib/abstracted_commands/hotColdCommand'; import { luckyPickCommand } from '../lib/abstracted_commands/luckyPickCommand'; import { slotsCommand } from '../lib/abstracted_commands/slotsCommand'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const gambleCommand: OSBMahojiCommand = { name: 'gamble', @@ -26,17 +27,18 @@ export const gambleCommand: OSBMahojiCommand = { */ { type: ApplicationCommandOptionType.Subcommand, - name: 'cape', - description: 'Allows you to gamble fire/infernal capes for a chance at the pets.', + name: 'item', + description: 'Allows you to gamble fire/infernal capes/quivers for a chance at the pets.', options: [ { type: ApplicationCommandOptionType.String, - name: 'type', - description: 'The cape you wish to gamble.', + name: 'item', + description: 'The item you wish to gamble.', required: false, choices: [ { name: 'fire', value: 'fire' }, - { name: 'infernal', value: 'infernal' } + { name: 'infernal', value: 'infernal' }, + { name: 'quiver', value: 'quiver' } ] }, { @@ -175,7 +177,7 @@ export const gambleCommand: OSBMahojiCommand = { guildID, userID }: CommandRunOptions<{ - cape?: { type?: string; autoconfirm?: boolean }; + item?: { item?: string; autoconfirm?: boolean }; dice?: { amount?: string }; duel?: { user: MahojiUserOption; amount?: string }; lucky_pick?: { amount: string }; @@ -185,9 +187,9 @@ export const gambleCommand: OSBMahojiCommand = { }>) => { const user = await mUserFetch(userID); - if (options.cape) { - if (options.cape.type) { - return capeGambleCommand(user, options.cape.type, interaction, options.cape.autoconfirm); + if (options.item) { + if (options.item.item) { + return capeGambleCommand(user, options.item.item, interaction, options.item.autoconfirm); } return capeGambleStatsCommand(user); } @@ -267,7 +269,7 @@ export const gambleCommand: OSBMahojiCommand = { type: 'gri' } }); - let debug = new Bank(); + const debug = new Bank(); for (const t of bank) debug.add(t[0].id); return `You gave ${qty.toLocaleString()}x ${item.name} to ${recipientuser.usernameOrMention}.`; diff --git a/src/mahoji/commands/ge.ts b/src/mahoji/commands/ge.ts index fae361509e4..15318686ff3 100644 --- a/src/mahoji/commands/ge.ts +++ b/src/mahoji/commands/ge.ts @@ -1,15 +1,16 @@ -import { getItem } from '@oldschoolgg/toolkit'; -import { evalMathExpression } from '@oldschoolgg/toolkit/dist/util/expressionParser'; -import { GEListing, GETransaction } from '@prisma/client'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import type { CommandOption } from '@oldschoolgg/toolkit'; +import { evalMathExpression } from '@oldschoolgg/toolkit'; +import type { GEListing, GETransaction } from '@prisma/client'; import { ApplicationCommandOptionType } from 'discord.js'; import { sumArr, uniqueArr } from 'e'; -import { CommandRunOptions } from 'mahoji'; -import { CommandOption } from 'mahoji/dist/lib/types'; import { PerkTier } from '../../lib/constants'; -import { createGECancelButton, GrandExchange } from '../../lib/grandExchange'; +import { GrandExchange, createGECancelButton } from '../../lib/grandExchange'; import { marketPricemap } from '../../lib/marketPrices'; -import { prisma } from '../../lib/settings/prisma'; + +import { Bank } from 'oldschooljs'; +import type { ItemBank } from 'oldschooljs/dist/meta/types'; import { formatDuration, isGEUntradeable, @@ -18,15 +19,15 @@ import { returnStringOrFile, toKMB } from '../../lib/util'; -import { lineChart } from '../../lib/util/chart'; -import getOSItem from '../../lib/util/getOSItem'; +import { createChart } from '../../lib/util/chart'; +import getOSItem, { getItem } from '../../lib/util/getOSItem'; import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; import { deferInteraction } from '../../lib/util/interactionReply'; import itemIsTradeable from '../../lib/util/itemIsTradeable'; import { cancelGEListingCommand } from '../lib/abstracted_commands/cancelGEListingCommand'; -import { itemArr, itemOption, ownedItemOption } from '../lib/mahojiCommandOptions'; -import { OSBMahojiCommand } from '../lib/util'; -import { patronMsg } from '../mahojiSettings'; +import { itemArr, itemOption } from '../lib/mahojiCommandOptions'; +import type { OSBMahojiCommand } from '../lib/util'; +import { mahojiUsersSettingsFetch } from '../mahojiSettings'; export type GEListingWithTransactions = GEListing & { buyTransactions: GETransaction[]; @@ -124,7 +125,7 @@ export const geCommand: OSBMahojiCommand = { value: itemID.toString() })); } - let res = itemArr.filter(i => i.key.includes(value.toLowerCase())); + const res = itemArr.filter(i => i.key.includes(value.toLowerCase())); return res.map(i => ({ name: `${i.name}`, value: i.id.toString() })); } }, @@ -138,10 +139,20 @@ export const geCommand: OSBMahojiCommand = { description: 'Sell something on the grand exchange.', options: [ { - ...ownedItemOption(item => !isGEUntradeable(item.id)), name: 'item', + type: ApplicationCommandOptionType.String, description: 'The item you want to sell.', - required: true + required: true, + autocomplete: async (value, { id }) => { + const raw = await mahojiUsersSettingsFetch(id, { bank: true }); + const bank = new Bank(raw.bank as ItemBank); + + return bank + .items() + .filter(i => !isGEUntradeable(i[0].id)) + .filter(i => (!value ? true : i[0].name.toLowerCase().includes(value.toLowerCase()))) + .map(i => ({ name: `${i[0].name} (${i[1]}x Owned)`, value: i[0].name })); + } }, quantityOption, priceOption @@ -204,7 +215,7 @@ export const geCommand: OSBMahojiCommand = { const listings = Array.from(marketPricemap.values()); return listings .filter(i => - !input ? true : itemNameFromID(i.itemID)!.toLowerCase().includes(input.toLowerCase()) + !input ? true : itemNameFromID(i.itemID)?.toLowerCase().includes(input.toLowerCase()) ) .map(l => ({ name: `${itemNameFromID(l.itemID)!}`, @@ -217,12 +228,12 @@ export const geCommand: OSBMahojiCommand = { { type: ApplicationCommandOptionType.Subcommand, name: 'view', - description: 'Lookup the market price of an item on the g.e', + description: 'Lookup information about an item on the g.e', options: [ { ...itemOption(item => Boolean(item.tradeable_on_ge)), - name: 'price_history', - description: 'View the price history of an item.' + name: 'item', + description: 'The item to view.' } ] } @@ -250,7 +261,7 @@ export const geCommand: OSBMahojiCommand = { }; stats?: {}; price?: { item: string }; - view?: { price_history?: string }; + view?: { item?: string }; }>) => { await deferInteraction(interaction); const user = await mUserFetch(userID); @@ -310,9 +321,7 @@ The next buy limit reset is at: ${GrandExchange.getInterval().nextResetStr}, it **G.E Slots You Can Use:** ${slots} **Taxes you have paid:** ${(totalGPYourSales._sum.total_tax_paid ?? 0).toLocaleString()} GP -**Total Tax Paid on your sales AND purchases:** ${( - totalGPYourTransactions._sum.total_tax_paid ?? 0 - ).toLocaleString()} GP`; +**Total Tax Paid on your sales AND purchases:** ${(totalGPYourTransactions._sum.total_tax_paid ?? 0).toLocaleString()} GP`; } if (options.my_listings) { @@ -391,12 +400,22 @@ The next buy limit reset is at: ${GrandExchange.getInterval().nextResetStr}, it } if (options.view) { - if (options.view.price_history) { - const item = getItem(options.view.price_history); + if (options.view.item) { + const item = getItem(options.view.item); if (!item) return 'Invalid item.'; if (!itemIsTradeable(item.id)) return 'That item is not tradeable on the Grand Exchange.'; + const priceData = marketPricemap.get(item.id); + let baseMessage = `**${item.name}** +**Buy Limit Per 4 hours:** ${GrandExchange.getItemBuyLimit(item).toLocaleString()}`; + if (priceData) { + baseMessage += ` +**Market Price:** ${toKMB(priceData.guidePrice)} (${priceData.guidePrice.toLocaleString()}) GP. +**Recent Price:** ${toKMB(priceData.averagePriceLast100)} (${priceData.averagePriceLast100.toLocaleString()}) GP. + +`; + } if (user.perkTier() < PerkTier.Four) { - return patronMsg(PerkTier.Four); + return baseMessage; } let result = await prisma.$queryRawUnsafe< { total_quantity_bought: number; average_price_per_item_before_tax: number; week: Date }[] @@ -419,18 +438,19 @@ GROUP BY ORDER BY week ASC; `); - if (result.length < 1) return 'No price history found for that item.'; + if (result.length < 1) return baseMessage; if (result[0].average_price_per_item_before_tax <= 1_000_000) { result = result.filter(i => i.total_quantity_bought > 1); } - const buffer = await lineChart( - `Price History for ${item.name}`, - result.map(i => [new Date(i.week).toDateString(), i.average_price_per_item_before_tax]), - val => val.toString(), - val => val, - false - ); + const buffer = await createChart({ + title: `Price History for ${item.name}`, + format: 'kmb', + values: result.map(i => [new Date(i.week).toDateString(), i.average_price_per_item_before_tax]), + type: 'line' + }); + return { + content: baseMessage, files: [buffer] }; } @@ -446,7 +466,7 @@ ORDER BY itemName: (options.buy?.item ?? options.sell?.item)!, price: parseNumber((options.buy?.price ?? options.sell?.price)!), quantity: parseNumber((options.buy?.quantity ?? options.sell?.quantity)!), - type: Boolean(options.buy) ? 'Buy' : 'Sell' + type: options.buy ? 'Buy' : 'Sell' }); if ('error' in result) return result.error; @@ -461,9 +481,9 @@ ORDER BY if (options.buy) { const result = await GrandExchange.createListing({ user, - itemName: options.buy!.item, - price: parseNumber(options.buy!.price), - quantity: parseNumber(options.buy!.quantity), + itemName: options.buy?.item, + price: parseNumber(options.buy?.price), + quantity: parseNumber(options.buy?.quantity), type: 'Buy' }); diff --git a/src/mahoji/commands/gear.ts b/src/mahoji/commands/gear.ts index d5c076d6959..c1d80cae2bd 100644 --- a/src/mahoji/commands/gear.ts +++ b/src/mahoji/commands/gear.ts @@ -1,11 +1,13 @@ import { Canvas, loadImage } from '@napi-rs/canvas'; import { toTitleCase } from '@oldschoolgg/toolkit'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; +import type { GearSetupType } from '@prisma/client'; import { gearValidationChecks } from '../../lib/constants'; import { allPetIDs } from '../../lib/data/CollectionsExport'; +import { GearSetupTypes, GearStat } from '../../lib/gear'; import { generateGearImage } from '../../lib/gear/functions/generateGearImage'; -import { GearSetupType, GearSetupTypes, GearStat } from '../../lib/gear/types'; import { equipPet } from '../../lib/minions/functions/equipPet'; import { unequipPet } from '../../lib/minions/functions/unequipPet'; import { itemNameFromID } from '../../lib/util'; @@ -18,7 +20,7 @@ import { gearViewCommand } from '../lib/abstracted_commands/gearCommands'; import { equippedItemOption, gearPresetOption, gearSetupOption, ownedItemOption } from '../lib/mahojiCommandOptions'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; import { getMahojiBank, mahojiUsersSettingsFetch } from '../mahojiSettings'; export const gearCommand: OSBMahojiCommand = { diff --git a/src/mahoji/commands/gearpresets.ts b/src/mahoji/commands/gearpresets.ts index 7e412741565..79cfd86e618 100644 --- a/src/mahoji/commands/gearpresets.ts +++ b/src/mahoji/commands/gearpresets.ts @@ -1,20 +1,22 @@ -import { GearPreset } from '@prisma/client'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; -import { CommandOption } from 'mahoji/dist/lib/types'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import type { CommandOption } from '@oldschoolgg/toolkit'; +import type { GearPreset } from '@prisma/client'; +import { ApplicationCommandOptionType } from 'discord.js'; import { EquipmentSlot } from 'oldschooljs/dist/meta/types'; import { production } from '../../config'; import { ParsedCustomEmojiWithGroups } from '../../lib/constants'; import { generateGearImage } from '../../lib/gear/functions/generateGearImage'; -import { GearSetup, GearSetupType, GearSetupTypes } from '../../lib/gear/types'; -import { prisma } from '../../lib/settings/prisma'; -import { defaultGear, Gear, globalPresets } from '../../lib/structures/Gear'; +import type { GearSetup, GearSetupType } from '../../lib/gear/types'; +import { GearSetupTypes } from '../../lib/gear/types'; + +import { Gear, defaultGear, globalPresets } from '../../lib/structures/Gear'; import { cleanString, isValidGearSetup, isValidNickname, stringMatches } from '../../lib/util'; import { emojiServers } from '../../lib/util/cachedUserIDs'; import { getItem } from '../../lib/util/getOSItem'; import { gearEquipCommand } from '../lib/abstracted_commands/gearCommands'; import { allEquippableItems, gearPresetOption, gearSetupOption } from '../lib/mahojiCommandOptions'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; function maxPresets(user: MUser) { return user.perkTier() * 2 + 4; @@ -23,8 +25,8 @@ function maxPresets(user: MUser) { type InputGear = Partial>; type ParsedInputGear = Partial>; function parseInputGear(inputGear: InputGear) { - let gear: ParsedInputGear = {}; - let remove: EquipmentSlot[] = []; + const gear: ParsedInputGear = {}; + const remove: EquipmentSlot[] = []; for (const [key, val] of Object.entries(inputGear)) { if (val?.toLowerCase() === 'none') { remove.push(key as EquipmentSlot); @@ -38,7 +40,7 @@ function parseInputGear(inputGear: InputGear) { return { gear, remove }; } -export function gearPresetToGear(preset: GearPreset): GearSetup { +function gearPresetToGear(preset: GearPreset): GearSetup { function gearItem(val: null | number) { if (val === null) return null; return { @@ -63,13 +65,15 @@ export function gearPresetToGear(preset: GearPreset): GearSetup { export async function createOrEditGearSetup( user: MUser, setupToCopy: GearSetupType | undefined, - name = '', + name: string, isUpdating: boolean, gearInput: InputGear, emoji: string | undefined, pinned_setup: GearSetupType | 'reset' | undefined ) { - name = cleanString(name).toLowerCase(); + if (name) { + name = cleanString(name).toLowerCase(); + } if (name.length > 24) return 'Gear preset names must be less than 25 characters long.'; if (!name) return "You didn't supply a name."; if (!isUpdating && !isValidNickname(name)) { @@ -109,7 +113,6 @@ export async function createOrEditGearSetup( if (emoji) { const res = ParsedCustomEmojiWithGroups.exec(emoji); if (!res || !res[3]) return "That's not a valid emoji."; - // eslint-disable-next-line prefer-destructuring emoji = res[3]; const cachedEmoji = globalClient.emojis.cache.get(emoji); diff --git a/src/mahoji/commands/gift.ts b/src/mahoji/commands/gift.ts index 44678b202bd..6411618ff6c 100644 --- a/src/mahoji/commands/gift.ts +++ b/src/mahoji/commands/gift.ts @@ -1,19 +1,20 @@ -import { mentionCommand, miniID, truncateString } from '@oldschoolgg/toolkit'; +import { containsBlacklistedWord, mentionCommand, miniID, truncateString } from '@oldschoolgg/toolkit'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import type { MahojiUserOption } from '@oldschoolgg/toolkit'; import { GiftBoxStatus } from '@prisma/client'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; -import { MahojiUserOption } from 'mahoji/dist/lib/types'; +import { ApplicationCommandOptionType } from 'discord.js'; import { Bank } from 'oldschooljs'; +import type { ItemBank } from 'oldschooljs/dist/meta/types'; import { BLACKLISTED_USERS } from '../../lib/blacklists'; import { BOT_TYPE } from '../../lib/constants'; -import { prisma } from '../../lib/settings/prisma'; -import { ItemBank } from '../../lib/types'; -import { containsBlacklistedWord, isSuperUntradeable, isValidNickname } from '../../lib/util'; + +import { isSuperUntradeable, isValidNickname } from '../../lib/util'; import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; import itemIsTradeable from '../../lib/util/itemIsTradeable'; import { makeBankImage } from '../../lib/util/makeBankImage'; import { parseBank } from '../../lib/util/parseStringBank'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const giftCommand: OSBMahojiCommand = { name: 'gift', diff --git a/src/mahoji/commands/giveaway.ts b/src/mahoji/commands/giveaway.ts index b230f6f8f58..e2bae75446f 100644 --- a/src/mahoji/commands/giveaway.ts +++ b/src/mahoji/commands/giveaway.ts @@ -1,9 +1,10 @@ -import { Giveaway } from '@prisma/client'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import type { Giveaway } from '@prisma/client'; import { Duration } from '@sapphire/time-utilities'; +import type { BaseMessageOptions } from 'discord.js'; import { ActionRowBuilder, AttachmentBuilder, - BaseMessageOptions, ButtonBuilder, ButtonStyle, ChannelType, @@ -11,14 +12,14 @@ import { messageLink, time } from 'discord.js'; -import { randInt, Time } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import { ApplicationCommandOptionType } from 'discord.js'; +import { Time, randInt } from 'e'; import { Bank } from 'oldschooljs'; -import { ItemBank } from 'oldschooljs/dist/meta/types'; +import type { ItemBank } from 'oldschooljs/dist/meta/types'; import { Emoji, patronFeatures } from '../../lib/constants'; import { marketPriceOfBank } from '../../lib/marketPrices'; -import { prisma } from '../../lib/settings/prisma'; + import { channelIsSendable, isModOrAdmin, isSuperUntradeable, makeComponents, toKMB } from '../../lib/util'; import { generateGiveawayContent } from '../../lib/util/giveaway'; import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; @@ -27,7 +28,7 @@ import { logError } from '../../lib/util/logError'; import { makeBankImage } from '../../lib/util/makeBankImage'; import { parseBank } from '../../lib/util/parseStringBank'; import { filterOption } from '../lib/mahojiCommandOptions'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; import { addToGPTaxBalance } from '../mahojiSettings'; function makeGiveawayButtons(giveawayID: number): BaseMessageOptions['components'] { diff --git a/src/mahoji/commands/gp.ts b/src/mahoji/commands/gp.ts index 04749ed790c..00c2174b1cf 100644 --- a/src/mahoji/commands/gp.ts +++ b/src/mahoji/commands/gp.ts @@ -1,8 +1,8 @@ -import { CommandRunOptions } from 'mahoji'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; import { toKMB } from 'oldschooljs/dist/util'; import { Emoji } from '../../lib/constants'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; import { mahojiUsersSettingsFetch } from '../mahojiSettings'; export const gpCommand: OSBMahojiCommand = { diff --git a/src/mahoji/commands/help.ts b/src/mahoji/commands/help.ts index 91c3cb81ffd..b11d54c7db1 100644 --- a/src/mahoji/commands/help.ts +++ b/src/mahoji/commands/help.ts @@ -1,7 +1,7 @@ import { ButtonStyle, ComponentType } from 'discord.js'; import { mahojiInformationalButtons } from '../../lib/constants'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const helpCommand: OSBMahojiCommand = { name: 'help', diff --git a/src/mahoji/commands/hunt.ts b/src/mahoji/commands/hunt.ts index 7b54dfc4ca9..335ee8b0996 100644 --- a/src/mahoji/commands/hunt.ts +++ b/src/mahoji/commands/hunt.ts @@ -1,26 +1,28 @@ -import { reduceNumByPercent, Time } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; import { Bank } from 'oldschooljs'; -import { ItemBank } from 'oldschooljs/dist/meta/types'; +import type { ItemBank } from 'oldschooljs/dist/meta/types'; +import { type CommandRunOptions, formatDuration, stringMatches } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; +import { Time, reduceNumByPercent } from 'e'; +import { itemID } from 'oldschooljs/dist/util'; import { HERBIBOAR_ID, RAZOR_KEBBIT_ID } from '../../lib/constants'; -import { UserFullGearSetup } from '../../lib/gear'; +import type { UserFullGearSetup } from '../../lib/gear'; import { hasWildyHuntGearEquipped } from '../../lib/gear/functions/hasWildyHuntGearEquipped'; -import { inventionBoosts, InventionID, inventionItemBoost } from '../../lib/invention/inventions'; +import { InventionID, inventionBoosts, inventionItemBoost } from '../../lib/invention/inventions'; import { trackLoot } from '../../lib/lootTrack'; import { monkeyTiers } from '../../lib/monkeyRumble'; import { soteSkillRequirements } from '../../lib/skilling/functions/questRequirements'; import creatures from '../../lib/skilling/skills/hunter/creatures'; import Hunter from '../../lib/skilling/skills/hunter/hunter'; -import { Creature, HunterTechniqueEnum } from '../../lib/skilling/types'; -import { Peak } from '../../lib/tickers'; -import { Skills } from '../../lib/types'; -import { HunterActivityTaskOptions } from '../../lib/types/minions'; -import { formatDuration, hasSkillReqs, itemID, stringMatches } from '../../lib/util'; +import { type Creature, HunterTechniqueEnum } from '../../lib/skilling/types'; +import type { Peak } from '../../lib/tickers'; +import type { Skills } from '../../lib/types'; +import type { HunterActivityTaskOptions } from '../../lib/types/minions'; +import { hasSkillReqs } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; import { updateBankSetting } from '../../lib/util/updateBankSetting'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; import { userHasGracefulEquipped } from '../mahojiSettings'; export function calculateHunterInput({ @@ -80,7 +82,7 @@ export function calculateHunterInput({ return "You can't hunt Chimpchompa's! You need to be wearing a greegree."; } - let crystalImpling = creature.name === 'Crystal impling'; + const crystalImpling = creature.name === 'Crystal impling'; if (crystalImpling) { const [hasReqs, reason] = hasSkillReqs(user, soteSkillRequirements); @@ -178,7 +180,7 @@ export function calculateHunterInput({ timePerCatch = boostedActionTime; } - let maxQuantity = Math.floor(maxTripLength / timePerCatch); + const maxQuantity = Math.floor(maxTripLength / timePerCatch); let quantity = quantityInput; if (!quantity) { if (crystalImpling) { @@ -200,7 +202,7 @@ export function calculateHunterInput({ )}, try a lower quantity. The highest amount of ${creature.name} you can hunt is ${maxQuantity}.`; } - let totalCost = new Bank(); + const totalCost = new Bank(); if (creature.itemsConsumed) { for (const [item, qty] of creature.itemsConsumed.items()) { @@ -219,7 +221,7 @@ export function calculateHunterInput({ // If creatures Herbiboar or Razor-backed kebbit or Crystal Impling use Stamina potion(4) if (shouldUseStaminaPotions) { if (creature.id === HERBIBOAR_ID || creature.id === RAZOR_KEBBIT_ID || crystalImpling) { - let staminaPotionQuantity = + const staminaPotionQuantity = creature.id === HERBIBOAR_ID || crystalImpling ? Math.round(duration / (9 * Time.Minute)) : Math.round(duration / (18 * Time.Minute)); @@ -242,7 +244,7 @@ export function calculateHunterInput({ } if (creature.bait) { - let reqBank = creature.bait(quantity); + const reqBank = creature.bait(quantity); if (!bank.has(reqBank)) { return `You don't have enough bait to catch ${quantity}x ${creature.name}, you need: ${reqBank}.`; } @@ -337,7 +339,7 @@ export const huntCommand: OSBMahojiCommand = { if (!creature) return "That's not a valid creature to hunt."; - let crystalImpling = creature.name === 'Crystal impling'; + const crystalImpling = creature.name === 'Crystal impling'; const maxTripLength = calcMaxTripLength(user, 'Hunter'); const elligibleForQuickTrap = @@ -377,7 +379,7 @@ export const huntCommand: OSBMahojiCommand = { user, inventionID: InventionID.QuickTrap, duration: preResult.duration - }) + }) : null; const webshooterResult = elligibleForWebshooter @@ -385,7 +387,7 @@ export const huntCommand: OSBMahojiCommand = { user, inventionID: InventionID.Webshooter, duration: preResult.duration - }) + }) : null; const result = calculateHunterInput({ @@ -429,8 +431,8 @@ export const huntCommand: OSBMahojiCommand = { type: 'Hunter' }); - let response = `${user.minionName} is now ${crystalImpling ? 'hunting' : `${creature.huntTechnique}`} ${ - crystalImpling ? '' : ` ${quantity}x ` + let response = `${user.minionName} is now ${crystalImpling ? 'hunting' : `${creature.huntTechnique}`}${ + crystalImpling ? ' ' : ` ${quantity}x ` }${creature.name}, it'll take around ${formatDuration(duration)} to finish.`; if (messages.length > 0) { diff --git a/src/mahoji/commands/ic.ts b/src/mahoji/commands/ic.ts index 40880dacd88..51a7d207e03 100644 --- a/src/mahoji/commands/ic.ts +++ b/src/mahoji/commands/ic.ts @@ -1,19 +1,20 @@ import { formatOrdinal } from '@oldschoolgg/toolkit'; -import { ButtonBuilder, ButtonStyle, ChatInputCommandInteraction } from 'discord.js'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ButtonBuilder, ButtonStyle, type ChatInputCommandInteraction } from 'discord.js'; +import { ApplicationCommandOptionType } from 'discord.js'; import { randArrItem, roll } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; import { Bank, LootTable } from 'oldschooljs'; -import { ItemBank } from 'oldschooljs/dist/meta/types'; +import type { ItemBank } from 'oldschooljs/dist/meta/types'; -import { chargePortentIfHasCharges, PortentID } from '../../lib/bso/divination'; -import { allMbTables, MysteryBoxes, PMBTable } from '../../lib/bsoOpenables'; +import { itemContractResetTime } from '../../lib/MUser'; +import { PortentID, chargePortentIfHasCharges } from '../../lib/bso/divination'; +import { MysteryBoxes, PMBTable, allMbTables } from '../../lib/bsoOpenables'; import { BitField, Emoji } from '../../lib/constants'; import { AbyssalDragonLootTable } from '../../lib/minions/data/killableMonsters/custom/AbyssalDragon'; import { Ignecarus } from '../../lib/minions/data/killableMonsters/custom/bosses/Ignecarus'; import { kalphiteKingLootTable } from '../../lib/minions/data/killableMonsters/custom/bosses/KalphiteKing'; import { VasaMagus } from '../../lib/minions/data/killableMonsters/custom/bosses/VasaMagus'; import { BSOMonsters } from '../../lib/minions/data/killableMonsters/custom/customMonsters'; -import { itemContractResetTime } from '../../lib/MUser'; import { nexLootTable } from '../../lib/nex'; import { DragonTable } from '../../lib/simulation/grandmasterClue'; import { allThirdAgeItems, runeAlchablesTable } from '../../lib/simulation/sharedTables'; @@ -23,7 +24,7 @@ import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmatio import resolveItems from '../../lib/util/resolveItems'; import { updateBankSetting } from '../../lib/util/updateBankSetting'; import { LampTable } from '../../lib/xpLamps'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; import { updateClientGPTrackSetting, userStatsBankUpdate } from '../mahojiSettings'; const contractTable = new LootTable() @@ -79,7 +80,7 @@ const itemContractItems = Array.from(itemContractItemsSet); function pickItemContract(streak: number) { let item = randArrItem(itemContractItems); if (streak > 50) { - let fifties = Math.floor(streak / 50); + const fifties = Math.floor(streak / 50); for (let i = 0; i < fifties; i++) { if (roll(95 + i * 5)) { item = randArrItem(allThirdAgeItems); @@ -98,7 +99,7 @@ export function getItemContractDetails(mUser: MUser) { const totalContracts = mUser.user.total_item_contracts; const streak = mUser.user.item_contract_streak; const currentItem = mUser.user.current_item_contract ? getOSItem(mUser.user.current_item_contract) : null; - let durationRemaining = Date.now() - (lastDate + itemContractResetTime); + const durationRemaining = Date.now() - (lastDate + itemContractResetTime); const nextContractIsReady = difference >= itemContractResetTime; const { bank } = mUser; @@ -282,7 +283,7 @@ export const icCommand: OSBMahojiCommand = { .setLabel('Donate IC') .setEmoji('988422348434718812') .setCustomId(`DONATE_IC_${user.id}`) - ]) + ]) : undefined; if (options.info) { diff --git a/src/mahoji/commands/invention.ts b/src/mahoji/commands/invention.ts index 57f39bda442..c9ad2f6a0e0 100644 --- a/src/mahoji/commands/invention.ts +++ b/src/mahoji/commands/invention.ts @@ -1,9 +1,11 @@ -import { reduceNumByPercent, Time } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; +import { Time, reduceNumByPercent } from 'e'; import { Bank } from 'oldschooljs'; -import { ItemBank } from 'oldschooljs/dist/meta/types'; +import type { ItemBank } from 'oldschooljs/dist/meta/types'; -import { allItemsThatCanBeDisassembledIDs, IMaterialBank, MaterialType } from '../../lib/invention'; +import { type IMaterialBank, type MaterialType, allItemsThatCanBeDisassembledIDs } from '../../lib/invention'; +import { MaterialBank } from '../../lib/invention/MaterialBank'; import { calcJunkChance, calculateDisXP, @@ -11,15 +13,14 @@ import { findDisassemblyGroup } from '../../lib/invention/disassemble'; import { DisassemblySourceGroups } from '../../lib/invention/groups'; -import { inventCommand, inventingCost, inventionBoosts, Inventions } from '../../lib/invention/inventions'; -import { MaterialBank } from '../../lib/invention/MaterialBank'; +import { Inventions, inventCommand, inventingCost, inventionBoosts } from '../../lib/invention/inventions'; import { researchCommand } from '../../lib/invention/research'; import { SkillsEnum } from '../../lib/skilling/types'; import { calcPerHour, makeTable, stringMatches, toKMB } from '../../lib/util'; import { deferInteraction } from '../../lib/util/interactionReply'; import { makeBankImage } from '../../lib/util/makeBankImage'; import { ownedMaterialOption } from '../lib/mahojiCommandOptions'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; import { mahojiParseNumber, mahojiUsersSettingsFetch } from '../mahojiSettings'; export const inventionCommand: OSBMahojiCommand = { @@ -223,7 +224,7 @@ export const inventionCommand: OSBMahojiCommand = { const group = DisassemblySourceGroups.find(i => i.name === options.group?.group); if (!group) return "That's not a valid group."; let str = `${['Name', 'Weighting/Level'].join('\t')}\n`; - let results: [string, number][] = []; + const results: [string, number][] = []; for (const baseItem of group.items) { for (const item of Array.isArray(baseItem.item) ? baseItem.item : [baseItem.item]) { results.push([item.name, baseItem.lvl]); @@ -290,7 +291,7 @@ These Inventions are still not unlocked: ${locked .join(', ')}`; } case 'xp': { - let xpTable = []; + const xpTable = []; const lvls = [1, 10, 30, 60, 80, 90, 99, 110, 120]; const weightings = [1, 10, 30, 60, 80, 90, 99]; @@ -298,12 +299,12 @@ These Inventions are still not unlocked: ${locked for (const weighting of weightings) { if (weighting > lvl) continue; const { xp } = calculateDisXP({} as any, lvl, 1, weighting); - let dur = Time.Second * 0.33; - let toolkitDur = reduceNumByPercent( + const dur = Time.Second * 0.33; + const toolkitDur = reduceNumByPercent( dur, inventionBoosts.dwarvenToolkit.disassembleBoostPercent ); - let capeAndToolkitDur = reduceNumByPercent( + const capeAndToolkitDur = reduceNumByPercent( toolkitDur, inventionBoosts.inventionMasterCape.disassemblySpeedBoostPercent ); diff --git a/src/mahoji/commands/invite.ts b/src/mahoji/commands/invite.ts index b7f2fee88c7..c77fd8193e0 100644 --- a/src/mahoji/commands/invite.ts +++ b/src/mahoji/commands/invite.ts @@ -1,4 +1,4 @@ -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const inviteCommand: OSBMahojiCommand = { name: 'invite', diff --git a/src/mahoji/commands/k.ts b/src/mahoji/commands/k.ts index 3099cfae2ec..b9686b058f0 100644 --- a/src/mahoji/commands/k.ts +++ b/src/mahoji/commands/k.ts @@ -1,6 +1,7 @@ -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; -import { PVM_METHODS, PvMMethod, ZALCANO_ID } from '../../lib/constants'; +import { PVM_METHODS, type PvMMethod, ZALCANO_ID } from '../../lib/constants'; import killableMonsters from '../../lib/minions/data/killableMonsters'; import { Ignecarus } from '../../lib/minions/data/killableMonsters/custom/bosses/Ignecarus'; import { KalphiteKingMonster } from '../../lib/minions/data/killableMonsters/custom/bosses/KalphiteKing'; @@ -9,10 +10,9 @@ import { MOKTANG_ID } from '../../lib/minions/data/killableMonsters/custom/bosse import { Naxxus } from '../../lib/minions/data/killableMonsters/custom/bosses/Naxxus'; import { VasaMagus } from '../../lib/minions/data/killableMonsters/custom/bosses/VasaMagus'; import { NexMonster } from '../../lib/nex'; -import { prisma } from '../../lib/settings/prisma'; import { returnStringOrFile } from '../../lib/util/smallUtils'; import { minionKillCommand, monsterInfo } from '../lib/abstracted_commands/minionKill'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const autocompleteMonsters = [ ...killableMonsters, @@ -79,23 +79,30 @@ export const autocompleteMonsters = [ name: 'Moktang', aliases: ['moktang'], id: MOKTANG_ID + }, + { + name: 'Colosseum', + aliases: ['colo', 'colosseum'], + id: -1 } ]; async function fetchUsersRecentlyKilledMonsters(userID: string) { - const res = await prisma.$queryRawUnsafe<{ mon_id: string }[]>( - `SELECT DISTINCT((data->>'monsterID')) AS mon_id + const res = await prisma.$queryRawUnsafe<{ mon_id: string; last_killed: Date }[]>( + `SELECT DISTINCT((data->>'mi')) AS mon_id, MAX(start_date) as last_killed FROM activity WHERE user_id = $1 AND type = 'MonsterKilling' AND finish_date > now() - INTERVAL '31 days' +GROUP BY 1 +ORDER BY 2 DESC LIMIT 10;`, BigInt(userID) ); - return new Set(res.map(i => Number(i.mon_id))); + return res.map(i => Number(i.mon_id)); } -export const killCommand: OSBMahojiCommand = { +export const minionKCommand: OSBMahojiCommand = { name: 'k', description: 'Send your minion to kill things.', attributes: { @@ -117,15 +124,17 @@ export const killCommand: OSBMahojiCommand = { : [m.name.toLowerCase(), ...m.aliases].some(str => str.includes(value.toLowerCase())) ) .sort((a, b) => { - const hasA = recentlyKilled.has(a.id); - const hasB = recentlyKilled.has(b.id); - if (hasA && hasB) return 0; + const hasA = recentlyKilled.includes(a.id); + const hasB = recentlyKilled.includes(b.id); + if (hasA && hasB) { + return recentlyKilled.indexOf(a.id) < recentlyKilled.indexOf(b.id) ? -1 : 1; + } if (hasA) return -1; if (hasB) return 1; return 0; }) .map(i => ({ - name: `${i.name}${recentlyKilled.has(i.id) ? ' (Recently killed)' : ''}`, + name: `${i.name}${recentlyKilled.includes(i.id) ? ' (Recently killed)' : ''}`, value: i.name })); } @@ -149,6 +158,18 @@ export const killCommand: OSBMahojiCommand = { name: 'show_info', description: 'Show information on this monster.', required: false + }, + { + type: ApplicationCommandOptionType.Boolean, + name: 'wilderness', + description: 'If you want to kill the monster in the wilderness.', + required: false + }, + { + type: ApplicationCommandOptionType.Boolean, + name: 'solo', + description: 'Solo (if its a group boss)', + required: false } ], run: async ({ @@ -161,11 +182,22 @@ export const killCommand: OSBMahojiCommand = { quantity?: number; method?: PvMMethod; show_info?: boolean; + wilderness?: boolean; + solo?: boolean; }>) => { const user = await mUserFetch(userID); if (options.show_info) { return returnStringOrFile(await monsterInfo(user, options.name)); } - return minionKillCommand(user, interaction, channelID, options.name, options.quantity, options.method); + return minionKillCommand( + user, + interaction, + channelID, + options.name, + options.quantity, + options.method, + options.wilderness, + options.solo + ); } }; diff --git a/src/mahoji/commands/kc.ts b/src/mahoji/commands/kc.ts new file mode 100644 index 00000000000..997bc613549 --- /dev/null +++ b/src/mahoji/commands/kc.ts @@ -0,0 +1,55 @@ +import { stringMatches, toTitleCase } from '@oldschoolgg/toolkit'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; +import { Hiscores } from 'oldschooljs'; +import { bossNameMap, mappedBossNames } from 'oldschooljs/dist/constants'; +import type { BossRecords } from 'oldschooljs/dist/meta/types'; + +import type { OSBMahojiCommand } from '../lib/util'; + +export const kcCommand: OSBMahojiCommand = { + name: 'kc', + description: 'See your OSRS kc for a monster/boss.', + attributes: { + examples: ['/kc name:General Graardor'] + }, + options: [ + { + type: ApplicationCommandOptionType.String, + name: 'rsn', + description: 'The runescape username to check', + required: true + }, + { + type: ApplicationCommandOptionType.String, + name: 'boss', + description: 'The boss you want to check', + required: true, + autocomplete: async (value: string) => { + return mappedBossNames + .filter(i => (!value ? true : (i[0] + i[1]).toLowerCase().includes(value.toLowerCase()))) + .map(i => ({ name: i[1], value: i[1] })); + } + } + ], + run: async ({ options }: CommandRunOptions<{ rsn: string; boss: string }>) => { + try { + const { bossRecords } = await Hiscores.fetch(options.rsn); + + for (const [boss, { rank, score }] of Object.entries(bossRecords)) { + if (stringMatches(boss, options.boss)) { + if (score === -1 || rank === -1) { + return `${toTitleCase(options.rsn)}'s has no recorded KC for that boss.`; + } + return `${toTitleCase(options.rsn)}'s ${bossNameMap.get( + boss as keyof BossRecords + )} KC is **${score.toLocaleString()}** (Rank ${rank.toLocaleString()})`; + } + } + + return `${toTitleCase(options.rsn)} doesn't have any recorded kills for that boss.`; + } catch (err: any) { + return err.message; + } + } +}; diff --git a/src/mahoji/commands/kibble.ts b/src/mahoji/commands/kibble.ts index a72b5d51abe..e3a62f797b6 100644 --- a/src/mahoji/commands/kibble.ts +++ b/src/mahoji/commands/kibble.ts @@ -1,16 +1,17 @@ +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { Time } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; import { Bank } from 'oldschooljs'; -import { Eatable, Eatables } from '../../lib/data/eatables'; +import { type Eatable, Eatables } from '../../lib/data/eatables'; import { kibbles } from '../../lib/data/kibble'; import { SkillsEnum } from '../../lib/skilling/types'; -import { KibbleOptions } from '../../lib/types/minions'; +import type { KibbleOptions } from '../../lib/types/minions'; import { formatDuration, itemNameFromID, stringMatches } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; import { updateBankSetting } from '../../lib/util/updateBankSetting'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const kibbleCommand: OSBMahojiCommand = { name: 'kibble', @@ -52,7 +53,7 @@ export const kibbleCommand: OSBMahojiCommand = { const cost = new Bank(); const qtyPerComponent = 5 * (kibbles.indexOf(kibble) + 1); - let totalQtyPerComponent = qtyPerComponent * options.quantity; + const totalQtyPerComponent = qtyPerComponent * options.quantity; const herbComponent = kibble.herbComponent.find(i => userBank.amount(i.id) >= totalQtyPerComponent); if (!herbComponent) { @@ -62,7 +63,7 @@ export const kibbleCommand: OSBMahojiCommand = { } cost.add(herbComponent.id, totalQtyPerComponent); - let herbsNeeded = Math.ceil(totalQtyPerComponent / 2); + const herbsNeeded = Math.ceil(totalQtyPerComponent / 2); const cropComponent = kibble.cropComponent.find(i => userBank.amount(i.id) >= herbsNeeded); if (!cropComponent) { return `You need ${herbsNeeded} of one of these crops for ${kibble.item.name}: ${kibble.cropComponent @@ -71,13 +72,13 @@ export const kibbleCommand: OSBMahojiCommand = { } cost.add(cropComponent.id, herbsNeeded); - let healAmountNeeded = qtyPerComponent * kibble.minimumFishHeal; + const healAmountNeeded = qtyPerComponent * kibble.minimumFishHeal; const calcFish = (fish: Eatable) => Math.ceil( (healAmountNeeded * options.quantity) / (typeof fish.healAmount === 'number' ? fish.healAmount : fish.healAmount(user)) ); - let suitableFish = Eatables.filter( + const suitableFish = Eatables.filter( i => i.raw && (typeof i.healAmount === 'number' ? i.healAmount : i.healAmount(user)) >= kibble.minimumFishHeal @@ -93,15 +94,15 @@ export const kibbleCommand: OSBMahojiCommand = { .map(i => itemNameFromID(i.raw!)) .join(', ')}.`; } - let fishNeeded = calcFish(rawFishComponent); + const fishNeeded = calcFish(rawFishComponent); cost.add(rawFishComponent.raw!, fishNeeded); let timePer = Time.Second * 2; if (user.usingPet('Remy')) { timePer = Math.floor(timePer / 2); } - let duration = timePer * options.quantity; - let maxTripLength = calcMaxTripLength(user, 'KibbleMaking'); + const duration = timePer * options.quantity; + const maxTripLength = calcMaxTripLength(user, 'KibbleMaking'); if (duration > calcMaxTripLength(user, 'KibbleMaking')) { return `The maximum amount of ${kibble.item.name} you can create in ${formatDuration( calcMaxTripLength(user, 'KibbleMaking') diff --git a/src/mahoji/commands/kill.ts b/src/mahoji/commands/kill.ts index e9506acd9eb..492d3bfc1f8 100644 --- a/src/mahoji/commands/kill.ts +++ b/src/mahoji/commands/kill.ts @@ -1,5 +1,6 @@ import { toTitleCase } from '@oldschoolgg/toolkit'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { Bank, Monsters } from 'oldschooljs'; import { PerkTier } from '../../lib/constants'; @@ -8,9 +9,9 @@ import { stringMatches } from '../../lib/util'; import { deferInteraction } from '../../lib/util/interactionReply'; import { makeBankImage } from '../../lib/util/makeBankImage'; import { Workers } from '../../lib/workers'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; -export function determineKillLimit(user: MUser) { +function determineKillLimit(user: MUser) { const perkTier = user.perkTier(); if (perkTier >= PerkTier.Six) { @@ -73,7 +74,7 @@ export const killCommand: OSBMahojiCommand = { ], run: async ({ options, userID, interaction }: CommandRunOptions<{ name: string; quantity: number }>) => { const user = await mUserFetch(userID); - deferInteraction(interaction); + await deferInteraction(interaction); const osjsMonster = Monsters.find(mon => mon.aliases.some(alias => stringMatches(alias, options.name))); const simulatedKillable = simulatedKillables.find(i => stringMatches(i.name, options.name)); @@ -98,7 +99,7 @@ export const killCommand: OSBMahojiCommand = { limit, catacombs: false, onTask: false, - lootTableTertiaryChanges: Array.from(user.buildCATertiaryItemChanges().entries()) + lootTableTertiaryChanges: Array.from(user.buildTertiaryItemChanges().entries()) }); if (result.error) { diff --git a/src/mahoji/commands/laps.ts b/src/mahoji/commands/laps.ts index 9f9bfe1035a..3278ac813a8 100644 --- a/src/mahoji/commands/laps.ts +++ b/src/mahoji/commands/laps.ts @@ -1,17 +1,18 @@ +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { Time } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; import { Bank } from 'oldschooljs'; import { BitField } from '../../lib/constants'; -import { inventionBoosts, InventionID, inventionItemBoost } from '../../lib/invention/inventions'; +import { InventionID, inventionBoosts, inventionItemBoost } from '../../lib/invention/inventions'; import { courses } from '../../lib/skilling/skills/agility'; import { SkillsEnum } from '../../lib/skilling/types'; -import { AgilityActivityTaskOptions } from '../../lib/types/minions'; +import type { AgilityActivityTaskOptions } from '../../lib/types/minions'; import { formatDuration, stringMatches } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; import { updateBankSetting } from '../../lib/util/updateBankSetting'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; const unlimitedFireRuneProviders = [ 'Staff of fire', @@ -139,7 +140,7 @@ export const lapsCommand: OSBMahojiCommand = { } if (course.qpRequired && user.QP < course.qpRequired) { - return `You need atleast ${course.qpRequired} Quest Points to do this course.`; + return `You need at least ${course.qpRequired} Quest Points to do this course.`; } if (course.name === 'Daemonheim Rooftop Course' && !user.bitfield.includes(BitField.HasDaemonheimAgilityPass)) { @@ -150,7 +151,7 @@ export const lapsCommand: OSBMahojiCommand = { let timePerLap = course.lapTime * Time.Second; - let boosts: string[] = []; + const boosts: string[] = []; if (user.hasEquippedOrInBank('Silverhawk boots')) { const boostedTimePerLap = Math.floor(timePerLap / inventionBoosts.silverHawks.agilityBoostMultiplier); const costRes = await inventionItemBoost({ @@ -191,12 +192,12 @@ export const lapsCommand: OSBMahojiCommand = { course.name === 'Ape Atoll Agility Course' ? null : !options.alch - ? null - : alching({ - user, - tripLength: duration, - isUsingVoidling: user.usingPet('Voidling') - }); + ? null + : alching({ + user, + tripLength: duration, + isUsingVoidling: user.usingPet('Voidling') + }); if (alchResult !== null) { if (!user.owns(alchResult.bankToRemove)) { return `You don't own ${alchResult.bankToRemove}.`; @@ -223,7 +224,7 @@ export const lapsCommand: OSBMahojiCommand = { : { itemID: alchResult.itemToAlch.id, quantity: alchResult.maxCasts - } + } }); return response; diff --git a/src/mahoji/commands/leaderboard.ts b/src/mahoji/commands/leaderboard.ts index ce813dbbf1e..dc57ab4c4f9 100644 --- a/src/mahoji/commands/leaderboard.ts +++ b/src/mahoji/commands/leaderboard.ts @@ -1,18 +1,22 @@ -/* eslint-disable @typescript-eslint/no-floating-promises */ -import { toTitleCase } from '@oldschoolgg/toolkit'; -import { Prisma, UserStats } from '@prisma/client'; -import { ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; -import { calcWhatPercent, chunk, objectValues, Time } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; - -import { ClueTier, ClueTiers } from '../../lib/clues/clueTiers'; -import { badges, badgesCache, Emoji, masteryKey, usernameCache } from '../../lib/constants'; +import { type CommandRunOptions, toTitleCase } from '@oldschoolgg/toolkit'; + +import type { UserStats } from '@prisma/client'; +import { + ApplicationCommandOptionType, + type ChatInputCommandInteraction, + EmbedBuilder, + type MessageEditOptions +} from 'discord.js'; +import { calcWhatPercent, chunk, isFunction } from 'e'; +import type { ClueTier } from '../../lib/clues/clueTiers'; +import { ClueTiers } from '../../lib/clues/clueTiers'; +import { masteryKey } from '../../lib/constants'; import { allClNames, getCollectionItems } from '../../lib/data/Collections'; import { allLeagueTasks } from '../../lib/leagues/leagues'; import { effectiveMonsters } from '../../lib/minions/data/killableMonsters'; import { allOpenables } from '../../lib/openables'; import { Minigames } from '../../lib/settings/minigames'; -import { prisma } from '../../lib/settings/prisma'; + import Skills from '../../lib/skilling/skills'; import Agility from '../../lib/skilling/skills/agility'; import Hunter from '../../lib/skilling/skills/hunter/hunter'; @@ -22,15 +26,15 @@ import { convertXPtoLVL, formatDuration, getUsername, + getUsernameSync, makePaginatedMessage, - stringMatches, - stripEmojis + stringMatches } from '../../lib/util'; import { fetchCLLeaderboard, fetchTameCLLeaderboard } from '../../lib/util/clLeaderboard'; import { deferInteraction } from '../../lib/util/interactionReply'; import { userEventsToMap } from '../../lib/util/userEvents'; import { sendToChannelID } from '../../lib/util/webhook'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; const LB_PAGE_SIZE = 10; @@ -47,11 +51,12 @@ export function getPos(page: number, record: number) { return `${page * LB_PAGE_SIZE + 1 + record}. `; } +export type AsyncPageString = () => Promise; export async function doMenu( interaction: ChatInputCommandInteraction, user: MUser, channelID: string, - pages: string[], + pages: string[] | AsyncPageString[], title: string ) { if (pages.length === 0) { @@ -62,11 +67,68 @@ export async function doMenu( makePaginatedMessage( channel, - pages.map(p => ({ embeds: [new EmbedBuilder().setTitle(title).setDescription(p)] })), + pages.map(p => { + if (isFunction(p)) { + return async () => ({ embeds: [new EmbedBuilder().setTitle(title).setDescription(await p())] }); + } + + return { embeds: [new EmbedBuilder().setTitle(title).setDescription(p)] }; + }), user.id ); } +function doMenuWrapper({ + user, + channelID, + users, + title, + ironmanOnly, + formatter +}: { + ironmanOnly: boolean; + users: { id: string; score: number }[]; + title: string; + interaction: ChatInputCommandInteraction; + user: MUser; + channelID: string; + formatter?: (val: number) => string; +}) { + const chunked = chunk(users, LB_PAGE_SIZE); + const pages: (() => Promise)[] = []; + for (let c = 0; c < chunked.length; c++) { + const makePage = async () => { + const chnk = chunked[c]; + const unwaited = chnk.map( + async (user, i) => + `${getPos(c, i)}**${await getUsername(user.id)}:** ${formatter ? formatter(user.score) : user.score.toLocaleString()}` + ); + const pageText = (await Promise.all(unwaited)).join('\n'); + return { embeds: [new EmbedBuilder().setTitle(title).setDescription(pageText)] }; + }; + pages.push(makePage); + } + if (pages.length === 0) { + return 'There are no users on this leaderboard.'; + } + const channel = globalClient.channels.cache.get(channelID); + if (!channelIsSendable(channel)) return 'Invalid channel.'; + + makePaginatedMessage( + channel, + pages.map(p => { + if (isFunction(p)) { + return p; + } + + return { embeds: [new EmbedBuilder().setTitle(title).setDescription(p)] }; + }), + user.id + ); + + return lbMsg(title, ironmanOnly); +} + async function kcLb( interaction: ChatInputCommandInteraction, user: MUser, @@ -76,29 +138,24 @@ async function kcLb( ) { const monster = effectiveMonsters.find(mon => [mon.name, ...mon.aliases].some(alias => stringMatches(alias, name))); if (!monster) return "That's not a valid monster!"; - let list = await prisma.$queryRawUnsafe<{ id: string; kc: number }[]>( - `SELECT user_id::text AS id, CAST("monster_scores"->>'${monster.id}' AS INTEGER) as kc + const list = await prisma.$queryRawUnsafe<{ id: string; score: number }[]>( + `SELECT user_id::text AS id, CAST("monster_scores"->>'${monster.id}' AS INTEGER) as score FROM user_stats ${ironmanOnly ? 'INNER JOIN "users" on "users"."id" = "user_stats"."user_id"::text' : ''} WHERE CAST("monster_scores"->>'${monster.id}' AS INTEGER) > 5 ${ironmanOnly ? ' AND "users"."minion.ironman" = true ' : ''} - ORDER BY kc DESC + ORDER BY score DESC LIMIT 2000;` ); - doMenu( - interaction, + return doMenuWrapper({ + ironmanOnly, user, + interaction, channelID, - chunk(list, LB_PAGE_SIZE).map((subList, i) => - subList - .map((user, j) => `${getPos(i, j)}**${getUsername(user.id)}:** ${user.kc.toLocaleString()}`) - .join('\n') - ), - `KC Leaderboard for ${monster.name}` - ); - - return lbMsg(`${monster.name} KC `, ironmanOnly); + users: list, + title: `KC Leaderboard for ${monster.name}` + }); } async function farmingContractLb( @@ -107,7 +164,7 @@ async function farmingContractLb( channelID: string, ironmanOnly: boolean ) { - let list = await prisma.$queryRawUnsafe<{ id: string; count: number }[]>( + const list = await prisma.$queryRawUnsafe<{ id: string; count: number }[]>( `SELECT id, CAST("minion.farmingContract"->>'contractsCompleted' AS INTEGER) as count FROM users WHERE "minion.farmingContract" is not null and CAST ("minion.farmingContract"->>'contractsCompleted' AS INTEGER) >= 1 @@ -123,7 +180,7 @@ async function farmingContractLb( chunk(list, LB_PAGE_SIZE).map((subList, i) => subList .map(({ id, count }, j) => { - return `${getPos(i, j)}**${getUsername(id)}:** ${count.toLocaleString()}`; + return `${getPos(i, j)}**${getUsernameSync(id)}:** ${count.toLocaleString()}`; }) .join('\n') ), @@ -146,7 +203,7 @@ LIMIT 10;`); } return `**Inferno Records**\n\n${res - .map((e, i) => `${i + 1}. **${getUsername(e.user_id)}:** ${formatDuration(e.duration)}`) + .map((e, i) => `${i + 1}. **${getUsernameSync(e.user_id)}:** ${formatDuration(e.duration)}`) .join('\n')}`; } // Leaderboard for BSO general boxSpawn.ts event @@ -169,7 +226,7 @@ async function bsoChallenge(interaction: ChatInputCommandInteraction, user: MUse subList .map( ({ id, challengescore }, j) => - `${getPos(i, j)}**${getUsername(id)}:** ${challengescore.toLocaleString()} Challenges` + `${getPos(i, j)}**${getUsernameSync(id)}:** ${challengescore.toLocaleString()} Challenges` ) .join('\n') ), @@ -195,7 +252,7 @@ async function sacrificeLb( ORDER BY "sacrificedValue" DESC LIMIT 2000;` ) - ).map((res: any) => ({ ...res, amount: parseInt(res.sacrificedValue) })); + ).map((res: any) => ({ ...res, amount: Number.parseInt(res.sacrificedValue) })); doMenu( interaction, @@ -203,7 +260,10 @@ async function sacrificeLb( channelID, chunk(list, LB_PAGE_SIZE).map((subList, i) => subList - .map(({ id, amount }, j) => `${getPos(i, j)}**${getUsername(id)}:** ${amount.toLocaleString()} GP `) + .map( + ({ id, amount }, j) => + `${getPos(i, j)}**${getUsernameSync(id)}:** ${amount.toLocaleString()} GP ` + ) .join('\n') ), 'Sacrifice Leaderboard' @@ -229,7 +289,7 @@ async function sacrificeLb( subList .map( ({ id, sacbanklength }, j) => - `${getPos(i, j)}**${getUsername(id)}:** ${sacbanklength.toLocaleString()} Unique Sac's` + `${getPos(i, j)}**${getUsernameSync(id)}:** ${sacbanklength.toLocaleString()} Unique Sac's` ) .join('\n') ), @@ -258,7 +318,7 @@ async function minigamesLb(interaction: ChatInputCommandInteraction, user: MUser channelID, chunk(titheCompletions, LB_PAGE_SIZE).map((subList, i) => subList - .map(({ id, amount }, j) => `${getPos(i, j)}**${getUsername(id)}:** ${amount.toLocaleString()}`) + .map(({ id, amount }, j) => `${getPos(i, j)}**${getUsernameSync(id)}:** ${amount.toLocaleString()}`) .join('\n') ), 'Tithe farm Leaderboard' @@ -277,18 +337,14 @@ async function minigamesLb(interaction: ChatInputCommandInteraction, user: MUser take: 10 }); - doMenu( - interaction, + return doMenuWrapper({ + ironmanOnly: false, user, + interaction, channelID, - chunk(res, LB_PAGE_SIZE).map((subList, i) => - subList - .map((u, j) => `${getPos(i, j)}**${getUsername(u.user_id)}:** ${u[minigame.column].toLocaleString()}`) - .join('\n') - ), - `${minigame.name} Leaderboard` - ); - return lbMsg(`${minigame.name} Leaderboard`); + users: res.map(u => ({ id: u.user_id, score: u[minigame.column] })), + title: `${minigame.name} Leaderboard` + }); } async function clLb( @@ -301,7 +357,7 @@ async function clLb( ) { const { resolvedCl, items } = getCollectionItems(inputType, false, false, true); if (!items || items.length === 0) { - return "That's not a valid collection log category. Check +cl for all possible logs."; + return "That's not a valid collection log category. Check /cl for all possible logs."; } inputType = toTitleCase(inputType.toLowerCase()); @@ -313,7 +369,10 @@ async function clLb( channelID, chunk(tameLb, LB_PAGE_SIZE).map((subList, i) => subList - .map(({ user_id, qty }, j) => `${getPos(i, j)}**${getUsername(user_id)}:** ${qty.toLocaleString()}`) + .map( + ({ user_id, qty }, j) => + `${getPos(i, j)}**${getUsernameSync(user_id)}:** ${qty.toLocaleString()}` + ) .join('\n') ), `${inputType} Tame Collection Log Leaderboard (${items.length} slots)` @@ -321,35 +380,18 @@ async function clLb( return lbMsg(`${inputType} Tame Collection Log Leaderboard`); } - const userEventOrders = await prisma.userEvent.findMany({ - where: { - type: 'CLCompletion', - collection_log_name: resolvedCl.toLowerCase() - }, - orderBy: { - date: 'asc' - } - }); + const { users } = await fetchCLLeaderboard({ ironmenOnly, items, resultLimit: 200, clName: resolvedCl }); + inputType = toTitleCase(inputType.toLowerCase()); - const users = await fetchCLLeaderboard({ ironmenOnly, items, resultLimit: 200, userEvents: userEventOrders }); - doMenu( - interaction, + return doMenuWrapper({ + ironmanOnly: ironmenOnly, user, + interaction, channelID, - chunk(users, LB_PAGE_SIZE).map((subList, i) => - subList - .map( - ({ id, qty }, j) => - `${getPos(i, j)}**${getUsername(id)}:** ${qty.toLocaleString()} (${calcWhatPercent( - qty, - items.length - ).toFixed(1)}%)` - ) - .join('\n') - ), - `${inputType} Collection Log Leaderboard (${items.length} slots)` - ); - return lbMsg(`${inputType} Collection Log Leaderboard`, ironmenOnly); + users: users.map(u => ({ id: u.id, score: u.qty })), + title: `${inputType} Collection Log Leaderboard`, + formatter: val => `${val.toLocaleString()} (${calcWhatPercent(val, items.length).toFixed(1)}%)` + }); } async function creaturesLb( @@ -376,7 +418,7 @@ async function creaturesLb( channelID, chunk(data, LB_PAGE_SIZE).map((subList, i) => subList - .map(({ id, count }, j) => `${getPos(i, j)}**${getUsername(id)}:** ${count.toLocaleString()}`) + .map(({ id, count }, j) => `${getPos(i, j)}**${getUsernameSync(id)}:** ${count.toLocaleString()}`) .join('\n') ), `Catch Leaderboard for ${creature.name}` @@ -401,7 +443,7 @@ async function lapsLb(interaction: ChatInputCommandInteraction, user: MUser, cha channelID, chunk(data, LB_PAGE_SIZE).map((subList, i) => subList - .map(({ id, count }, j) => `${getPos(i, j)}**${getUsername(id)}:** ${count.toLocaleString()}`) + .map(({ id, count }, j) => `${getPos(i, j)}**${getUsernameSync(id)}:** ${count.toLocaleString()}`) .join('\n') ), `${course.name} Laps Leaderboard` @@ -416,7 +458,9 @@ async function openLb( name: string, ironmanOnly: boolean ) { - name = name.trim(); + if (name) { + name = name.trim(); + } let entityID = -1; let key = ''; @@ -426,7 +470,7 @@ async function openLb( ? undefined : allOpenables.find( item => stringMatches(item.name, name) || item.name.toLowerCase().includes(name.toLowerCase()) - ); + ); if (openable) { entityID = openable.id; key = 'openable_scores'; @@ -437,7 +481,7 @@ async function openLb( return `That's not a valid openable item! You can check: ${allOpenables.map(i => i.name).join(', ')}.`; } - let list = await prisma.$queryRawUnsafe<{ id: string; qty: number }[]>( + const list = await prisma.$queryRawUnsafe<{ id: string; qty: number }[]>( `SELECT user_id::text AS id, ("${key}"->>'${entityID}')::int as qty FROM user_stats ${ironmanOnly ? 'INNER JOIN users ON users.id::bigint = user_stats.user_id' : ''} WHERE ("${key}"->>'${entityID}')::int > 3 @@ -445,18 +489,14 @@ async function openLb( ORDER BY qty DESC LIMIT 30;` ); - doMenu( - interaction, + return doMenuWrapper({ + ironmanOnly, user, + interaction, channelID, - chunk(list, LB_PAGE_SIZE).map((subList, i) => - subList - .map((user, j) => `${getPos(i, j)}**${getUsername(user.id)}:** ${user.qty.toLocaleString()}`) - .join('\n') - ), - `Open Leaderboard for ${openableName}` - ); - return lbMsg(`${openableName} Opening`); + users: list.map(u => ({ id: u.id, score: u.qty })), + title: `${openableName} Opening Leaderboard` + }); } async function gpLb(interaction: ChatInputCommandInteraction, user: MUser, channelID: string, ironmanOnly: boolean) { @@ -467,22 +507,19 @@ async function gpLb(interaction: ChatInputCommandInteraction, user: MUser, chann WHERE "GP" > 1000000 ${ironmanOnly ? ' AND "minion.ironman" = true ' : ''} ORDER BY "GP" DESC - LIMIT 500;` + LIMIT 100;` ) - ).map(res => ({ ...res, GP: Number(res.GP) })); + ).map(res => ({ ...res, score: Number(res.GP) })); - doMenu( - interaction, + return doMenuWrapper({ + ironmanOnly, user, + interaction, channelID, - chunk(users, LB_PAGE_SIZE).map((subList, i) => - subList - .map(({ id, GP }, j) => `${getPos(i, j)}**${getUsername(id)}:** ${GP.toLocaleString()} GP`) - .join('\n') - ), - 'GP Leaderboard' - ); - return lbMsg('GP Leaderboard', ironmanOnly); + users, + title: 'GP Leaderboard', + formatter: val => `${val.toLocaleString()} GP` + }); } async function skillsLb( @@ -617,7 +654,7 @@ async function skillsLb( chunk(overallUsers, LB_PAGE_SIZE).map((subList, i) => subList .map((obj, j) => { - return `${getPos(i, j)}**${getUsername( + return `${getPos(i, j)}**${getUsernameSync( obj.id )}:** ${obj.totalLevel.toLocaleString()} (${obj.totalXP.toLocaleString()} XP)`; }) @@ -638,7 +675,7 @@ async function skillsLb( const objKey = `skills.${skill?.id}`; const skillXP = Number(obj[objKey] ?? 0); - return `${getPos(i, j)}**${getUsername(obj.id)}:** ${skillXP.toLocaleString()} XP (${convertXPtoLVL( + return `${getPos(i, j)}**${getUsernameSync(obj.id)}:** ${skillXP.toLocaleString()} XP (${convertXPtoLVL( skillXP, 120 )})`; @@ -647,7 +684,7 @@ async function skillsLb( ), `${skill ? toTitleCase(skill.id) : 'Overall'} Leaderboard` ); - return lbMsg(`Overall ${skill!.name} ${type}`); + return lbMsg(`Overall ${skill?.name} ${type}`); } async function cluesLb( @@ -678,7 +715,10 @@ LIMIT 50;` channelID, chunk(users, LB_PAGE_SIZE).map((subList, i) => subList - .map(({ id, score }, j) => `${getPos(i, j)}**${getUsername(id)}:** ${score.toLocaleString()} Completed`) + .map( + ({ id, score }, j) => + `${getPos(i, j)}**${getUsernameSync(id)}:** ${score.toLocaleString()} Completed` + ) .join('\n') ), `${clueTier.name} Clue Leaderboard` @@ -686,105 +726,6 @@ LIMIT 50;` return lbMsg('Clue Leaderboard', ironmanOnly); } -export async function cacheUsernames() { - const roboChimpUseresToCache = await roboChimpClient.user.findMany({ - where: { - OR: [ - { - osb_cl_percent: { - gte: 80 - } - }, - { - bso_total_level: { - gte: 80 - } - }, - { - osb_total_level: { - gte: 1500 - } - }, - { - bso_total_level: { - gte: 1500 - } - }, - { - leagues_points_total: { - gte: 20_000 - } - } - ] - }, - select: { - id: true - } - }); - - let orConditions: Prisma.UserWhereInput[] = []; - for (const skill of objectValues(SkillsEnum)) { - orConditions.push({ - [`skills_${skill}`]: { - gte: 15_000_000 - } - }); - } - const usersToCache = await prisma.user.findMany({ - where: { - OR: [ - ...orConditions, - { - last_command_date: { - gt: new Date(Date.now() - Number(Time.Month)) - } - } - ], - id: { - notIn: roboChimpUseresToCache.map(i => i.id.toString()) - } - }, - select: { - id: true - } - }); - - const userIDsToCache = [...usersToCache, ...roboChimpUseresToCache].map(i => i.id.toString()); - debugLog(`Caching usernames of ${userIDsToCache.length} users`); - - const allNewUsers = await prisma.newUser.findMany({ - where: { - username: { - not: null - }, - id: { - in: userIDsToCache - } - }, - select: { - id: true, - username: true - } - }); - - const arrayOfIronmenAndBadges: { badges: number[]; id: string; ironman: boolean }[] = await prisma.$queryRawUnsafe( - 'SELECT "badges", "id", "minion.ironman" as "ironman" FROM users WHERE ARRAY_LENGTH(badges, 1) > 0 OR "minion.ironman" = true;' - ); - - for (const user of allNewUsers) { - const badgeUser = arrayOfIronmenAndBadges.find(i => i.id === user.id); - const name = stripEmojis(user.username!); - usernameCache.set(user.id, name); - if (badgeUser) { - const rawBadges = badgeUser.badges.map(num => badges[num]); - if (badgeUser.ironman) { - rawBadges.push(Emoji.Ironman); - } - badgesCache.set(user.id, rawBadges.join(' ')); - } - } -} - async function itemContractLb( interaction: ChatInputCommandInteraction, user: MUser, @@ -813,7 +754,9 @@ async function itemContractLb( user, channelID, chunk(results, 10).map(subList => - subList.map(({ id, item_contract_streak }) => `**${getUsername(id)}:** ${item_contract_streak}`).join('\n') + subList + .map(({ id, item_contract_streak }) => `**${getUsernameSync(id)}:** ${item_contract_streak}`) + .join('\n') ), 'Item Contract Streak Leaderboard' ); @@ -850,7 +793,7 @@ LIMIT 10; subList .map( ({ id, osb_xp_percent, bso_xp_percent }, j) => - `${getPos(i, j)}**${getUsername(id)}:** ${osb_xp_percent.toFixed( + `${getPos(i, j)}**${getUsernameSync(id)}:** ${osb_xp_percent.toFixed( 2 )}% OSB, ${bso_xp_percent.toFixed(2)}% BSO` ) @@ -878,16 +821,16 @@ LIMIT 10; user, channelID, chunk(result, LB_PAGE_SIZE).map((subList, i) => - subList.map(({ id, avg }, j) => `${getPos(i, j)}**${getUsername(id)}:** ${avg.toFixed(2)}%`).join('\n') + subList + .map(({ id, avg }, j) => `${getPos(i, j)}**${getUsernameSync(id)}:** ${avg.toFixed(2)}%`) + .join('\n') ), 'Global (OSB+BSO) Mastery Leaderboard' ); return lbMsg('Global Mastery Leaderboard'); } - const result = await roboChimpClient.$queryRaw< - { id: string; total_cl_percent: number }[] - >`SELECT ((osb_cl_percent + bso_cl_percent) / 2) AS total_cl_percent, id::text AS id + const result = await roboChimpClient.$queryRaw<{ id: string; total_cl_percent: number }[]>`SELECT ((osb_cl_percent + bso_cl_percent) / 2) AS total_cl_percent, id::text AS id FROM public.user WHERE osb_cl_percent IS NOT NULL AND bso_cl_percent IS NOT NULL ORDER BY total_cl_percent DESC @@ -897,12 +840,11 @@ LIMIT 20;`; interaction, user, channelID, - chunk(result, LB_PAGE_SIZE).map((subList, i) => subList .map( ({ id, total_cl_percent }, j) => - `${getPos(i, j)}**${getUsername(id)}:** ${total_cl_percent.toLocaleString()}%` + `${getPos(i, j)}**${getUsernameSync(id)}:** ${total_cl_percent.toLocaleString()}%` ) .join('\n') ), @@ -931,7 +873,7 @@ async function leaguesPointsLeaderboard(interaction: ChatInputCommandInteraction subList .map( ({ id, leagues_points_total }) => - `**${getUsername(id)}:** ${leagues_points_total.toLocaleString()} Pts` + `**${getUsernameSync(id)}:** ${leagues_points_total.toLocaleString()} Pts` ) .join('\n') ), @@ -941,16 +883,14 @@ async function leaguesPointsLeaderboard(interaction: ChatInputCommandInteraction } async function leastCompletedLeagueTasksLb() { - const taskCounts = await roboChimpClient.$queryRaw< - { task_id: number; qty: number }[] - >`SELECT task_id, count(*) AS qty + const taskCounts = await roboChimpClient.$queryRaw<{ task_id: number; qty: number }[]>`SELECT task_id, count(*)::int AS qty FROM ( SELECT unnest(leagues_completed_tasks_ids) AS task_id FROM public.user ) sub GROUP BY 1 ORDER BY 2 ASC;`; - let taskObj: Record = {}; + const taskObj: Record = {}; for (const task of allLeagueTasks) { taskObj[task.id] = 0; } @@ -963,7 +903,7 @@ ${Object.entries(taskObj) .sort((a, b) => a[1] - b[1]) .slice(0, 10) .map(task => { - const taskObj = allLeagueTasks.find(t => t.id === parseInt(task[0]))!; + const taskObj = allLeagueTasks.find(t => t.id === Number.parseInt(task[0]))!; return `${taskObj.name}: ${task[1]} users completed`; }) .join('\n')} @@ -973,7 +913,7 @@ ${Object.entries(taskObj) .sort((a, b) => b[1] - a[1]) .slice(0, 10) .map((task, index) => { - const taskObj = allLeagueTasks.find(t => t.id === parseInt(task[0]))!; + const taskObj = allLeagueTasks.find(t => t.id === Number.parseInt(task[0]))!; return `${index + 1}. ${taskObj.name}`; }) .join('\n')}`; @@ -987,7 +927,7 @@ async function compLeaderboard( channelID: string ) { const key: keyof UserStats = untrimmed ? 'untrimmed_comp_cape_percent' : 'comp_cape_percent'; - let list = await prisma.$queryRawUnsafe<{ id: string; percent: number }[]>( + const list = await prisma.$queryRawUnsafe<{ id: string; percent: number }[]>( `SELECT user_id::text AS id, ${key} AS percent FROM user_stats ${ironmanOnly ? 'INNER JOIN "users" on "users"."id" = "user_stats"."user_id"::text' : ''} @@ -1005,7 +945,7 @@ async function compLeaderboard( subList .map( ({ id, percent }) => - `**${getUsername(id)}:** ${percent.toFixed(2)}% ${untrimmed ? 'Untrimmed' : 'Trimmed'}` + `**${getUsernameSync(id)}:** ${percent.toFixed(2)}% ${untrimmed ? 'Untrimmed' : 'Trimmed'}` ) .join('\n') ), @@ -1022,8 +962,7 @@ async function leaguesLeaderboard( ) { if (type === 'points') return leaguesPointsLeaderboard(interaction, user, channelID); if (type === 'hardest_tasks') return leastCompletedLeagueTasksLb(); - const result: { id: number; tasks_completed: number }[] = - await roboChimpClient.$queryRaw`SELECT id::text, COALESCE(cardinality(leagues_completed_tasks_ids), 0) AS tasks_completed + const result: { id: number; tasks_completed: number }[] = await roboChimpClient.$queryRaw`SELECT id::text, COALESCE(cardinality(leagues_completed_tasks_ids), 0) AS tasks_completed FROM public.user ORDER BY tasks_completed DESC LIMIT 100;`; @@ -1035,7 +974,7 @@ async function leaguesLeaderboard( subList .map( ({ id, tasks_completed }) => - `**${getUsername(id.toString())}:** ${tasks_completed.toLocaleString()} Tasks` + `**${getUsernameSync(id.toString())}:** ${tasks_completed.toLocaleString()} Tasks` ) .join('\n') ), @@ -1115,7 +1054,7 @@ LIMIT 10; subList .map( ({ user_id, cl_completion_count, cl_global_rank, count_increase, rank_difference }, j) => - `${getPos(i, j)}**${getUsername( + `${getPos(i, j)}**${getUsernameSync( user_id )}:** Gained ${count_increase} CL slots, from ${cl_completion_count} to ${ cl_completion_count + count_increase @@ -1146,7 +1085,8 @@ LIMIT 50;` chunk(users, LB_PAGE_SIZE).map((subList, i) => subList .map( - ({ id, qty }, j) => `${getPos(i, j)}**${getUsername(id)}:** ${qty.toLocaleString()} Tasks Completed` + ({ id, qty }, j) => + `${getPos(i, j)}**${getUsernameSync(id)}:** ${qty.toLocaleString()} Tasks Completed` ) .join('\n') ), @@ -1156,36 +1096,32 @@ LIMIT 50;` } async function masteryLb(interaction: ChatInputCommandInteraction, user: MUser, channelID: string) { - const users = await roboChimpClient.user.findMany({ - where: { - [masteryKey]: { not: null } - }, - orderBy: { - [masteryKey]: 'desc' - }, - take: 50, - select: { - id: true, - osb_mastery: true, - bso_mastery: true - } - }); + const users = ( + await roboChimpClient.user.findMany({ + where: { + [masteryKey]: { not: null } + }, + orderBy: { + [masteryKey]: 'desc' + }, + take: 50, + select: { + id: true, + osb_mastery: true, + bso_mastery: true + } + }) + ).map(u => ({ id: u.id.toString(), score: u[masteryKey] ?? 0 })); - doMenu( + return doMenuWrapper({ interaction, - user, + title: 'Mastery Leaderboard', channelID, - chunk(users, LB_PAGE_SIZE).map((subList, i) => - subList - .map( - (lUser, j) => - `${getPos(i, j)}**${getUsername(lUser.id)}:** ${lUser[masteryKey]!.toFixed(3)}% mastery` - ) - .join('\n') - ), - 'Mastery Leaderboard' - ); - return lbMsg('Mastery Leaderboard'); + ironmanOnly: false, + user, + users, + formatter: val => `${val.toFixed(3)}% mastery` + }); } const ironmanOnlyOption = { @@ -1360,7 +1296,7 @@ export const leaderboardCommand: OSBMahojiCommand = { ? true : [i.name, ...i.aliases].some(str => str.toLowerCase().includes(value.toLowerCase()) - ) + ) ) .map(i => ({ name: i.name, value: i.name })); } @@ -1516,7 +1452,7 @@ export const leaderboardCommand: OSBMahojiCommand = { combat_achievements?: {}; mastery?: {}; }>) => { - deferInteraction(interaction); + await deferInteraction(interaction); const user = await mUserFetch(userID); const { opens, diff --git a/src/mahoji/commands/leagues.ts b/src/mahoji/commands/leagues.ts index e2f640f7e5f..9312cea8f2b 100644 --- a/src/mahoji/commands/leagues.ts +++ b/src/mahoji/commands/leagues.ts @@ -1,22 +1,22 @@ -import { calcWhatPercent, Time } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import { ApplicationCommandOptionType } from 'discord.js'; +import { Time, calcWhatPercent } from 'e'; import { production } from '../../config'; import { PerkTier } from '../../lib/constants'; import { allLeagueTasks, generateLeaguesTasksTextFile, + leagueTasks, leaguesCheckUser, - leaguesClaimCommand, - leagueTasks + leaguesClaimCommand } from '../../lib/leagues/leagues'; import { getUsersPerkTier } from '../../lib/perkTiers'; -import { formatDuration } from '../../lib/util'; +import { type CommandRunOptions, formatDuration } from '../../lib/util'; import { deferInteraction } from '../../lib/util/interactionReply'; import { Cooldowns } from '../lib/Cooldowns'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; -export const leaguesOSRSCommand: OSBMahojiCommand = { +export const bsoLeaguesCommand: OSBMahojiCommand = { name: 'leagues', description: 'Manage your Leagues progress.', options: [ diff --git a/src/mahoji/commands/light.ts b/src/mahoji/commands/light.ts index 2d3a78a47fb..b7ce2b987d1 100644 --- a/src/mahoji/commands/light.ts +++ b/src/mahoji/commands/light.ts @@ -1,15 +1,16 @@ import { stringMatches } from '@oldschoolgg/toolkit'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { Time } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; import { Bank } from 'oldschooljs'; import Firemaking from '../../lib/skilling/skills/firemaking'; import { SkillsEnum } from '../../lib/skilling/types'; -import { FiremakingActivityTaskOptions } from '../../lib/types/minions'; +import type { FiremakingActivityTaskOptions } from '../../lib/types/minions'; import { formatDuration } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const lightCommand: OSBMahojiCommand = { name: 'light', diff --git a/src/mahoji/commands/loot.ts b/src/mahoji/commands/loot.ts index c530e793ed2..4f867d3d352 100644 --- a/src/mahoji/commands/loot.ts +++ b/src/mahoji/commands/loot.ts @@ -1,10 +1,11 @@ -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { PerkTier } from '../../lib/constants'; import { getAllTrackedLootForUser, getDetailsOfSingleTrackedLoot } from '../../lib/lootTrack'; -import { prisma } from '../../lib/settings/prisma'; + import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const lootCommand: OSBMahojiCommand = { name: 'loot', diff --git a/src/mahoji/commands/lottery.ts b/src/mahoji/commands/lottery.ts index 41e8678c7cc..869ec30f251 100644 --- a/src/mahoji/commands/lottery.ts +++ b/src/mahoji/commands/lottery.ts @@ -1,13 +1,14 @@ import { userMention } from '@discordjs/builders'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { calcWhatPercent, sumArr } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; import { Bank } from 'oldschooljs'; -import { Item, ItemBank } from 'oldschooljs/dist/meta/types'; +import type { Item, ItemBank } from 'oldschooljs/dist/meta/types'; +import { mahojiUserSettingsUpdate } from '../../lib/MUser'; import { ores, secondaries, seedsFilter } from '../../lib/data/filterables'; import { Herb } from '../../lib/invention/groups/Herb'; -import { mahojiUserSettingsUpdate } from '../../lib/MUser'; -import { prisma } from '../../lib/settings/prisma'; + import Firemaking from '../../lib/skilling/skills/firemaking'; import Runecraft from '../../lib/skilling/skills/runecraft'; import { assert, isSuperUntradeable } from '../../lib/util'; @@ -17,7 +18,7 @@ import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmatio import { makeBankImage } from '../../lib/util/makeBankImage'; import { parseBank } from '../../lib/util/parseStringBank'; import { filterOption } from '../lib/mahojiCommandOptions'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; import { mahojiUsersSettingsFetch } from '../mahojiSettings'; async function addToLotteryBank(userID: string, bankToAdd: Bank) { @@ -152,7 +153,7 @@ const specialPricesBeforeMultiplying = new Bank() .add('Hellfire arrow', 30_000) .add('Mysterious seed', 5_000_000); -for (const herb of Herb.items.map(i => i.item).flat()) { +for (const herb of Herb.items.flatMap(i => i.item)) { if (!specialPricesBeforeMultiplying.has(herb.id)) { specialPricesBeforeMultiplying.add(herb.id, 10_000); } @@ -310,7 +311,7 @@ export const lotteryCommand: OSBMahojiCommand = { buy_tickets?: { quantity: number }; deposit_items?: { items?: string; filter?: string; search?: string }; }>) => { - let infoStr = ` + const infoStr = ` 1. This is a regular Lottery (no special event or DC items) 2. There'll be 4 spins, each winner winning 1/4th of the loot. 3. You can win more than once. @@ -385,7 +386,7 @@ export const lotteryCommand: OSBMahojiCommand = { return "Those items aren't worth enough, your deposit needs to be enough to get you atleast 1 ticket."; } - let perItemTickets = []; + const perItemTickets = []; for (const [item, quantity] of bankToSell .items() .sort((a, b) => getPriceOfItem(b[0]) * b[1] - getPriceOfItem(a[0]) * a[1]) diff --git a/src/mahoji/commands/m.ts b/src/mahoji/commands/m.ts index e67ca724820..aed390f57fa 100644 --- a/src/mahoji/commands/m.ts +++ b/src/mahoji/commands/m.ts @@ -1,7 +1,7 @@ -import { CommandRunOptions } from 'mahoji'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; import { minionStatusCommand } from '../lib/abstracted_commands/minionStatusCommand'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const mCommand: OSBMahojiCommand = { name: 'm', diff --git a/src/mahoji/commands/mass.ts b/src/mahoji/commands/mass.ts index e33586927fc..8439cf7d208 100644 --- a/src/mahoji/commands/mass.ts +++ b/src/mahoji/commands/mass.ts @@ -1,20 +1,21 @@ -import { TextChannel } from 'discord.js'; -import { objectKeys, Time } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import type { TextChannel } from 'discord.js'; +import { ApplicationCommandOptionType } from 'discord.js'; +import { Time, objectKeys } from 'e'; import killableMonsters from '../../lib/minions/data/killableMonsters'; import calculateMonsterFood from '../../lib/minions/functions/calculateMonsterFood'; import hasEnoughFoodForMonster from '../../lib/minions/functions/hasEnoughFoodForMonster'; import removeFoodFromUser from '../../lib/minions/functions/removeFoodFromUser'; -import { KillableMonster } from '../../lib/minions/types'; +import type { KillableMonster } from '../../lib/minions/types'; import { setupParty } from '../../lib/party'; -import { GroupMonsterActivityTaskOptions } from '../../lib/types/minions'; +import type { GroupMonsterActivityTaskOptions } from '../../lib/types/minions'; import { channelIsSendable, formatDuration } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import calcDurQty from '../../lib/util/calcMassDurationQuantity'; import findMonster from '../../lib/util/findMonster'; import { deferInteraction } from '../../lib/util/interactionReply'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; import { hasMonsterRequirements } from '../mahojiSettings'; function checkReqs(users: MUser[], monster: KillableMonster, quantity: number) { @@ -40,7 +41,7 @@ function checkReqs(users: MUser[], monster: KillableMonster, quantity: number) { if (1 > 2 && !hasEnoughFoodForMonster(monster, user, quantity, users.length)) { return `${ users.length === 1 ? "You don't" : `${user.usernameOrMention} doesn't` - } have enough food. You need at least ${monster!.healAmountNeeded! * quantity} HP in food to ${ + } have enough food. You need at least ${monster.healAmountNeeded! * quantity} HP in food to ${ users.length === 1 ? 'start the mass' : 'enter the mass' }.`; } @@ -70,7 +71,7 @@ export const massCommand: OSBMahojiCommand = { } ], run: async ({ interaction, options, userID, channelID }: CommandRunOptions<{ monster: string }>) => { - deferInteraction(interaction); + await deferInteraction(interaction); const user = await mUserFetch(userID); if (user.user.minion_ironman) return 'Ironmen cannot do masses.'; const channel = globalClient.channels.cache.get(channelID.toString()); @@ -130,7 +131,7 @@ export const massCommand: OSBMahojiCommand = { ephemeral: true }; } - let unchangedUsers = [...users]; + const unchangedUsers = [...users]; users = users.filter(i => !i.minionIsBusy); const usersKickedForBusy = unchangedUsers.filter(i => !users.includes(i)); @@ -155,10 +156,10 @@ export const massCommand: OSBMahojiCommand = { } await addSubTaskToActivityTask({ - monsterID: monster.id, + mi: monster.id, userID: user.id, channelID: channelID.toString(), - quantity, + q: quantity, duration, type: 'GroupMonsterKilling', leader: user.id, diff --git a/src/mahoji/commands/megaduck.ts b/src/mahoji/commands/megaduck.ts index 6a6076f5ed0..232504a9a69 100644 --- a/src/mahoji/commands/megaduck.ts +++ b/src/mahoji/commands/megaduck.ts @@ -1,26 +1,21 @@ -/* eslint-disable prefer-destructuring */ import { Canvas } from '@napi-rs/canvas'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { Time } from 'e'; -import { readFileSync } from 'fs'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; import { Bank } from 'oldschooljs'; import { Events } from '../../lib/constants'; -import { defaultMegaDuckLocation, MegaDuckLocation } from '../../lib/minions/types'; -import { prisma } from '../../lib/settings/prisma'; -import { getUsername } from '../../lib/util'; -import { canvasImageFromBuffer } from '../../lib/util/canvasUtil'; +import { type MegaDuckLocation, defaultMegaDuckLocation } from '../../lib/minions/types'; +import { getUsernameSync } from '../../lib/util'; +import { loadAndCacheLocalImage } from '../../lib/util/canvasUtil'; import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; import { mahojiGuildSettingsUpdate } from '../guildSettings'; -import { OSBMahojiCommand, resetCooldown } from '../lib/util'; +import { type OSBMahojiCommand, resetCooldown } from '../lib/util'; -const _mapImage = readFileSync('./src/lib/resources/images/megaduckmap.png'); -const noMoveImageBuf = readFileSync('./src/lib/resources/images/megaducknomovemap.png'); - -let apeAtoll = [1059, 1226]; -let portSarim = [1418, 422]; -let karamja = [1293, 554]; -let flyer = [1358, 728]; +const apeAtoll = [1059, 1226]; +const portSarim = [1418, 422]; +const karamja = [1293, 554]; +const flyer = [1358, 728]; const teleportationLocations = [ [ { name: 'Port Sarim', coords: portSarim }, @@ -40,7 +35,7 @@ function topFeeders(entries: any[]) { return `Top 10 Feeders: ${[...entries] .sort((a, b) => b[1] - a[1]) .slice(0, 10) - .map(ent => `${getUsername(ent[0])}. ${ent[1]}`) + .map(ent => `${getUsernameSync(ent[0])}. ${ent[1]}`) .join(', ')}`; } @@ -48,7 +43,7 @@ const directions = ['up', 'down', 'left', 'right'] as const; type MegaduckDirection = (typeof directions)[number]; function applyDirection(location: MegaDuckLocation, direction: MegaduckDirection): MegaDuckLocation { - let newLocation = { ...location }; + const newLocation = { ...location }; switch (direction) { case 'up': newLocation.y--; @@ -67,16 +62,14 @@ function applyDirection(location: MegaDuckLocation, direction: MegaduckDirection } function getPixel(x: number, y: number, data: any, width: number) { - let i = (width * Math.round(y) + Math.round(x)) * 4; + const i = (width * Math.round(y) + Math.round(x)) * 4; return [data[i], data[i + 1], data[i + 2], data[i + 3]]; } -const _noMoveImage = canvasImageFromBuffer(noMoveImageBuf); - async function makeImage(location: MegaDuckLocation) { const { x, y, steps = [] } = location; - const mapImage = await canvasImageFromBuffer(_mapImage); - const noMoveImage = await _noMoveImage; + const mapImage = await loadAndCacheLocalImage('./src/lib/resources/images/megaduckmap.png'); + const noMoveImage = await loadAndCacheLocalImage('./src/lib/resources/images/megaducknomovemap.png'); const scale = 3; const canvasSize = 250; @@ -109,8 +102,8 @@ async function makeImage(location: MegaDuckLocation) { ctx.fillStyle = 'rgba(0,0,255,0.05)'; for (const [_xS, _yS] of steps) { - let xS = _xS - x + centerPosition; - let yS = _yS - y + centerPosition; + const xS = _xS - x + centerPosition; + const yS = _yS - y + centerPosition; ctx.fillRect(xS, yS, 1, 1); } diff --git a/src/mahoji/commands/mine.ts b/src/mahoji/commands/mine.ts index 4cfcf15aa16..58b8ba4f003 100644 --- a/src/mahoji/commands/mine.ts +++ b/src/mahoji/commands/mine.ts @@ -1,19 +1,21 @@ -import { stringMatches } from '@oldschoolgg/toolkit'; +import { formatDuration, stringMatches } from '@oldschoolgg/toolkit'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { increaseNumByPercent, reduceNumByPercent } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import { itemID, randomVariation } from 'oldschooljs/dist/util'; -import { UserFullGearSetup } from '../../lib/gear'; +import type { UserFullGearSetup } from '../../lib/gear'; import { determineMiningTime } from '../../lib/skilling/functions/determineMiningTime'; import { miningCapeOreEffect, miningGloves, pickaxes, varrockArmours } from '../../lib/skilling/functions/miningBoosts'; import { sinsOfTheFatherSkillRequirements } from '../../lib/skilling/functions/questRequirements'; import Mining from '../../lib/skilling/skills/mining'; -import { MiningActivityTaskOptions } from '../../lib/types/minions'; -import { formatDuration, formatSkillRequirements, itemNameFromID, randomVariation } from '../../lib/util'; +import type { MiningActivityTaskOptions } from '../../lib/types/minions'; +import { formatSkillRequirements, itemNameFromID } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; import { minionName } from '../../lib/util/minionUtils'; import { motherlodeMineCommand } from '../lib/abstracted_commands/motherlodeMineCommand'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export function calculateMiningInput({ nameInput, @@ -25,7 +27,8 @@ export function calculateMiningInput({ miningLevel, craftingLevel, strengthLevel, - maxTripLength + maxTripLength, + user }: { nameInput: string; quantityInput: number | undefined; @@ -37,6 +40,7 @@ export function calculateMiningInput({ craftingLevel: number; strengthLevel: number; maxTripLength: number; + user: MUser; }) { const ore = Mining.Ores.find( ore => @@ -105,9 +109,9 @@ export function calculateMiningInput({ let glovesRate = 0; if (miningLevel >= 60) { for (const glove of miningGloves) { - if (!gearValues.some(g => g.hasEquipped(glove.id)) || !glove.Percentages.has(ore.id)) continue; - glovesRate = glove.Percentages.amount(ore.id); - if (glovesRate !== 0) { + if (!user.hasEquipped(glove.id) || !glove.Percentages[ore.name]) continue; + glovesRate = glove.Percentages[ore.name]; + if (glovesRate) { messages.push(`Lowered rock depletion rate by **${glovesRate}%** for ${itemNameFromID(glove.id)}`); break; } @@ -116,9 +120,9 @@ export function calculateMiningInput({ let armourEffect = 0; for (const armour of varrockArmours) { - if (!gearValues.some(g => g.hasEquipped(armour.id)) || !armour.Percentages.has(ore.id)) continue; - armourEffect = armour.Percentages.amount(ore.id); - if (armourEffect !== 0) { + if (!user.hasEquippedOrInBank(armour.id) || !armour.Percentages[ore.name]) continue; + armourEffect = armour.Percentages[ore.name]; + if (armourEffect) { messages.push(`**${armourEffect}%** chance to mine an extra ore using ${itemNameFromID(armour.id)}`); break; } @@ -131,9 +135,9 @@ export function calculateMiningInput({ } let miningCapeEffect = 0; - if (gearValues.some(g => g.hasEquipped('Mining cape')) && miningCapeOreEffect.has(ore.id)) { - miningCapeEffect = miningCapeOreEffect.amount(ore.id); - if (miningCapeEffect !== 0) { + if (user.hasEquippedOrInBank([itemID('Mining cape')]) && miningCapeOreEffect[ore.name]) { + miningCapeEffect = miningCapeOreEffect[ore.name]; + if (miningCapeEffect) { messages.push(`**${miningCapeEffect}%** chance to mine an extra ore using Mining cape`); } } @@ -230,11 +234,11 @@ export const mineCommand: OSBMahojiCommand = { channelID }: CommandRunOptions<{ name: string; quantity?: number; powermine?: boolean }>) => { const user = await mUserFetch(userID); - let { quantity, powermine } = options; + const { quantity, powermine } = options; const motherlodeMine = stringMatches(Mining.MotherlodeMine.name, options.name) || - Mining.MotherlodeMine.aliases!.some(a => stringMatches(a, options.name)); + Mining.MotherlodeMine.aliases?.some(a => stringMatches(a, options.name)); if (motherlodeMine) { return motherlodeMineCommand({ user, channelID, quantity }); @@ -250,7 +254,8 @@ export const mineCommand: OSBMahojiCommand = { miningLevel: user.skillLevel('mining'), craftingLevel: user.skillLevel('crafting'), strengthLevel: user.skillLevel('strength'), - maxTripLength: calcMaxTripLength(user, 'Mining') + maxTripLength: calcMaxTripLength(user, 'Mining'), + user }); if (typeof result === 'string') { diff --git a/src/mahoji/commands/minigames.ts b/src/mahoji/commands/minigames.ts index 8abf2732bbd..316279fe209 100644 --- a/src/mahoji/commands/minigames.ts +++ b/src/mahoji/commands/minigames.ts @@ -1,22 +1,23 @@ -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; -import TrekShopItems from '../../lib/data/buyables/trekBuyables'; import { LMSBuyables } from '../../lib/data/CollectionsExport'; +import TrekShopItems from '../../lib/data/buyables/trekBuyables'; import { - agilityArenaBuyables, agilityArenaBuyCommand, + agilityArenaBuyables, agilityArenaCommand, agilityArenaRecolorCommand, agilityArenaXPCommand } from '../lib/abstracted_commands/agilityArenaCommand'; import { + BarbBuyables, + GambleTiers, barbAssaultBuyCommand, barbAssaultGambleCommand, barbAssaultLevelCommand, barbAssaultStartCommand, - barbAssaultStatsCommand, - BarbBuyables, - GambleTiers + barbAssaultStatsCommand } from '../lib/abstracted_commands/barbAssault'; import { castleWarsStartCommand, castleWarsStatsCommand } from '../lib/abstracted_commands/castleWarsCommand'; import { fishingTrawlerCommand } from '../lib/abstracted_commands/fishingTrawler'; @@ -32,16 +33,16 @@ import { lmsCommand } from '../lib/abstracted_commands/lmsCommand'; import { mageArena2Command } from '../lib/abstracted_commands/mageArena2Command'; import { mageArenaCommand } from '../lib/abstracted_commands/mageArenaCommand'; import { - mageTrainingArenaBuyables, mageTrainingArenaBuyCommand, + mageTrainingArenaBuyables, mageTrainingArenaPointsCommand, mageTrainingArenaStartCommand } from '../lib/abstracted_commands/mageTrainingArenaCommand'; import { contractTiers, mahoganyHomesBuildCommand, - mahoganyHomesBuyables, - mahoganyHomesBuyCommand + mahoganyHomesBuyCommand, + mahoganyHomesBuyables } from '../lib/abstracted_commands/mahoganyHomesCommand'; import { nightmareZoneShopCommand, @@ -49,8 +50,8 @@ import { nightmareZoneStatsCommand } from '../lib/abstracted_commands/nightmareZoneCommand'; import { - pestControlBuyables, pestControlBuyCommand, + pestControlBuyables, pestControlStartCommand, pestControlStatsCommand, pestControlXPCommand @@ -60,10 +61,10 @@ import { roguesDenCommand } from '../lib/abstracted_commands/roguesDenCommand'; import { sepulchreCommand } from '../lib/abstracted_commands/sepulchreCommand'; import { shades, shadesLogs, shadesOfMortonStartCommand } from '../lib/abstracted_commands/shadesOfMortonCommand'; import { - soulWarsBuyables, soulWarsBuyCommand, - soulWarsImbueables, + soulWarsBuyables, soulWarsImbueCommand, + soulWarsImbueables, soulWarsStartCommand, soulWarsTokensCommand } from '../lib/abstracted_commands/soulWarsCommand'; @@ -71,18 +72,19 @@ import { tearsOfGuthixCommand } from '../lib/abstracted_commands/tearsOfGuthixCo import { trekCommand, trekShop } from '../lib/abstracted_commands/trekCommand'; import { troubleBrewingStartCommand } from '../lib/abstracted_commands/troubleBrewingCommand'; import { - volcanicMineCommand, VolcanicMineShop, + volcanicMineCommand, volcanicMineShopCommand, volcanicMineStatsCommand } from '../lib/abstracted_commands/volcanicMineCommand'; -import { OSBMahojiCommand } from '../lib/util'; -import { NMZ_STRATEGY, NMZStrategy } from './../../lib/constants'; +import type { OSBMahojiCommand } from '../lib/util'; +import type { NMZStrategy } from './../../lib/constants'; +import { NMZ_STRATEGY } from './../../lib/constants'; import { giantsFoundryAlloys, giantsFoundryBuyables } from './../lib/abstracted_commands/giantsFoundryCommand'; import { nightmareZoneBuyables, - nightmareZoneImbueables, - nightmareZoneImbueCommand + nightmareZoneImbueCommand, + nightmareZoneImbueables } from './../lib/abstracted_commands/nightmareZoneCommand'; export const minigamesCommand: OSBMahojiCommand = { @@ -1219,11 +1221,11 @@ export const minigamesCommand: OSBMahojiCommand = { */ if (options.temple_trek) { if (options.temple_trek.buy) { - let { reward, difficulty, quantity } = options.temple_trek.buy!; + const { reward, difficulty, quantity } = options.temple_trek.buy!; return trekShop(user, reward, difficulty, quantity, interaction); } if (options.temple_trek.start) { - let { difficulty, quantity } = options.temple_trek.start!; + const { difficulty, quantity } = options.temple_trek.start!; return trekCommand(user, channelID, difficulty, quantity); } } diff --git a/src/mahoji/commands/minion.ts b/src/mahoji/commands/minion.ts index 9e4733a2bb6..af204717856 100644 --- a/src/mahoji/commands/minion.ts +++ b/src/mahoji/commands/minion.ts @@ -1,35 +1,37 @@ import { formatOrdinal, roboChimpCLRankQuery } from '@oldschoolgg/toolkit'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import type { MahojiUserOption } from '@oldschoolgg/toolkit'; import { bold } from 'discord.js'; +import { ApplicationCommandOptionType } from 'discord.js'; import { notEmpty, randArrItem } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; -import { MahojiUserOption } from 'mahoji/dist/lib/types'; import { BLACKLISTED_USERS } from '../../lib/blacklists'; import { - badges, BitField, BitFieldData, FormattedCustomEmoji, MAX_LEVEL, - minionActivityCache, - PerkTier + PerkTier, + minionActivityCache } from '../../lib/constants'; import { degradeableItems } from '../../lib/degradeableItems'; import { diaries } from '../../lib/diaries'; import { calculateMastery } from '../../lib/mastery'; import { effectiveMonsters } from '../../lib/minions/data/killableMonsters'; -import { AttackStyles } from '../../lib/minions/functions'; +import type { AttackStyles } from '../../lib/minions/functions'; import { blowpipeCommand, blowpipeDarts } from '../../lib/minions/functions/blowpipeCommand'; import { degradeableItemsCommand } from '../../lib/minions/functions/degradeableItemsCommand'; import { allPossibleStyles, trainCommand } from '../../lib/minions/functions/trainCommand'; +import { roboChimpCache } from '../../lib/perkTier'; import { roboChimpUserFetch } from '../../lib/roboChimp'; import { Minigames } from '../../lib/settings/minigames'; import Skills from '../../lib/skilling/skills'; import creatures from '../../lib/skilling/skills/hunter/creatures'; import { MUserStats } from '../../lib/structures/MUserStats'; -import { convertLVLtoXP, getAllIDsOfUser, getUsername, isValidNickname } from '../../lib/util'; +import { convertLVLtoXP, isValidNickname } from '../../lib/util'; +import { findGroupOfUser } from '../../lib/util/findGroupOfUser'; import { getKCByName } from '../../lib/util/getKCByName'; -import getOSItem from '../../lib/util/getOSItem'; +import getOSItem, { getItem } from '../../lib/util/getOSItem'; import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; import { minionStatsEmbed } from '../../lib/util/minionStatsEmbed'; import { checkPeakTimes } from '../../lib/util/minionUtils'; @@ -47,7 +49,7 @@ import { Lampables, lampCommand } from '../lib/abstracted_commands/lampCommand'; import { minionBuyCommand } from '../lib/abstracted_commands/minionBuyCommand'; import { minionStatusCommand } from '../lib/abstracted_commands/minionStatusCommand'; import { ownedItemOption, skillOption } from '../lib/mahojiCommandOptions'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; import { patronMsg } from '../mahojiSettings'; const patMessages = [ @@ -82,23 +84,10 @@ export async function getUserInfo(user: MUser) { const task = minionActivityCache.get(user.id); const taskText = task ? `${task.type}` : 'None'; - const userBadges = user.user.badges.map(i => badges[i]); - - const premiumDate = Number(user.user.premium_balance_expiry_date); - const premiumTier = user.user.premium_balance_tier; - const result = { perkTier: user.perkTier(), isBlacklisted: BLACKLISTED_USERS.has(user.id), - badges: userBadges, - mainAccount: - user.user.main_account !== null - ? `${getUsername(user.user.main_account)}[${user.user.main_account}]` - : 'None', - ironmanAlts: user.user.ironman_alts.map(id => `${getUsername(id)}[${id}]`), - premiumBalance: `${premiumDate ? new Date(premiumDate).toLocaleString() : ''} ${ - premiumTier ? `Tier ${premiumTier}` : '' - }`, + badges: user.badgesString, isIronman: user.isIronman, bitfields, currentTask: taskText, @@ -110,16 +99,14 @@ export async function getUserInfo(user: MUser) { 2 ); + const roboCache = roboChimpCache.get(user.id); return { ...result, everythingString: `${user.badgedUsername}[${user.id}] **Current Trip:** ${taskText} -**Perk Tier:** ${result.perkTier} +**Perk Tier:** ${roboCache?.perk_tier ?? 'None'} **Blacklisted:** ${result.isBlacklisted} -**Badges:** ${result.badges.join(' ')} -**Main Account:** ${result.mainAccount} -**Ironman Alts:** ${result.ironmanAlts} -**Patron Balance:** ${result.premiumBalance} +**Badges:** ${result.badges} **Ironman:** ${result.isIronman} **Bitfields:** ${result.bitfields} **Patreon Connected:** ${result.patreon} @@ -205,7 +192,7 @@ export const minionCommand: OSBMahojiCommand = { const mUser = await mUserFetch(user.id); const isMod = mUser.bitfield.includes(BitField.isModerator); const bankImages = bankImageGenerator.backgroundImages; - const allAccounts = getAllIDsOfUser(mUser); + const allAccounts = await findGroupOfUser(mUser.id); const owned = bankImages .filter( bg => @@ -238,7 +225,8 @@ export const minionCommand: OSBMahojiCommand = { autocomplete: async (value, user) => { const mappedLampables = Lampables.map(i => i.items) .flat(2) - .map(getOSItem) + .map(getItem) + .filter(notEmpty) .map(i => ({ id: i.id, name: i.name })); const botUser = await mUserFetch(user.id); diff --git a/src/mahoji/commands/mix.ts b/src/mahoji/commands/mix.ts index 34d170eae3f..b687d0602f0 100644 --- a/src/mahoji/commands/mix.ts +++ b/src/mahoji/commands/mix.ts @@ -1,17 +1,17 @@ -import { stringMatches } from '@oldschoolgg/toolkit'; -import { clamp, reduceNumByPercent, Time } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import { type CommandRunOptions, stringMatches } from '@oldschoolgg/toolkit'; import { Bank } from 'oldschooljs'; -import { inventionBoosts, InventionID, inventionItemBoost } from '../../lib/invention/inventions'; +import { ApplicationCommandOptionType } from 'discord.js'; +import { Time, clamp, reduceNumByPercent } from 'e'; +import { InventionID, inventionBoosts, inventionItemBoost } from '../../lib/invention/inventions'; import Herblore from '../../lib/skilling/skills/herblore/herblore'; import { SkillsEnum } from '../../lib/skilling/types'; -import { HerbloreActivityTaskOptions } from '../../lib/types/minions'; +import type { HerbloreActivityTaskOptions } from '../../lib/types/minions'; import { formatDuration } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; import { updateBankSetting } from '../../lib/util/updateBankSetting'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const mixCommand: OSBMahojiCommand = { name: 'mix', @@ -72,7 +72,7 @@ export const mixCommand: OSBMahojiCommand = { } if (mixableItem.qpRequired && user.QP < mixableItem.qpRequired) { - return `You need atleast **${mixableItem.qpRequired}** QP to make ${mixableItem.item.name}.`; + return `You need at least **${mixableItem.qpRequired}** QP to make ${mixableItem.item.name}.`; } const requiredItems = new Bank(mixableItem.inputItems); diff --git a/src/mahoji/commands/nursery.ts b/src/mahoji/commands/nursery.ts index 39b9fba2637..7aa478be66d 100644 --- a/src/mahoji/commands/nursery.ts +++ b/src/mahoji/commands/nursery.ts @@ -1,19 +1,20 @@ +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; import { tame_growth } from '@prisma/client'; -import { ChatInputCommandInteraction } from 'discord.js'; +import type { ChatInputCommandInteraction } from 'discord.js'; +import { ApplicationCommandOptionType } from 'discord.js'; import { randArrItem, reduceNumByPercent } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; import { Bank } from 'oldschooljs'; import { production } from '../../config'; import { Events } from '../../lib/constants'; -import { prisma } from '../../lib/settings/prisma'; + import { SkillsEnum } from '../../lib/skilling/types'; -import { Nursery, Species, tameSpecies, TameSpeciesID } from '../../lib/tames'; +import { type Nursery, type Species, TameSpeciesID, tameSpecies } from '../../lib/tames'; import { formatDuration, gaussianRandom, roll } from '../../lib/util'; import { getItem } from '../../lib/util/getOSItem'; import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; import { updateBankSetting } from '../../lib/util/updateBankSetting'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; function makeTameNickname(species: Species) { switch (species.id) { @@ -36,7 +37,7 @@ function makeTameNickname(species: Species) { } export async function generateNewTame(user: MUser, species: Species) { - let shinyChance = user.hasEquippedOrInBank(['Ring of luck']) + const shinyChance = user.hasEquippedOrInBank(['Ring of luck']) ? Math.floor(reduceNumByPercent(species.shinyChance, 3)) : species.shinyChance; @@ -83,8 +84,8 @@ async function view(user: MUser) { const specie = tameSpecies.find(i => i.id === egg.species)!; let diff = Date.now() - egg.insertedAt; - let constructionMaster = user.hasEquippedOrInBank('Construction master cape'); - let masterString = constructionMaster + const constructionMaster = user.hasEquippedOrInBank('Construction master cape'); + const masterString = constructionMaster ? '\n\nYour minion has constructed a very high quality nursery that hatches eggs twice as fast.' : ''; if (constructionMaster) { @@ -165,7 +166,7 @@ async function buildCommand(user: MUser) { await user.update({ nursery: newNursery }); - let constructionMaster = user.hasEquippedOrInBank('Construction master cape'); + const constructionMaster = user.hasEquippedOrInBank('Construction master cape'); return `You built a nursery! Removed ${cost} from your bank.${ constructionMaster ? '\n\nYour minion has constructed a very high quality nursery that hatches eggs twice as fast.' diff --git a/src/mahoji/commands/offer.ts b/src/mahoji/commands/offer.ts index c64172ef905..a4c2f7de3ce 100644 --- a/src/mahoji/commands/offer.ts +++ b/src/mahoji/commands/offer.ts @@ -1,8 +1,11 @@ import { formatOrdinal, stringMatches } from '@oldschoolgg/toolkit'; -import { User } from 'discord.js'; -import { randArrItem, randInt, roll, Time } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { formatDuration } from '@oldschoolgg/toolkit'; +import type { User } from 'discord.js'; +import { ApplicationCommandOptionType } from 'discord.js'; +import { Time, randArrItem, randInt, roll } from 'e'; import { Bank } from 'oldschooljs'; +import { resolveItems } from 'oldschooljs/dist/util/util'; import { Events } from '../../lib/constants'; import { evilChickenOutfit } from '../../lib/data/CollectionsExport'; @@ -10,15 +13,13 @@ import { Offerables } from '../../lib/data/offerData'; import { birdsNestID, treeSeedsNest } from '../../lib/simulation/birdsNest'; import Prayer from '../../lib/skilling/skills/prayer'; import { SkillsEnum } from '../../lib/skilling/types'; -import { OfferingActivityTaskOptions } from '../../lib/types/minions'; -import { formatDuration } from '../../lib/util'; +import type { OfferingActivityTaskOptions } from '../../lib/types/minions'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; import getOSItem from '../../lib/util/getOSItem'; import { deferInteraction } from '../../lib/util/interactionReply'; import { makeBankImage } from '../../lib/util/makeBankImage'; -import resolveItems from '../../lib/util/resolveItems'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; import { userStatsBankUpdate, userStatsUpdate } from '../mahojiSettings'; const specialBones = [ @@ -54,7 +55,7 @@ function notifyUniques(user: MUser, activity: string, uniques: number[], loot: B } } -export const mineCommand: OSBMahojiCommand = { +export const offerCommand: OSBMahojiCommand = { name: 'offer', description: 'Offer bones or bird eggs.', attributes: { @@ -103,7 +104,7 @@ export const mineCommand: OSBMahojiCommand = { const whichOfferable = Offerables.find( item => stringMatches(options.name, item.name) || - (item.aliases && item.aliases.some(alias => stringMatches(alias, options.name))) + item.aliases?.some(alias => stringMatches(alias, options.name)) ); if (whichOfferable) { const offerableOwned = userBank.amount(whichOfferable.itemID); @@ -114,7 +115,7 @@ export const mineCommand: OSBMahojiCommand = { if (quantity > offerableOwned) { return `You don't have ${quantity} ${whichOfferable.name} to offer the ${whichOfferable.offerWhere}. You have ${offerableOwned}.`; } - let loot = new Bank().add(whichOfferable.table.roll(quantity)); + const loot = new Bank().add(whichOfferable.table.roll(quantity)); const { previousCL, itemsAdded } = await user.transactItems({ collectionLog: true, @@ -132,7 +133,7 @@ export const mineCommand: OSBMahojiCommand = { { slayer_chewed_offered: true, slayer_unsired_offered: true } ); // Notify uniques if (whichOfferable.uniques) { - let current = newStats[whichOfferable.economyCounter]; + const current = newStats[whichOfferable.economyCounter]; notifyUniques( user, whichOfferable.name, @@ -166,7 +167,7 @@ export const mineCommand: OSBMahojiCommand = { const cost = new Bank().add(egg.id, quantity); if (!user.owns(cost)) return "You don't own enough of these eggs."; - let loot = new Bank(); + const loot = new Bank(); for (let i = 0; i < quantity; i++) { if (roll(300)) { loot.add(randArrItem(evilChickenOutfit)); @@ -186,7 +187,7 @@ export const mineCommand: OSBMahojiCommand = { itemsToAdd: loot, itemsToRemove: cost }); - await userStatsBankUpdate(user.id, 'bird_eggs_offered_bank', cost); + await userStatsBankUpdate(user, 'bird_eggs_offered_bank', cost); notifyUniques(user, egg.name, evilChickenOutfit, loot, quantity); @@ -207,10 +208,10 @@ export const mineCommand: OSBMahojiCommand = { const specialBone = specialBones.find(bone => stringMatches(bone.item.name, options.name)); if (specialBone) { if (user.QP < 8) { - return 'You need atleast 8 QP to offer long/curved bones for XP.'; + return 'You need at least 8 QP to offer long/curved bones for XP.'; } if (user.skillLevel(SkillsEnum.Construction) < 30) { - return 'You need atleast level 30 Construction to offer long/curved bones for XP.'; + return 'You need at least level 30 Construction to offer long/curved bones for XP.'; } const amountHas = userBank.amount(specialBone.item.id); if (!quantity) quantity = Math.max(amountHas, 1); diff --git a/src/mahoji/commands/open.ts b/src/mahoji/commands/open.ts index 7cf4181433a..eb221230f52 100644 --- a/src/mahoji/commands/open.ts +++ b/src/mahoji/commands/open.ts @@ -1,15 +1,15 @@ -import { truncateString } from '@oldschoolgg/toolkit'; -import { clamp } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import { type CommandRunOptions, truncateString } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; +import { clamp } from 'e'; import { allOpenables, allOpenablesIDs } from '../../lib/openables'; import { deferInteraction } from '../../lib/util/interactionReply'; import { + OpenUntilItems, abstractedOpenCommand, - abstractedOpenUntilCommand, - OpenUntilItems + abstractedOpenUntilCommand } from '../lib/abstracted_commands/openCommand'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const openCommand: OSBMahojiCommand = { name: 'open', diff --git a/src/mahoji/commands/paint.ts b/src/mahoji/commands/paint.ts index c9c858d638f..b24e994e33d 100644 --- a/src/mahoji/commands/paint.ts +++ b/src/mahoji/commands/paint.ts @@ -1,4 +1,5 @@ -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { Bank } from 'oldschooljs'; import { getPaintedItemImage, paintColors } from '../../lib/paintColors'; @@ -6,7 +7,7 @@ import { itemEffectImageCache } from '../../lib/util/customItemEffects'; import { getItem } from '../../lib/util/getOSItem'; import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; import { ownedItemOption } from '../lib/mahojiCommandOptions'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const paintCommand: OSBMahojiCommand = { name: 'paint', diff --git a/src/mahoji/commands/patreon.ts b/src/mahoji/commands/patreon.ts index 31efc1c443b..686f93245d9 100644 --- a/src/mahoji/commands/patreon.ts +++ b/src/mahoji/commands/patreon.ts @@ -1,5 +1,5 @@ import { Emoji } from '../../lib/constants'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const patreonCommand: OSBMahojiCommand = { name: 'patreon', diff --git a/src/mahoji/commands/pay.ts b/src/mahoji/commands/pay.ts index 3a96f9411d2..fd172a228df 100644 --- a/src/mahoji/commands/pay.ts +++ b/src/mahoji/commands/pay.ts @@ -1,15 +1,16 @@ -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; -import { MahojiUserOption } from 'mahoji/dist/lib/types'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import type { MahojiUserOption } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { Bank } from 'oldschooljs'; import { BLACKLISTED_USERS } from '../../lib/blacklists'; import { Events } from '../../lib/constants'; -import { prisma } from '../../lib/settings/prisma'; + import { toKMB } from '../../lib/util'; import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; import { deferInteraction } from '../../lib/util/interactionReply'; import { tradePlayerItems } from '../../lib/util/tradePlayerItems'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; import { addToGPTaxBalance, mahojiParseNumber } from '../mahojiSettings'; export const payCommand: OSBMahojiCommand = { diff --git a/src/mahoji/commands/poh.ts b/src/mahoji/commands/poh.ts index da89a6d96a4..306a89b093b 100644 --- a/src/mahoji/commands/poh.ts +++ b/src/mahoji/commands/poh.ts @@ -1,5 +1,6 @@ -import { User } from 'discord.js'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import type { User } from 'discord.js'; +import { ApplicationCommandOptionType } from 'discord.js'; import { PoHObjects } from '../../lib/poh'; import { minionIsBusy } from '../../lib/util/minionIsBusy'; @@ -14,7 +15,7 @@ import { pohWallkits } from '../lib/abstracted_commands/pohCommand'; import { ownedItemOption } from '../lib/mahojiCommandOptions'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const pohCommand: OSBMahojiCommand = { name: 'poh', diff --git a/src/mahoji/commands/poll.ts b/src/mahoji/commands/poll.ts index 8fa5cee9c95..da58c55896f 100644 --- a/src/mahoji/commands/poll.ts +++ b/src/mahoji/commands/poll.ts @@ -1,8 +1,9 @@ -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { channelIsSendable } from '../../lib/util'; import { deferInteraction } from '../../lib/util/interactionReply'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const pollCommand: OSBMahojiCommand = { name: 'poll', diff --git a/src/mahoji/commands/price.ts b/src/mahoji/commands/price.ts index 90f976f8e83..847142a249c 100644 --- a/src/mahoji/commands/price.ts +++ b/src/mahoji/commands/price.ts @@ -1,11 +1,10 @@ +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; import { EmbedBuilder } from 'discord.js'; -import { CommandRunOptions } from 'mahoji'; import { toKMB } from 'oldschooljs/dist/util'; -import { secretItems } from '../../lib/constants'; import { getItem } from '../../lib/util/getOSItem'; import { itemOption } from '../lib/mahojiCommandOptions'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; import { sellPriceOfItem } from './sell'; export const priceCommand: OSBMahojiCommand = { @@ -20,7 +19,7 @@ export const priceCommand: OSBMahojiCommand = { ], run: async ({ options }: CommandRunOptions<{ item: string }>) => { const item = getItem(options.item); - if (!item || secretItems.includes(item.id)) return "Couldn't find that item."; + if (!item || item.customItemData?.isSecret) return "Couldn't find that item."; const { basePrice: priceOfItem } = sellPriceOfItem(item); diff --git a/src/mahoji/commands/raid.ts b/src/mahoji/commands/raid.ts index 97ac79700b1..a1b72e02b14 100644 --- a/src/mahoji/commands/raid.ts +++ b/src/mahoji/commands/raid.ts @@ -1,30 +1,31 @@ import { randArrItem, reduceNumByPercent, roll, sumArr } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; import { Bank } from 'oldschooljs'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { production } from '../../config'; +import { DOANonUniqueTable } from '../../lib/bso/doa/doaLootTable'; +import { doaStartCommand } from '../../lib/bso/doa/doaStartCommand'; import { doaMetamorphPets } from '../../lib/data/CollectionsExport'; import { globalDroprates } from '../../lib/data/globalDroprates'; import { degradeableItems } from '../../lib/degradeableItems'; import { + DOARooms, calcDOAInput, chanceOfDOAUnique, createDOATeam, doaHelpCommand, - DOARooms, - doaStartCommand, pickUniqueToGiveUser } from '../../lib/depthsOfAtlantis'; -import { mileStoneBaseDeathChances, RaidLevel, toaHelpCommand, toaStartCommand } from '../../lib/simulation/toa'; +import { type RaidLevel, mileStoneBaseDeathChances, toaHelpCommand, toaStartCommand } from '../../lib/simulation/toa'; import { averageBank, formatDuration } from '../../lib/util'; import { deferInteraction } from '../../lib/util/interactionReply'; import { makeBankImage } from '../../lib/util/makeBankImage'; import { minionIsBusy } from '../../lib/util/minionIsBusy'; import resolveItems from '../../lib/util/resolveItems'; -import { DOANonUniqueTable } from '../../tasks/minions/bso/doaActivity'; import { coxCommand, coxStatsCommand } from '../lib/abstracted_commands/coxCommand'; import { tobCheckCommand, tobStartCommand, tobStatsCommand } from '../lib/abstracted_commands/tobCommand'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const raidCommand: OSBMahojiCommand = { name: 'raid', @@ -152,7 +153,10 @@ export const raidCommand: OSBMahojiCommand = { name: 'raid_level', description: 'Choose the raid level you want to do (1-600).', required: true, - choices: mileStoneBaseDeathChances.map(i => ({ name: i.level.toString(), value: i.level })) + choices: mileStoneBaseDeathChances.map(i => ({ + name: i.level.toString(), + value: i.level + })) }, { type: ApplicationCommandOptionType.Boolean, @@ -254,7 +258,7 @@ export const raidCommand: OSBMahojiCommand = { } ] } as any - ]) + ]) ] } ], @@ -317,7 +321,7 @@ export const raidCommand: OSBMahojiCommand = { for (let t = 0; t < samples; t++) { let i = 0; const totalLoot = new Bank(); - let items = resolveItems([ + const items = resolveItems([ 'Shark jaw', 'Shark tooth', 'Oceanic relic', @@ -340,7 +344,7 @@ export const raidCommand: OSBMahojiCommand = { while (items.some(item => !totalLoot.has(item))) { i++; - let loot = new Bank(); + const loot = new Bank(); if (roll(uniqueChance)) { loot.add(pickUniqueToGiveUser(totalLoot)); } else { @@ -365,7 +369,7 @@ export const raidCommand: OSBMahojiCommand = { kcBank.add(room.id, i); } - let team = []; + const team = []; for (let a = 0; a < teamSize; a++) { team.push({ user, kc: a, attempts: a, roomKCs: kcBank.bank as any }); } @@ -449,7 +453,7 @@ Slowest finish: ${formatDuration(slowest.time)} if (minionIsBusy(user.id)) return "Your minion is busy, you can't do this."; - if (cox && cox.start) { + if (cox?.start) { return coxCommand( channelID, user, diff --git a/src/mahoji/commands/rates.ts b/src/mahoji/commands/rates.ts index a9ccf0a02c9..0ed69d6cf77 100644 --- a/src/mahoji/commands/rates.ts +++ b/src/mahoji/commands/rates.ts @@ -1,20 +1,21 @@ import { bold } from '@discordjs/builders'; -import { InteractionReplyOptions } from 'discord.js'; -import { increaseNumByPercent, reduceNumByPercent, sumArr, Time } from 'e'; +import { type CommandRunOptions, calcPerHour, convertBankToPerHourStats, formatDuration } from '@oldschoolgg/toolkit'; +import type { InteractionReplyOptions } from 'discord.js'; +import { ApplicationCommandOptionType } from 'discord.js'; +import { Time, increaseNumByPercent, reduceNumByPercent, sumArr } from 'e'; import { uniq } from 'lodash'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; import { Bank } from 'oldschooljs'; -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; import { BathhouseOres, - bathHouseTiers, BathwaterMixtures, + bathHouseTiers, calculateBathouseResult, durationPerBaxBath } from '../../lib/baxtorianBathhouses'; import { calcAtomicEnergy, divinationEnergies, memoryHarvestTypes } from '../../lib/bso/divination'; -import { calculateTuraelsTrialsInput, TuraelsTrialsMethods } from '../../lib/bso/turaelsTrials'; +import { TuraelsTrialsMethods, calculateTuraelsTrialsInput } from '../../lib/bso/turaelsTrials'; import { ClueTiers } from '../../lib/clues/clueTiers'; import { GLOBAL_BSO_XP_MULTIPLIER, PeakTier } from '../../lib/constants'; import { Eatables } from '../../lib/data/eatables'; @@ -38,20 +39,20 @@ import Mining from '../../lib/skilling/skills/mining'; import Smithing from '../../lib/skilling/skills/smithing'; import { HunterTechniqueEnum } from '../../lib/skilling/types'; import { Gear } from '../../lib/structures/Gear'; -import { BathhouseTaskOptions } from '../../lib/types/minions'; -import { convertBankToPerHourStats, stringMatches, toKMB } from '../../lib/util'; +import type { BathhouseTaskOptions } from '../../lib/types/minions'; +import { stringMatches, toKMB } from '../../lib/util'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; import { deferInteraction } from '../../lib/util/interactionReply'; import itemID from '../../lib/util/itemID'; -import { calcPerHour, formatDuration, itemNameFromID, returnStringOrFile } from '../../lib/util/smallUtils'; +import { itemNameFromID, returnStringOrFile } from '../../lib/util/smallUtils'; +import { calculateHunterResult } from '../../tasks/minions/HunterActivity/hunterActivity'; import { calculateAgilityResult } from '../../tasks/minions/agilityActivity'; import { calculateDungeoneeringResult } from '../../tasks/minions/bso/dungeoneeringActivity'; import { memoryHarvestResult, totalTimePerRound } from '../../tasks/minions/bso/memoryHarvestActivity'; import { calculateTuraelsTrialsResult } from '../../tasks/minions/bso/turaelsTrialsActivity'; -import { calculateHunterResult } from '../../tasks/minions/HunterActivity/hunterActivity'; import { calculateMiningResult } from '../../tasks/minions/miningActivity'; import { gearstatToSetup, gorajanBoosts } from '../lib/abstracted_commands/minionKill'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; import { calculateHunterInput } from './hunt'; import { calculateMiningInput } from './mine'; import { determineTameClueResult } from './tames'; @@ -184,7 +185,7 @@ export const ratesCommand: OSBMahojiCommand = { const user = await mUserFetch(userID); if (options.minigames?.baxtorian_bathhouses) { - let results = []; + const results = []; for (const tier of bathHouseTiers) { for (const ore of BathhouseOres) { for (const mixture of BathwaterMixtures) { @@ -207,7 +208,7 @@ export const ratesCommand: OSBMahojiCommand = { results.sort( (a, b) => b.firemakingXP * GLOBAL_BSO_XP_MULTIPLIER - a.firemakingXP * GLOBAL_BSO_XP_MULTIPLIER ); - let tableArr = [['Combo', 'FM XP/hr', 'Herb XP/hr'].join('\t')]; + const tableArr = [['Combo', 'FM XP/hr', 'Herb XP/hr'].join('\t')]; for (const { tier, ore, mixture, firemakingXP, herbXP } of results) { const duration = durationPerBaxBath * 4; const totalFiremakingXP = firemakingXP * GLOBAL_BSO_XP_MULTIPLIER; @@ -251,9 +252,7 @@ export const ratesCommand: OSBMahojiCommand = { const averageHoursToGetBoth = averageMinutesToGetBoth / 60; return `For every minute in any trip, a random, valid seed from your bank has a 1 in ${zygomiteSeedMutChance} chance of mutating, and then that mutated seed has a 1 in ${avgSurvivalChance.toFixed( 2 - )} (weighted average) chance of surviving. ${averageHoursToGetBoth.toFixed( - 1 - )} hours on average to get a zygomite seed. + )} (weighted average) chance of surviving. ${averageHoursToGetBoth.toFixed(1)} hours on average to get a zygomite seed. ${zygomiteFarmingSource .map( @@ -370,10 +369,10 @@ ${zygomiteFarmingSource timeToFinish = reduceNumByPercent(timeToFinish, bestFood!); const sampleSize = 100_000; - let boostedSize = increaseNumByPercent(sampleSize, 25); + const boostedSize = increaseNumByPercent(sampleSize, 25); const loot = monster.table.kill(boostedSize, {}); - let totalTime = timeToFinish * sampleSize; + const totalTime = timeToFinish * sampleSize; let str = `${monster.name}\n`; @@ -426,7 +425,7 @@ ${zygomiteFarmingSource for (const _hasQuickTrap of [true, false]) { for (const usingStamAndHunterPotions of [true, false]) { for (const hasMaxLearning of [true, false]) { - let hasQuickTrap = + const hasQuickTrap = _hasQuickTrap && creature.huntTechnique === HunterTechniqueEnum.BoxTrapping; if (creature.huntTechnique !== HunterTechniqueEnum.BoxTrapping && _hasQuickTrap) { continue; @@ -622,7 +621,7 @@ ${zygomiteFarmingSource ); if (usingAdze && (!smeltedOre || isPowerminingInput)) continue; - let miningLevel = 120; + const miningLevel = 120; const fakeGear = user.gear.skilling.clone(); fakeGear.equip('Volcanic pickaxe'); fakeGear.equip('Varrock armour 4'); @@ -653,7 +652,8 @@ ${zygomiteFarmingSource miningLevel, craftingLevel: 120, strengthLevel: 120, - maxTripLength: duration + maxTripLength: duration, + user }); if (typeof result === 'string') continue; const spiritOre = stoneSpirits.find(t => t.ore.id === ore.id); @@ -760,8 +760,7 @@ ${zygomiteFarmingSource const nextEnergy = divinationEnergies[divinationEnergies.indexOf(energy) + 1]; let timeToGetBoon = 0; if ( - nextEnergy && - nextEnergy.boonEnergyCost && + nextEnergy?.boonEnergyCost && energyPerHour > 0 && res.loot.has(energy.item.id) ) { @@ -773,7 +772,7 @@ ${zygomiteFarmingSource ? '0' : calcPerHour(energyReceived * calcAtomicEnergy(energy), duration).toFixed( 1 - ); + ); results += [ energy.type, @@ -827,7 +826,7 @@ ${zygomiteFarmingSource } const quantity = Math.floor(Time.Hour / timePerLap); const duration = quantity * timePerLap; - let agilityLevel = 120; + const agilityLevel = 120; const result = calculateAgilityResult({ quantity, course, @@ -860,10 +859,10 @@ ${zygomiteFarmingSource for (const floor of [1, 2, 3, 4, 5, 6, 7]) { for (const hasPortent of [true, false]) { const dungeonLength = Time.Minute * 5 * (floor / 2); - let quantity = Math.floor(calcMaxTripLength(user, 'Dungeoneering') / dungeonLength); - let duration = quantity * dungeonLength; - let dungeoneeringLevel = 120; - let goraShardChance = calcGorajanShardChance({ + const quantity = Math.floor(calcMaxTripLength(user, 'Dungeoneering') / dungeonLength); + const duration = quantity * dungeonLength; + const dungeoneeringLevel = 120; + const goraShardChance = calcGorajanShardChance({ dungLevel: dungeoneeringLevel, hasMasterCape: user.hasEquipped('Dungeoneering master cape'), hasRingOfLuck: user.hasEquipped('Ring of luck') diff --git a/src/mahoji/commands/redeem.ts b/src/mahoji/commands/redeem.ts index 19968b3dc06..70e8467cd4a 100644 --- a/src/mahoji/commands/redeem.ts +++ b/src/mahoji/commands/redeem.ts @@ -1,12 +1,12 @@ import { ProductID, products } from '@oldschoolgg/toolkit'; -import { bold } from 'discord.js'; -import { notEmpty, Time } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType, bold } from 'discord.js'; +import { Time, notEmpty } from 'e'; import { BOT_TYPE } from '../../lib/constants'; import { addToDoubleLootTimer } from '../../lib/doubleLoot'; import { roboChimpSyncData } from '../../lib/roboChimp'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const redeemCommand: OSBMahojiCommand = { name: 'redeem', @@ -79,7 +79,7 @@ export const redeemCommand: OSBMahojiCommand = { push: product.bit } } - }) + }) : undefined ].filter(notEmpty) ); diff --git a/src/mahoji/commands/roll.ts b/src/mahoji/commands/roll.ts index 7842d330b13..6b66ef32914 100644 --- a/src/mahoji/commands/roll.ts +++ b/src/mahoji/commands/roll.ts @@ -1,7 +1,8 @@ -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { cryptoRand } from '../../lib/util'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const rollCommand: OSBMahojiCommand = { name: 'roll', diff --git a/src/mahoji/commands/rp.ts b/src/mahoji/commands/rp.ts index c4e3293b03f..bc8dfc9993c 100644 --- a/src/mahoji/commands/rp.ts +++ b/src/mahoji/commands/rp.ts @@ -1,37 +1,35 @@ -import { toTitleCase } from '@oldschoolgg/toolkit'; -import { UserEventType, xp_gains_skill_enum } from '@prisma/client'; +import { dateFm, toTitleCase } from '@oldschoolgg/toolkit'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import type { MahojiUserOption } from '@oldschoolgg/toolkit'; +import { type Prisma, UserEventType, xp_gains_skill_enum } from '@prisma/client'; import { DiscordSnowflake } from '@sapphire/snowflake'; import { Duration } from '@sapphire/time-utilities'; -import { codeBlock, SnowflakeUtil } from 'discord.js'; -import { randArrItem, sumArr, Time } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; -import { MahojiUserOption } from 'mahoji/dist/lib/types'; +import { SnowflakeUtil, codeBlock } from 'discord.js'; +import { ApplicationCommandOptionType } from 'discord.js'; +import { Time, objectValues, randArrItem, sumArr } from 'e'; import { Bank } from 'oldschooljs'; -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; -import { ADMIN_IDS, OWNER_IDS, production, SupportServer } from '../../config'; -import { analyticsTick } from '../../lib/analytics'; -import { calculateCompCapeProgress } from '../../lib/bso/calculateCompCapeProgress'; -import { BitField, Channel } from '../../lib/constants'; +import { ADMIN_IDS, OWNER_IDS, SupportServer, production } from '../../config'; +import { BitField, Channel, globalConfig } from '../../lib/constants'; import { allCollectionLogsFlat } from '../../lib/data/Collections'; -import { GearSetupType } from '../../lib/gear/types'; +import type { GearSetupType } from '../../lib/gear/types'; import { GrandExchange } from '../../lib/grandExchange'; import { marketPricemap } from '../../lib/marketPrices'; import { unEquipAllCommand } from '../../lib/minions/functions/unequipAllCommand'; import { unequipPet } from '../../lib/minions/functions/unequipPet'; -import { mahojiUserSettingsUpdate } from '../../lib/MUser'; -import { patreonTask } from '../../lib/patreon'; -import { allPerkBitfields } from '../../lib/perkTiers'; import { premiumPatronTime } from '../../lib/premiumPatronTime'; -import { prisma } from '../../lib/settings/prisma'; + +import { runRolesTask } from '../../lib/rolesTask'; import { TeamLoot } from '../../lib/simulation/TeamLoot'; -import { ItemBank } from '../../lib/types'; -import { dateFm, isValidDiscordSnowflake, returnStringOrFile } from '../../lib/util'; +import { SkillsEnum } from '../../lib/skilling/types'; +import type { ItemBank } from '../../lib/types'; +import { isValidDiscordSnowflake } from '../../lib/util'; import getOSItem from '../../lib/util/getOSItem'; import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; import { deferInteraction } from '../../lib/util/interactionReply'; import itemIsTradeable from '../../lib/util/itemIsTradeable'; -import { syncLinkedAccounts } from '../../lib/util/linkedAccountsUtil'; +import { logError } from '../../lib/util/logError'; import { makeBankImage } from '../../lib/util/makeBankImage'; import { migrateUser } from '../../lib/util/migrateUser'; import { parseBank } from '../../lib/util/parseStringBank'; @@ -39,8 +37,7 @@ import { insertUserEvent } from '../../lib/util/userEvents'; import { sendToChannelID } from '../../lib/util/webhook'; import { cancelUsersListings } from '../lib/abstracted_commands/cancelGEListingCommand'; import { gearSetupOption } from '../lib/mahojiCommandOptions'; -import { OSBMahojiCommand } from '../lib/util'; -import { mahojiUsersSettingsFetch } from '../mahojiSettings'; +import type { OSBMahojiCommand } from '../lib/util'; import { gifs } from './admin'; import { getUserInfo } from './minion'; import { sellPriceOfItem } from './sell'; @@ -48,16 +45,140 @@ import { sellPriceOfItem } from './sell'; const itemFilters = [ { name: 'Tradeable', - filter: (item: Item) => itemIsTradeable(item.id, true) + filter: (item: Item) => itemIsTradeable(item.id, true), + run: async () => { + const isValid = await GrandExchange.extensiveVerification(); + if (isValid) { + return 'No issues found.'; + } + return 'Something was invalid. Check logs!'; + } } ]; +async function usernameSync() { + const roboChimpUsersToCache = ( + await roboChimpClient.user.findMany({ + where: { + OR: [ + { + osb_cl_percent: { + gte: 80 + } + }, + { + bso_total_level: { + gte: 80 + } + }, + { + osb_total_level: { + gte: 1500 + } + }, + { + bso_total_level: { + gte: 1500 + } + }, + { + leagues_points_total: { + gte: 20_000 + } + } + ] + }, + select: { + id: true + } + }) + ).map(i => i.id.toString()); + + const orConditions: Prisma.UserWhereInput[] = []; + for (const skill of objectValues(SkillsEnum)) { + orConditions.push({ + [`skills_${skill}`]: { + gte: 15_000_000 + } + }); + } + const usersToCache = ( + await prisma.user.findMany({ + where: { + OR: [ + ...orConditions, + { + last_command_date: { + gt: new Date(Date.now() - Number(Time.Month)) + } + } + ], + id: { + notIn: roboChimpUsersToCache + } + }, + select: { + id: true + } + }) + ).map(i => i.id); + + const response: string[] = []; + const allNewUsers = await prisma.newUser.findMany({ + where: { + username: { + not: null + }, + id: { + in: [...usersToCache, ...roboChimpUsersToCache] + } + }, + select: { + id: true, + username: true + } + }); + + response.push(`Cached ${allNewUsers.length} usernames.`); + return response.join(', '); +} + function isProtectedAccount(user: MUser) { - if ([...ADMIN_IDS, ...OWNER_IDS].includes(user.id)) return true; - if ([BitField.isModerator, BitField.isContributor].some(bf => user.bitfield.includes(bf))) return true; + const botAccounts = ['303730326692429825', '729244028989603850', '969542224058654790']; + if ([...ADMIN_IDS, ...OWNER_IDS, ...botAccounts].includes(user.id)) return true; + if ([BitField.isModerator].some(bf => user.bitfield.includes(bf))) return true; return false; } +const actions = [ + { + name: 'validate_ge', + allowed: (user: MUser) => ADMIN_IDS.includes(user.id) || OWNER_IDS.includes(user.id), + run: async () => { + const isValid = await GrandExchange.extensiveVerification(); + if (isValid) { + return 'No issues found.'; + } + return 'Something was invalid. Check logs!'; + } + }, + { + name: 'sync_roles', + allowed: (user: MUser) => + ADMIN_IDS.includes(user.id) || OWNER_IDS.includes(user.id) || user.bitfield.includes(BitField.isModerator), + run: async () => { + return runRolesTask(!globalConfig.isProduction); + } + }, + { + name: 'sync_usernames', + allowed: (user: MUser) => ADMIN_IDS.includes(user.id) || OWNER_IDS.includes(user.id), + run: async () => { + return usernameSync(); + } + } +]; + export const rpCommand: OSBMahojiCommand = { name: 'rp', description: 'Admin tools second set', @@ -66,45 +187,13 @@ export const rpCommand: OSBMahojiCommand = { { type: ApplicationCommandOptionType.SubcommandGroup, name: 'action', - description: 'Action tools', - options: [ - { - type: ApplicationCommandOptionType.Subcommand, - name: 'validate_ge', - description: 'Validate the g.e.', - options: [] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'patreon_reset', - description: 'Reset all patreon data.', - options: [] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'force_comp_update', - description: 'Force the top 100 completionist users to update their completion percentage.', - options: [] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'view_all_items', - description: 'View all item IDs present in banks/cls.', - options: [] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'analytics_tick', - description: 'analyticsTick.', - options: [] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'networth_sync', - description: 'networth_sync.', - options: [] - } - ] + description: 'Actions', + options: actions.map(a => ({ + type: ApplicationCommandOptionType.Subcommand, + name: a.name, + description: a.name, + options: [] + })) }, { type: ApplicationCommandOptionType.SubcommandGroup, @@ -253,25 +342,6 @@ export const rpCommand: OSBMahojiCommand = { } ] }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'add_ironman_alt', - description: 'Add an ironman alt account for a user', - options: [ - { - type: ApplicationCommandOptionType.User, - name: 'main', - description: 'The main', - required: true - }, - { - type: ApplicationCommandOptionType.User, - name: 'ironman_alt', - description: 'The ironman alt', - required: true - } - ] - }, { type: ApplicationCommandOptionType.Subcommand, name: 'view_user', @@ -468,14 +538,7 @@ export const rpCommand: OSBMahojiCommand = { max_total?: { user: MahojiUserOption; type: UserEventType; message_id: string }; max?: { user: MahojiUserOption; type: UserEventType; skill: xp_gains_skill_enum; message_id: string }; }; - action?: { - validate_ge?: {}; - patreon_reset?: {}; - force_comp_update?: {}; - view_all_items?: {}; - analytics_tick?: {}; - networth_sync?: {}; - }; + action?: any; player?: { givetgb?: { user: MahojiUserOption }; viewbank?: { user: MahojiUserOption; json?: boolean }; @@ -497,7 +560,6 @@ export const rpCommand: OSBMahojiCommand = { user: MahojiUserOption; message_id: string; }; - add_ironman_alt?: { main: MahojiUserOption; ironman_alt: MahojiUserOption }; view_user?: { user: MahojiUserOption }; migrate_user?: { source: MahojiUserOption; dest: MahojiUserOption; reason?: string }; list_trades?: { @@ -514,12 +576,8 @@ export const rpCommand: OSBMahojiCommand = { const isOwner = OWNER_IDS.includes(userID.toString()); const isAdmin = ADMIN_IDS.includes(userID); const isMod = isOwner || isAdmin || adminUser.bitfield.includes(BitField.isModerator); - const isContrib = isMod || adminUser.bitfield.includes(BitField.isContributor); - const isTrusted = [BitField.IsWikiContributor, BitField.isContributor].some(bit => - adminUser.bitfield.includes(bit) - ); if (!guildID || (production && guildID.toString() !== SupportServer)) return randArrItem(gifs); - if (!isAdmin && !isMod && !isTrusted) return randArrItem(gifs); + if (!isAdmin && !isMod) return randArrItem(gifs); if (options.user_event) { const messageId = @@ -574,96 +632,19 @@ Date: ${dateFm(date)}`; if (!isMod) return randArrItem(gifs); - if (!guildID || !isContrib || (production && guildID.toString() !== SupportServer)) return randArrItem(gifs); - // Contributor+ only commands: - if (options.player?.givetgb) { - const user = await mUserFetch(options.player?.givetgb.user.user.id); - if (user.id === adminUser.id) { - return randArrItem(gifs); - } - await user.addItemsToBank({ items: new Bank().add('Tester gift box'), collectionLog: true }); - return `Gave 1x Tester gift box to ${user}.`; - } - - if (!isMod) return randArrItem(gifs); - // Mod+ only commands: - if (options.action?.validate_ge) { - const isValid = await GrandExchange.extensiveVerification(); - if (isValid) { - return 'No issues found.'; - } - return 'Something was invalid. Check logs!'; - } - if (options.action?.force_comp_update) { - const usersToUpdate = await prisma.userStats.findMany({ - where: { - untrimmed_comp_cape_percent: { - not: null - } - }, - orderBy: { - untrimmed_comp_cape_percent: 'desc' - }, - take: 100 - }); - for (const user of usersToUpdate) { - await calculateCompCapeProgress(await mUserFetch(user.user_id.toString())); - } - return 'Done.'; - } - - if (options.action?.analytics_tick) { - await analyticsTick(); - return 'Finished.'; - } - if (options.action?.networth_sync) { - const users = await prisma.user.findMany({ - where: { - GP: { - gt: 10_000_000_000 + if (options.action) { + for (const action of actions) { + if (options.action[action.name]) { + if (!action.allowed(adminUser)) return randArrItem(gifs); + try { + const result = await action.run(); + return result; + } catch (err) { + logError(err); + return 'An error occurred.'; } - }, - take: 20, - orderBy: { - GP: 'desc' - }, - select: { - id: true } - }); - for (const { id } of users) { - const user = await mUserFetch(id); - await user.update({ - cached_networth_value: (await user.calculateNetWorth()).value - }); } - return 'Done.'; - } - if (options.action?.view_all_items) { - const result = await prisma.$queryRawUnsafe< - { item_id: number }[] - >(`SELECT DISTINCT json_object_keys(bank)::int AS item_id -FROM users -UNION -SELECT DISTINCT jsonb_object_keys("collectionLogBank")::int AS item_id -FROM users -ORDER BY item_id ASC;`); - return returnStringOrFile(`[${result.map(i => i.item_id).join(',')}]`); - } - - if (options.action?.patreon_reset) { - const bitfieldsToRemove = [ - BitField.IsPatronTier1, - BitField.IsPatronTier2, - BitField.IsPatronTier3, - BitField.IsPatronTier4, - BitField.IsPatronTier5, - BitField.IsPatronTier6 - ]; - await prisma.$queryRaw`UPDATE users SET bitfield = bitfield - '{${bitfieldsToRemove.join(',')}'::int[];`; - await patreonTask.run(); - await syncLinkedAccounts(); - return 'Finished.'; } if (options.player?.set_buy_date) { @@ -718,8 +699,8 @@ ORDER BY item_id ASC;`); const gearSlot = opts.all ? 'all' : opts.gear_setup && allGearSlots.includes(opts.gear_setup) - ? opts.gear_setup - : undefined; + ? opts.gear_setup + : undefined; if (gearSlot === undefined) { return 'No gear slot specified.'; } @@ -756,7 +737,7 @@ ORDER BY item_id ASC;`); const items = new Bank(); if (options.player.steal_items.item_filter) { - const filter = itemFilters.find(i => i.name === options.player!.steal_items!.item_filter); + const filter = itemFilters.find(i => i.name === options.player?.steal_items?.item_filter); if (!filter) return 'Invalid item filter.'; for (const [item, qty] of userToStealFrom.bank.items()) { if (filter.filter(item)) { @@ -797,59 +778,6 @@ ORDER BY item_id ASC;`); if (!toDelete) await adminUser.addItemsToBank({ items, collectionLog: false }); return `${toTitleCase(actionMsgPast)} ${items.toString().slice(0, 500)} from ${userToStealFrom.mention}`; } - if (options.player?.add_ironman_alt) { - const mainAccount = await mahojiUsersSettingsFetch(options.player.add_ironman_alt.main.user.id, { - minion_ironman: true, - id: true, - ironman_alts: true, - main_account: true - }); - const altAccount = await mahojiUsersSettingsFetch(options.player.add_ironman_alt.ironman_alt.user.id, { - minion_ironman: true, - bitfield: true, - id: true, - ironman_alts: true, - main_account: true - }); - const mainUser = await mUserFetch(mainAccount.id); - const altUser = await mUserFetch(altAccount.id); - if (mainAccount === altAccount) return "They're they same account."; - if (mainAccount.minion_ironman) return `${mainUser.usernameOrMention} is an ironman.`; - if (!altAccount.minion_ironman) return `${altUser.usernameOrMention} is not an ironman.`; - - const peopleWithThisAltAlready = ( - await prisma.$queryRawUnsafe( - `SELECT id FROM users WHERE '${altAccount.id}' = ANY(ironman_alts);` - ) - ).length; - if (peopleWithThisAltAlready > 0) { - return `Someone already has ${altUser.usernameOrMention} as an ironman alt.`; - } - if (mainAccount.main_account) { - return `${mainUser.usernameOrMention} has a main account connected already.`; - } - if (altAccount.main_account) { - return `${altUser.usernameOrMention} has a main account connected already.`; - } - const mainAccountsAlts = mainAccount.ironman_alts; - if (mainAccountsAlts.includes(altAccount.id)) { - return `${mainUser.usernameOrMention} already has ${altUser.usernameOrMention} as an alt.`; - } - - await handleMahojiConfirmation( - interaction, - `Are you sure that \`${altUser.usernameOrMention}\` is the alt account of \`${mainUser.usernameOrMention}\`?` - ); - await mahojiUserSettingsUpdate(mainAccount.id, { - ironman_alts: { - push: altAccount.id - } - }); - await mahojiUserSettingsUpdate(altAccount.id, { - main_account: mainAccount.id - }); - return `You set \`${altUser.usernameOrMention}\` as the alt account of \`${mainUser.usernameOrMention}\`.`; - } if (options.player?.view_user) { const userToView = await mUserFetch(options.player.view_user.user.user.id); @@ -860,17 +788,16 @@ ORDER BY item_id ASC;`); if (!isOwner && !isAdmin) { return randArrItem(gifs); } + const { source, dest, reason } = options.player.migrate_user; + + if (source.user.id === dest.user.id) { + return 'Destination cannot be the same as the source!'; + } const sourceUser = await mUserFetch(source.user.id); const destUser = await mUserFetch(dest.user.id); if (isProtectedAccount(destUser)) return 'You cannot clobber that account.'; - if (allPerkBitfields.some(pt => destUser.bitfield.includes(pt))) { - await handleMahojiConfirmation( - interaction, - `The target user, ${destUser.logName}, has a Patreon Tier; are you really sure you want to DELETE all data from that account?` - ); - } const sourceXp = sumArr(Object.values(sourceUser.skillsAsXP)); const destXp = sumArr(Object.values(destUser.skillsAsXP)); if (destXp > sourceXp) { diff --git a/src/mahoji/commands/runecraft.ts b/src/mahoji/commands/runecraft.ts index 00c2aa381f2..feff23763c9 100644 --- a/src/mahoji/commands/runecraft.ts +++ b/src/mahoji/commands/runecraft.ts @@ -1,22 +1,22 @@ -import { toTitleCase } from '@oldschoolgg/toolkit'; -import { reduceNumByPercent, Time } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import { type CommandRunOptions, toTitleCase } from '@oldschoolgg/toolkit'; import { Bank } from 'oldschooljs'; import { SkillsEnum } from 'oldschooljs/dist/constants'; +import { ApplicationCommandOptionType } from 'discord.js'; +import { Time, reduceNumByPercent } from 'e'; import { Emoji } from '../../lib/constants'; -import { inventionBoosts, InventionID, inventionItemBoost } from '../../lib/invention/inventions'; +import { InventionID, inventionBoosts, inventionItemBoost } from '../../lib/invention/inventions'; import { darkAltarCommand } from '../../lib/minions/functions/darkAltarCommand'; import { sinsOfTheFatherSkillRequirements } from '../../lib/skilling/functions/questRequirements'; import Runecraft from '../../lib/skilling/skills/runecraft'; -import { RunecraftActivityTaskOptions } from '../../lib/types/minions'; +import type { RunecraftActivityTaskOptions } from '../../lib/types/minions'; import { formatDuration, formatSkillRequirements, itemID, stringMatches } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; import { determineRunes } from '../../lib/util/determineRunes'; import { updateBankSetting } from '../../lib/util/updateBankSetting'; import { tiaraRunecraftCommand } from '../lib/abstracted_commands/tiaraRunecraftCommand'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; import { calcMaxRCQuantity, userHasGracefulEquipped } from '../mahojiSettings'; export const runecraftCommand: OSBMahojiCommand = { @@ -251,7 +251,7 @@ export const runecraftCommand: OSBMahojiCommand = { let imbueCasts = 0; let teleportReduction = 1; - let removeTalismanAndOrRunes = new Bank(); + const removeTalismanAndOrRunes = new Bank(); let hasRingOfTheElements = false; if (runeObj.inputTalisman) { const tomeOfFire = user.hasEquippedOrInBank(['Tome of fire', 'Tome of fire (empty)']) ? 0 : 7; diff --git a/src/mahoji/commands/sacrifice.ts b/src/mahoji/commands/sacrifice.ts index 900788efc53..c3d1b991b52 100644 --- a/src/mahoji/commands/sacrifice.ts +++ b/src/mahoji/commands/sacrifice.ts @@ -1,27 +1,27 @@ -import { roll } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; import { Bank } from 'oldschooljs'; -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { Emoji, Events } from '../../lib/constants'; import { cats } from '../../lib/growablePets'; import minionIcons from '../../lib/minions/data/minionIcons'; -import { ItemBank } from '../../lib/types'; -import { toKMB } from '../../lib/util'; +import type { ItemBank } from '../../lib/types'; +import { roll, toKMB, truncateString } from '../../lib/util'; import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; import { deferInteraction } from '../../lib/util/interactionReply'; import { parseBank } from '../../lib/util/parseStringBank'; import resolveItems from '../../lib/util/resolveItems'; import { updateBankSetting } from '../../lib/util/updateBankSetting'; import { filterOption } from '../lib/mahojiCommandOptions'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; import { userStatsBankUpdate } from '../mahojiSettings'; import { sellPriceOfItem } from './sell'; async function trackSacBank(user: MUser, bank: Bank) { await Promise.all([ updateBankSetting('economyStats_sacrificedBank', bank), - userStatsBankUpdate(user.id, 'sacrificed_bank', bank) + userStatsBankUpdate(user, 'sacrificed_bank', bank) ]); const stats = await user.fetchStats({ sacrificed_bank: true }); return new Bank(stats.sacrificed_bank as ItemBank); @@ -99,7 +99,7 @@ export const sacrificeCommand: OSBMahojiCommand = { ); } - deferInteraction(interaction); + await deferInteraction(interaction); const bankToSac = parseBank({ inputStr: options.items, @@ -161,7 +161,7 @@ export const sacrificeCommand: OSBMahojiCommand = { await handleMahojiConfirmation( interaction, - `${user}, are you sure you want to sacrifice ${bankToSac}? This will add ${totalPrice.toLocaleString()} (${toKMB( + `${user}, are you sure you want to sacrifice ${truncateString(bankToSac.toString(), 15000)}? This will add ${totalPrice.toLocaleString()} (${toKMB( totalPrice )}) to your sacrificed amount.` ); diff --git a/src/mahoji/commands/sell.ts b/src/mahoji/commands/sell.ts index 47f10a80b54..531caec9f53 100644 --- a/src/mahoji/commands/sell.ts +++ b/src/mahoji/commands/sell.ts @@ -1,20 +1,21 @@ -import { Prisma } from '@prisma/client'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import type { Prisma } from '@prisma/client'; +import { ApplicationCommandOptionType } from 'discord.js'; import { calcPercentOfNum, clamp, reduceNumByPercent } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; import { Bank } from 'oldschooljs'; -import { Item, ItemBank } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; import { MAX_INT_JAVA } from '../../lib/constants'; import { customPrices } from '../../lib/customItems/util'; -import { prisma } from '../../lib/settings/prisma'; + import { NestBoxesTable } from '../../lib/simulation/misc'; import { itemID, returnStringOrFile, toKMB } from '../../lib/util'; import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; import { parseBank } from '../../lib/util/parseStringBank'; import { updateBankSetting } from '../../lib/util/updateBankSetting'; import { filterOption } from '../lib/mahojiCommandOptions'; -import { OSBMahojiCommand } from '../lib/util'; -import { updateClientGPTrackSetting, userStatsUpdate } from '../mahojiSettings'; +import type { OSBMahojiCommand } from '../lib/util'; +import { updateClientGPTrackSetting, userStatsBankUpdate, userStatsUpdate } from '../mahojiSettings'; /** * - Hardcoded prices @@ -42,11 +43,11 @@ const specialSoldItems = new Map([ export const CUSTOM_PRICE_CACHE = new Map(); export function sellPriceOfItem(item: Item, taxRate = 25): { price: number; basePrice: number } { - let cachePrice = CUSTOM_PRICE_CACHE.get(item.id); + const cachePrice = CUSTOM_PRICE_CACHE.get(item.id); if (!cachePrice && (item.price === undefined || !item.tradeable)) { return { price: 0, basePrice: 0 }; } - let basePrice = cachePrice ?? item.price; + const basePrice = cachePrice ?? item.price; let price = basePrice; price = reduceNumByPercent(price, taxRate); if (!(item.id in customPrices) && price < (item.highalch ?? 0) * 3) { @@ -58,7 +59,7 @@ export function sellPriceOfItem(item: Item, taxRate = 25): { price: number; base export function sellStorePriceOfItem(item: Item, qty: number): { price: number; basePrice: number } { if (!item.cost || !item.lowalch) return { price: 0, basePrice: 0 }; - let basePrice = item.cost; + const basePrice = item.cost; // Sell price decline with stock by 3% until 10% of item value and is always low alch price when stock is 0. const percentageFirstEleven = (0.4 - 0.015 * Math.min(qty - 1, 10)) * Math.min(qty, 11); let price = ((percentageFirstEleven + Math.max(qty - 11, 0) * 0.1) * item.cost) / qty; @@ -271,14 +272,14 @@ export const sellCommand: OSBMahojiCommand = { await Promise.all([ updateClientGPTrackSetting('gp_sell', totalPrice), updateBankSetting('sold_items_bank', bankToSell), + userStatsBankUpdate(user, 'items_sold_bank', bankToSell), userStatsUpdate( user.id, - userStats => ({ - items_sold_bank: new Bank(userStats.items_sold_bank as ItemBank).add(bankToSell).bank, + { sell_gp: { increment: totalPrice } - }), + }, {} ), prisma.botItemSell.createMany({ data: botItemSellData }) diff --git a/src/mahoji/commands/simulate.ts b/src/mahoji/commands/simulate.ts index e7c5aace037..eb1978c27cb 100644 --- a/src/mahoji/commands/simulate.ts +++ b/src/mahoji/commands/simulate.ts @@ -1,16 +1,20 @@ +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import type { CommandResponse } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { randInt, roll } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; import { Bank } from 'oldschooljs'; import { ChambersOfXeric } from 'oldschooljs/dist/simulation/misc'; import { toKMB } from 'oldschooljs/dist/util'; -import { PerkTier } from '../../lib/constants'; +import { PerkTier } from '@oldschoolgg/toolkit'; +import { ColosseumWaveBank, startColosseumRun } from '../../lib/colosseum'; import pets from '../../lib/data/pets'; +import { assert, averageBank, formatDuration } from '../../lib/util'; +import { deferInteraction } from '../../lib/util/interactionReply'; import { makeBankImage } from '../../lib/util/makeBankImage'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; -export function determineCoxLimit(user: MUser) { +function determineCoxLimit(user: MUser) { const perkTier = user.perkTier(); if (perkTier >= PerkTier.Three) { @@ -28,6 +32,68 @@ export function determineCoxLimit(user: MUser) { return 10; } +function simulateColosseumRuns(samples = 100) { + const totalSimulations = samples; + let totalAttempts = 0; + let totalDeaths = 0; + const totalLoot = new Bank(); + const finishAttemptAmounts = []; + let totalDuration = 0; + + for (let i = 0; i < totalSimulations; i++) { + let attempts = 0; + let deaths = 0; + let done = false; + const kcBank = new ColosseumWaveBank(); + const runLoot = new Bank(); + + while (!done) { + attempts++; + const result = startColosseumRun({ + kcBank, + hasScythe: true, + hasTBow: true, + hasVenBow: true, + hasBF: false, + hasClaws: true, + hasSGS: true, + hasTorture: true, + scytheCharges: 300, + venatorBowCharges: 50, + bloodFuryCharges: 0 + }); + totalDuration += result.realDuration; + kcBank.add(result.addedWaveKCBank); + if (result.diedAt === null) { + if (result.loot) runLoot.add(result.loot); + done = true; + } else { + deaths++; + } + } + assert(kcBank.amount(12) > 0); + finishAttemptAmounts.push(attempts); + totalAttempts += attempts; + totalDeaths += deaths; + totalLoot.add(runLoot); + } + + const averageAttempts = totalAttempts / totalSimulations; + const averageDeaths = totalDeaths / totalSimulations; + + finishAttemptAmounts.sort((a, b) => a - b); + + const result = `Results from the simulation of ${totalSimulations}x people completing the Colosseum: +**Average duration to beat wave 12 for first time:** ${formatDuration(totalDuration / totalSimulations)} +**Average deaths before beating wave 12:** ${averageDeaths} +**Average loot:** ${averageBank(totalLoot, totalSimulations)} +**Fastest completion trips:** ${finishAttemptAmounts[0]} +**Mean completion trips:** ${finishAttemptAmounts[Math.floor(finishAttemptAmounts.length / 2)]} +**Average trips to beat wave 12:** ${averageAttempts}. +**Longest completion trips:** ${finishAttemptAmounts[finishAttemptAmounts.length - 1]}`; + return result; +} + async function coxCommand(user: MUser, quantity: number, cm = false, points = 25_000, teamSize = 4): CommandResponse { const limit = determineCoxLimit(user); if (quantity > limit) { @@ -44,7 +110,7 @@ async function coxCommand(user: MUser, quantity: number, cm = false, points = 25 team.push({ id: `${randInt(1, 10_000_000)}`, personalPoints: points }); } - let loot = new Bank(); + const loot = new Bank(); for (let i = 0; i < quantity; i++) { const singleRaidLoot = ChambersOfXeric.complete({ team, @@ -125,9 +191,15 @@ export const simulateCommand: OSBMahojiCommand = { required: true } ] + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'colosseum', + description: 'Simulate colosseum.' } ], run: async ({ + interaction, options, userID }: CommandRunOptions<{ @@ -140,8 +212,13 @@ export const simulateCommand: OSBMahojiCommand = { petroll?: { quantity: number; }; + colosseum?: {}; }>) => { + await deferInteraction(interaction); const user = await mUserFetch(userID.toString()); + if (options.colosseum) { + return simulateColosseumRuns(); + } if (options.cox) { return coxCommand( user, diff --git a/src/mahoji/commands/slayer.ts b/src/mahoji/commands/slayer.ts index 120db93f6fe..784a11e0cae 100644 --- a/src/mahoji/commands/slayer.ts +++ b/src/mahoji/commands/slayer.ts @@ -1,5 +1,6 @@ -import { User } from 'discord.js'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import type { User } from 'discord.js'; +import { ApplicationCommandOptionType } from 'discord.js'; import { Monsters } from 'oldschooljs'; import { autoslayChoices, slayerMasterChoices } from '../../lib/slayer/constants'; @@ -18,7 +19,7 @@ import { slayerStatusCommand, slayerUnblockCommand } from '../lib/abstracted_commands/slayerTaskCommand'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; import { mahojiUsersSettingsFetch } from '../mahojiSettings'; export const slayerCommand: OSBMahojiCommand = { @@ -115,9 +116,9 @@ export const slayerCommand: OSBMahojiCommand = { !value ? true : r.name.toLowerCase().includes(value) || - r.aliases?.some(alias => + r.aliases?.some(alias => alias.toLowerCase().includes(value.toLowerCase()) - ) + ) ) .map(m => { return { name: m.name, value: m.name }; @@ -142,13 +143,13 @@ export const slayerCommand: OSBMahojiCommand = { if (blockList.slayer_blocked_ids.length === 0) { return [{ name: "You don't have any monsters blocked", value: '' }]; } - const blockedMonsters = blockList.slayer_blocked_ids.map(mId => - Monsters.find(m => m.id === mId) + const blockedMonsters = blockList.slayer_blocked_ids.map( + mId => Monsters.find(m => m.id === mId)! ); return blockedMonsters - .filter(m => (!value ? true : m!.name.toLowerCase().includes(value.toLowerCase()))) + .filter(m => (!value ? true : m?.name.toLowerCase().includes(value.toLowerCase()))) .map(m => { - return { name: m!.name, value: m!.name }; + return { name: m?.name, value: m?.name }; }); } } @@ -172,9 +173,9 @@ export const slayerCommand: OSBMahojiCommand = { (!value ? true : r.name.toLowerCase().includes(value) || - r.aliases?.some(alias => + r.aliases?.some(alias => alias.toLowerCase().includes(value.toLowerCase()) - )) + )) ).map(m => { return { name: m.name, value: m.name }; }); @@ -232,9 +233,9 @@ export const slayerCommand: OSBMahojiCommand = { (!value ? true : r.name.toLowerCase().includes(value) || - r.aliases?.some(alias => + r.aliases?.some(alias => alias.toLowerCase().includes(value.toLowerCase()) - )) + )) ).map(m => { return { name: m.name, value: m.name }; }); diff --git a/src/mahoji/commands/smelt.ts b/src/mahoji/commands/smelt.ts index 5df608719ed..05f7de04802 100644 --- a/src/mahoji/commands/smelt.ts +++ b/src/mahoji/commands/smelt.ts @@ -1,16 +1,17 @@ +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { Time } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; import { Bank } from 'oldschooljs'; +import { resolveItems } from 'oldschooljs/dist/util/util'; import Smithing from '../../lib/skilling/skills/smithing'; import { SkillsEnum } from '../../lib/skilling/types'; -import { SmeltingActivityTaskOptions } from '../../lib/types/minions'; +import type { SmeltingActivityTaskOptions } from '../../lib/types/minions'; import { formatDuration, formatSkillRequirements, itemID, stringMatches } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; -import resolveItems from '../../lib/util/resolveItems'; import { updateBankSetting } from '../../lib/util/updateBankSetting'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; import { userHasGracefulEquipped } from '../mahojiSettings'; export const smeltingCommand: OSBMahojiCommand = { @@ -61,7 +62,7 @@ export const smeltingCommand: OSBMahojiCommand = { const bar = blast_furnace ? Smithing.BlastableBars.find( bar => stringMatches(bar.name, name) || stringMatches(bar.name.split(' ')[0], name) - ) + ) : Smithing.Bars.find(bar => stringMatches(bar.name, name) || stringMatches(bar.name.split(' ')[0], name)); if (!bar) { @@ -163,7 +164,7 @@ export const smeltingCommand: OSBMahojiCommand = { coinsToRemove = Math.floor(gpPerHour * (duration / Time.Hour)); const gp = user.GP; if (gp < coinsToRemove) { - return `You need atleast ${coinsToRemove} GP to work at the Blast Furnace.`; + return `You need at least ${coinsToRemove} GP to work at the Blast Furnace.`; } cost.add('Coins', coinsToRemove); diff --git a/src/mahoji/commands/smith.ts b/src/mahoji/commands/smith.ts index 7aa9569c58b..1dd168fef09 100644 --- a/src/mahoji/commands/smith.ts +++ b/src/mahoji/commands/smith.ts @@ -1,5 +1,6 @@ -import { calcPercentOfNum, Time } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; +import { Time, calcPercentOfNum } from 'e'; import { Bank } from 'oldschooljs'; import { BlacksmithOutfit } from '../../lib/bsoOpenables'; @@ -7,14 +8,14 @@ import { KaramjaDiary, userhasDiaryTier } from '../../lib/diaries'; import Smithing from '../../lib/skilling/skills/smithing'; import smithables from '../../lib/skilling/skills/smithing/smithables'; import { SkillsEnum } from '../../lib/skilling/types'; -import { SmithingActivityTaskOptions } from '../../lib/types/minions'; +import type { SmithingActivityTaskOptions } from '../../lib/types/minions'; import { formatDuration, stringMatches } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; import resolveItems from '../../lib/util/resolveItems'; import { pluraliseItemName } from '../../lib/util/smallUtils'; import { updateBankSetting } from '../../lib/util/updateBankSetting'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const smithCommand: OSBMahojiCommand = { name: 'smith', @@ -76,7 +77,7 @@ export const smithCommand: OSBMahojiCommand = { let setBonus = 0; if ( user.hasEquippedOrInBank( - Object.keys(Smithing.smithsUniformItems).map(i => parseInt(i)), + Object.keys(Smithing.smithsUniformItems).map(i => Number.parseInt(i)), 'every' ) ) { @@ -84,7 +85,7 @@ export const smithCommand: OSBMahojiCommand = { } else { // For each Smiths' Uniform item, check if they have it, give % chance to save 1 tick each item for (const [itemID, bonus] of Object.entries(Smithing.smithsUniformItems)) { - if (user.hasEquippedOrInBank(parseInt(itemID))) { + if (user.hasEquippedOrInBank(Number.parseInt(itemID))) { setBonus += bonus; } } diff --git a/src/mahoji/commands/steal.ts b/src/mahoji/commands/steal.ts index 876fbbbca66..d4a16a7b833 100644 --- a/src/mahoji/commands/steal.ts +++ b/src/mahoji/commands/steal.ts @@ -1,20 +1,20 @@ -import { stringMatches } from '@oldschoolgg/toolkit'; -import { User } from 'discord.js'; -import { randInt, reduceNumByPercent } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import { type CommandRunOptions, stringMatches } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType, type User } from 'discord.js'; +import { randInt, reduceNumByPercent } from 'e'; import { ArdougneDiary, userhasDiaryTier } from '../../lib/diaries'; import removeFoodFromUser from '../../lib/minions/functions/removeFoodFromUser'; -import { Stealable, stealables } from '../../lib/skilling/skills/thieving/stealables'; +import type { Stealable } from '../../lib/skilling/skills/thieving/stealables'; +import { stealables } from '../../lib/skilling/skills/thieving/stealables'; import { SkillsEnum } from '../../lib/skilling/types'; -import { PickpocketActivityTaskOptions } from '../../lib/types/minions'; +import type { PickpocketActivityTaskOptions } from '../../lib/types/minions'; import { formatDuration } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; import { logError } from '../../lib/util/logError'; import { updateBankSetting } from '../../lib/util/updateBankSetting'; import { calcLootXPPickpocketing } from '../../tasks/minions/pickpocketActivity'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; import { rogueOutfitPercentBonus, userStatsBankUpdate } from '../mahojiSettings'; export const stealCommand: OSBMahojiCommand = { @@ -68,7 +68,7 @@ export const stealCommand: OSBMahojiCommand = { } if (stealable.qpRequired && user.QP < stealable.qpRequired) { - return `You need atleast **${stealable.qpRequired}** QP to ${ + return `You need at least **${stealable.qpRequired}** QP to ${ stealable.type === 'pickpockable' ? 'pickpocket' : 'steal from' } a ${stealable.name}.`; } diff --git a/src/mahoji/commands/tames.ts b/src/mahoji/commands/tames.ts index 1f4a548605a..ef3ec086144 100644 --- a/src/mahoji/commands/tames.ts +++ b/src/mahoji/commands/tames.ts @@ -1,50 +1,49 @@ +import { readFileSync } from 'node:fs'; +import { readFile } from 'node:fs/promises'; import { bold, time } from '@discordjs/builders'; -import { Canvas, Image, loadImage, SKRSContext2D } from '@napi-rs/canvas'; +import { Canvas, type Image, type SKRSContext2D, loadImage } from '@napi-rs/canvas'; import { mentionCommand } from '@oldschoolgg/toolkit'; -import { Tame, tame_growth } from '@prisma/client'; +import type { CommandResponse, CommandRunOptions } from '@oldschoolgg/toolkit'; +import { type Tame, tame_growth } from '@prisma/client'; import { toTitleCase } from '@sapphire/utilities'; -import { ChatInputCommandInteraction, User } from 'discord.js'; +import { ApplicationCommandOptionType, type ChatInputCommandInteraction, type User } from 'discord.js'; import { + Time, calcPercentOfNum, calcWhatPercent, increaseNumByPercent, notEmpty, percentChance, randInt, - reduceNumByPercent, - Time + reduceNumByPercent } from 'e'; -import { readFileSync } from 'fs'; -import { readFile } from 'fs/promises'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; import { Bank } from 'oldschooljs'; -import { Item, ItemBank } from 'oldschooljs/dist/meta/types'; +import type { Item, ItemBank } from 'oldschooljs/dist/meta/types'; -import { ClueTier, ClueTiers } from '../../lib/clues/clueTiers'; -import { badges, PerkTier } from '../../lib/constants'; +import { type ClueTier, ClueTiers } from '../../lib/clues/clueTiers'; +import { PerkTier, badges } from '../../lib/constants'; import { Eatables } from '../../lib/data/eatables'; import { getSimilarItems } from '../../lib/data/similarItems'; import { trackLoot } from '../../lib/lootTrack'; import { Planks } from '../../lib/minions/data/planks'; import getUserFoodFromBank from '../../lib/minions/functions/getUserFoodFromBank'; import { getUsersPerkTier } from '../../lib/perkTiers'; -import { prisma } from '../../lib/settings/prisma'; + import Tanning from '../../lib/skilling/skills/crafting/craftables/tanning'; import { SkillsEnum } from '../../lib/skilling/types'; import { + type SeaMonkeySpell, + type TameKillableMonster, + TameSpeciesID, + TameType, createTameTask, getIgneTameKC, igneArmors, - SeaMonkeySpell, seaMonkeySpells, seaMonkeyStaves, tameFeedableItems, - TameKillableMonster, tameKillableMonsters, - tameSpecies, - TameSpeciesID, - TameType + tameSpecies } from '../../lib/tames'; import { assert, @@ -75,8 +74,8 @@ import { } from '../../lib/util/tameUtil'; import { updateBankSetting } from '../../lib/util/updateBankSetting'; import { arbitraryTameActivities } from '../../tasks/tames/tameTasks'; -import { collectables } from '../lib/abstracted_commands/collectCommand'; -import { OSBMahojiCommand } from '../lib/util'; +import { collectables } from '../lib/collectables'; +import type { OSBMahojiCommand } from '../lib/util'; const tameImageSize = 96; @@ -264,7 +263,6 @@ const tameImageReplacementEasterEggs = [ })) ]; -// eslint-disable-next-line @typescript-eslint/init-declarations let sprites: { base: { image: Image; @@ -377,11 +375,11 @@ export async function tameImage(user: MUser): CommandResponse { return "You don't have any tames."; } - let { tame, activity } = await getUsersTame(user); + const { tame, activity } = await getUsersTame(user); // Init the background images if they are not already - let { + const { sprite, uniqueSprite, background: userBgImage @@ -430,8 +428,8 @@ export async function tameImage(user: MUser): CommandResponse { let i = 0; for (const t of userTames) { const species = tameSpecies.find(i => i.id === t.species_id)!; - let isTameActive: boolean = false; - let selectedTame = tame && t.id === tame.id; + let isTameActive = false; + const selectedTame = tame && t.id === tame.id; if (selectedTame) isTameActive = activity !== null; const x = i % tamesPerLine; @@ -512,7 +510,7 @@ export async function tameImage(user: MUser): CommandResponse { if (tameHasBeenFed(t, item.id)) { const itemImage = await bankImageGenerator.getItemImage(item.id); if (itemImage) { - let ratio = 19 / itemImage.height; + const ratio = 19 / itemImage.height; const yLine = Math.floor(feedQty / 3); if (feedQty % 3 === 0) prevWidth = 0; ctx.drawImage( @@ -642,7 +640,7 @@ export async function removeRawFood({ return { success: true, - str: `${itemCost} from ${user.usernameOrMention}${foodBoosts.length > 0 ? `(${foodBoosts.join(', ')})` : ''}`, + str: `${itemCost} from ${user.rawUsername}${foodBoosts.length > 0 ? `(${foodBoosts.join(', ')})` : ''}`, removed: itemCost }; } @@ -700,13 +698,13 @@ async function mergeCommand(user: MUser, interaction: ChatInputCommandInteractio )}`; } - const tames = await prisma.tame.findMany({ where: { user_id: user.id } }); + const tames = await user.fetchTames(); const toSelect = tames.find(t => t.id === tameID); if (!toSelect || !tameID) { return "Couldn't find a tame to participate in the ritual. Make sure you selected the correct Tame, by its number or nickname."; } - if (toSelect.equipped_armor || toSelect.equipped_primary) { + if (toSelect.equippedArmor || toSelect.equippedPrimary) { return "The tame you're merging has gear equipped, unequip that gear first."; } @@ -714,7 +712,7 @@ async function mergeCommand(user: MUser, interaction: ChatInputCommandInteractio if (activity) return 'Your tame is busy. Wait for it to be free to do this.'; if (!tame || !species) return "You don't have a selected tame. Select your tame first."; if (tame.id === toSelect.id) return `You can't merge ${tameName(tame)} with itself!`; - if (species.id !== getTameSpecies(toSelect).id) { + if (species.id !== toSelect.species.id) { return "You can't merge two tames from two different species!"; } @@ -727,27 +725,25 @@ async function mergeCommand(user: MUser, interaction: ChatInputCommandInteractio } const mergeStuff = { - totalLoot: new Bank(tame!.max_total_loot as ItemBank).add(toSelect.max_total_loot as ItemBank).bank, - fedItems: new Bank(tame!.fed_items as ItemBank).add(toSelect.fed_items as ItemBank).bank, - maxCombatLevel: Math.max(tame!.max_combat_level, toSelect.max_combat_level), - maxArtisanLevel: Math.max(tame!.max_artisan_level, toSelect.max_artisan_level), - maxGathererLevel: Math.max(tame!.max_gatherer_level, toSelect.max_gatherer_level), - maxSupportLevel: Math.max(tame!.max_support_level, toSelect.max_support_level), + totalLoot: new Bank(tame!.max_total_loot as ItemBank).add(toSelect.totalLoot).bank, + fedItems: new Bank(tame!.fed_items as ItemBank).add(toSelect.fedItems).bank, + maxCombatLevel: Math.max(tame!.max_combat_level, toSelect.maxCombatLevel), + maxArtisanLevel: Math.max(tame!.max_artisan_level, toSelect.maxArtisanLevel), + maxGathererLevel: Math.max(tame!.max_gatherer_level, toSelect.maxGathererLevel), + maxSupportLevel: Math.max(tame!.max_support_level, toSelect.maxSupportLevel), speciesVariant: - tame!.species_variant === shinyVariant || toSelect.species_variant === shinyVariant + tame!.species_variant === shinyVariant || toSelect.speciesVariant === shinyVariant ? shinyVariant : tame!.species_variant }; await handleMahojiConfirmation( interaction, - `Are you sure you want to merge **${tameName(toSelect)}** (Tame ${toSelect.id}) into **${tameName( + `Are you sure you want to merge **${toSelect}** (Tame ${toSelect.id}) into **${tameName( tame! )}** (Tame ${tame!.id})?\n\n${tameName( tame! - )} will receive all the items fed and all loot obtained from ${tameName( - toSelect - )}, and will have its stats match the highest of both tames.\n\n**THIS ACTION CAN NOT BE REVERSED!**` + )} will receive all the items fed and all loot obtained from ${toSelect}, and will have its stats match the highest of both tames.\n\n**THIS ACTION CAN NOT BE REVERSED!**` ); await user.removeItemsFromBank(mergingCost); @@ -784,7 +780,7 @@ async function mergeCommand(user: MUser, interaction: ChatInputCommandInteractio } }); - return `${tameName(tame)} consumed ${tameName(toSelect)} and all its attributes.`; + return `${tameName(tame)} consumed ${toSelect} and all its attributes.`; } async function feedCommand(interaction: ChatInputCommandInteraction, user: MUser, str: string) { @@ -793,13 +789,13 @@ async function feedCommand(interaction: ChatInputCommandInteraction, user: MUser return 'You have no selected tame.'; } - let rawBank = parseStringBank(str); - let bankToAdd = new Bank(); - let userBank = user.bank; + const rawBank = parseStringBank(str); + const bankToAdd = new Bank(); + const userBank = user.bank; for (const [item, qty] of rawBank) { - let qtyOwned = userBank.amount(item.id); + const qtyOwned = userBank.amount(item.id); if (qtyOwned === 0) continue; - let qtyToUse = !qty ? 1 : qty > qtyOwned ? qtyOwned : qty; + const qtyToUse = !qty ? 1 : qty > qtyOwned ? qtyOwned : qty; bankToAdd.add(item.id, qtyToUse); } @@ -876,7 +872,7 @@ Your tame will gain between (inclusively) ${levelRange[0]} and ${levelRange[1]} return `You fed ${bankToAdd} to ${tameName(tame)}. It gained ${bold(gained.toString())} levels from the egg!`; } - let specialStrArr = []; + const specialStrArr = []; for (const { item, description, tameSpeciesCanBeFedThis } of thisTameSpecialFeedableItems) { const similarItems = getSimilarItems(item.id); if (similarItems.some(si => bankToAdd.has(si))) { @@ -889,7 +885,7 @@ Your tame will gain between (inclusively) ${levelRange[0]} and ${levelRange[1]} specialStrArr.push(`**${item.name}**: ${description}`); } } - let specialStr = specialStrArr.length === 0 ? '' : `\n\n${specialStrArr.join(', ')}`; + const specialStr = specialStrArr.length === 0 ? '' : `\n\n${specialStrArr.join(', ')}`; await handleMahojiConfirmation( interaction, `Are you sure you want to feed \`${bankToAdd}\` to ${tameName( @@ -906,7 +902,7 @@ Note: Some items must be equipped to your tame, not fed. Check that you are feed } } - let newBoosts: string[] = []; + const newBoosts: string[] = []; for (const { item, announcementString } of thisTameSpecialFeedableItems) { if (bankToAdd.has(item.id) && !tameHasBeenFed(tame, item.id)) { newBoosts.push(`**${announcementString}**`); @@ -963,7 +959,7 @@ async function killCommand(user: MUser, channelID: string, str: string) { // Increase trip length based on minion growth: let speed = monster.timeToFinish * tameGrowthLevel(tame); - let boosts = []; + const boosts = []; // Apply calculated boost: const combatLevelChange = reduceNumByPercent(speed, combatLevelBoost); @@ -1085,9 +1081,7 @@ async function killCommand(user: MUser, channelID: string, str: string) { reply += `\n\n${monster.name} has a base kill time of **${formatDuration( monster.timeToFinish, true - )}**, your kill time is **${formatDuration(speed, true)}**, meaning you can kill **${( - maxTripLength / speed - ).toFixed(2)}** in your max trip length of **${formatDuration(maxTripLength, true)}**`; + )}**, your kill time is **${formatDuration(speed, true)}**, meaning you can kill **${(maxTripLength / speed).toFixed(2)}** in your max trip length of **${formatDuration(maxTripLength, true)}**`; return reply; } @@ -1124,7 +1118,7 @@ async function collectCommand(user: MUser, channelID: string, str: string) { speed /= 2.5; } - let boosts = []; + const boosts = []; const equippedStaff = seaMonkeyStaves.find(s => s.item.id === tame.equipped_primary); if (equippedStaff) { @@ -1159,7 +1153,7 @@ async function collectCommand(user: MUser, channelID: string, str: string) { return "Your tame can't kill this monster fast enough."; } - let duration = Math.floor(quantity * speed); + const duration = Math.floor(quantity * speed); await createTameTask({ user, @@ -1233,7 +1227,7 @@ async function monkeyMagicHandler( } let speed = spellOptions.timePerSpell; - let boosts = []; + const boosts = []; const [min] = getTameSpecies(tame).gathererLevelRange; const minBoost = exponentialPercentScale(min, 0.01); const gathererLevelBoost = exponentialPercentScale(tame.max_gatherer_level, 0.01) - minBoost; @@ -1295,7 +1289,7 @@ async function monkeyMagicHandler( }); await updateBankSetting('economyStats_PVMCost', finalCost); - let duration = Math.floor(quantity * speed); + const duration = Math.floor(quantity * speed); await createTameTask({ user, @@ -1395,7 +1389,7 @@ async function spinFlaxCommand(user: MUser, channelID: string) { }); } async function selectCommand(user: MUser, tameID: number) { - const tames = await prisma.tame.findMany({ where: { user_id: user.id } }); + const tames = await user.fetchTames(); const toSelect = tames.find(t => t.id === tameID); if (!toSelect) { return "Couldn't find a tame to select."; @@ -1407,7 +1401,7 @@ async function selectCommand(user: MUser, tameID: number) { await user.update({ selected_tame: toSelect.id }); - return `You selected your ${tameName(toSelect)}.`; + return `You selected your ${toSelect}.`; } async function viewCommand(user: MUser, tameID: number): CommandResponse { @@ -1656,7 +1650,7 @@ async function tameClueCommand(user: MUser, channelID: string, inputName: string return 'Your tame lacks the *knowledge* required to complete elder clues.'; } - let { cost, quantity, duration, boosts, costSavedByDemonicJibwings } = determineTameClueResult({ + const { cost, quantity, duration, boosts, costSavedByDemonicJibwings } = determineTameClueResult({ tameGrowthLevel: tame.growthLevel, clueTier, extraTripLength: patronMaxTripBonus(user) * 2, @@ -1824,7 +1818,7 @@ export const tamesCommand: OSBMahojiCommand = { !value ? true : i.name.toLowerCase().includes(value.toLowerCase()) || - i.aliases.some(alias => stringMatches(alias, value)) + i.aliases.some(alias => stringMatches(alias, value)) ) .map(i => ({ name: i.name, value: i.name })); } diff --git a/src/mahoji/commands/testershop.ts b/src/mahoji/commands/testershop.ts index 153f955679c..f44559921d9 100644 --- a/src/mahoji/commands/testershop.ts +++ b/src/mahoji/commands/testershop.ts @@ -1,10 +1,11 @@ import { Time } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; import { Bank } from 'oldschooljs'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { premiumPatronTime } from '../../lib/premiumPatronTime'; import { roboChimpUserFetch } from '../../lib/roboChimp'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; const shop = [ { diff --git a/src/mahoji/commands/testpotato.ts b/src/mahoji/commands/testpotato.ts index f5df08df584..e43c9ee156c 100644 --- a/src/mahoji/commands/testpotato.ts +++ b/src/mahoji/commands/testpotato.ts @@ -1,16 +1,16 @@ -import { mentionCommand } from '@oldschoolgg/toolkit'; -import { activity_type_enum, Prisma, tame_growth, xp_gains_skill_enum } from '@prisma/client'; -import { User } from 'discord.js'; -import { noOp, Time, uniqueArr } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; +import { type CommandRunOptions, mentionCommand } from '@oldschoolgg/toolkit'; import { Bank, Items } from 'oldschooljs'; import { convertLVLtoXP, itemID, toKMB } from 'oldschooljs/dist/util'; +import { type Prisma, activity_type_enum, tame_growth, xp_gains_skill_enum } from '@prisma/client'; +import { ApplicationCommandOptionType, type User } from 'discord.js'; +import { Time, noOp } from 'e'; import { production } from '../../config'; +import { mahojiUserSettingsUpdate } from '../../lib/MUser'; import { BathhouseOres, BathwaterMixtures } from '../../lib/baxtorianBathhouses'; -import { allStashUnitsFlat, allStashUnitTiers } from '../../lib/clues/stashUnits'; +import { allStashUnitTiers, allStashUnitsFlat } from '../../lib/clues/stashUnits'; import { CombatAchievements } from '../../lib/combat_achievements/combatAchievements'; -import { BitField, BitFieldData, MAX_INT_JAVA, MAX_XP } from '../../lib/constants'; +import { BitField, MAX_INT_JAVA, MAX_XP } from '../../lib/constants'; import { expertCapesCL, gorajanArcherOutfit, @@ -24,19 +24,18 @@ import { leaguesCreatables } from '../../lib/data/creatables/leagueCreatables'; import { Eatables } from '../../lib/data/eatables'; import { TOBMaxMageGear, TOBMaxMeleeGear, TOBMaxRangeGear } from '../../lib/data/tob'; import { dyedItems } from '../../lib/dyedItems'; -import { GearSetupType, GearStat } from '../../lib/gear'; +import { type GearSetupType, GearStat } from '../../lib/gear'; import { materialTypes } from '../../lib/invention'; +import { MaterialBank } from '../../lib/invention/MaterialBank'; import { DisassemblySourceGroups } from '../../lib/invention/groups'; import { Inventions, transactMaterialsFromUser } from '../../lib/invention/inventions'; -import { MaterialBank } from '../../lib/invention/MaterialBank'; import killableMonsters, { effectiveMonsters } from '../../lib/minions/data/killableMonsters'; import { Celestara, Solis } from '../../lib/minions/data/killableMonsters/custom/SunMoon'; import potions from '../../lib/minions/data/potions'; -import { mahojiUserSettingsUpdate } from '../../lib/MUser'; +import { MAX_QP } from '../../lib/minions/data/quests'; import { allOpenables } from '../../lib/openables'; -import { tiers } from '../../lib/patreon'; import { Minigames } from '../../lib/settings/minigames'; -import { prisma } from '../../lib/settings/prisma'; + import { getFarmingInfo } from '../../lib/skilling/functions/getFarmingInfo'; import Skills from '../../lib/skilling/skills'; import Farming from '../../lib/skilling/skills/farming'; @@ -45,12 +44,12 @@ import { slayerMasterChoices } from '../../lib/slayer/constants'; import { slayerMasters } from '../../lib/slayer/slayerMasters'; import { getUsersCurrentSlayerInfo } from '../../lib/slayer/slayerUtil'; import { allSlayerMonsters } from '../../lib/slayer/tasks'; -import { tameFeedableItems, tameSpecies, TameSpeciesID } from '../../lib/tames'; +import { TameSpeciesID, tameFeedableItems, tameSpecies } from '../../lib/tames'; import { stringMatches } from '../../lib/util'; import { calcDropRatesFromBankWithoutUniques } from '../../lib/util/calcDropRatesFromBank'; import { elderRequiredClueCLItems, elderSherlockItems } from '../../lib/util/elderClueRequirements'; import { - FarmingPatchName, + type FarmingPatchName, farmingPatchNames, getFarmingKeyFromName, userGrowingProgressStr @@ -64,43 +63,32 @@ import resolveItems from '../../lib/util/resolveItems'; import { getUsersTame } from '../../lib/util/tameUtil'; import { userEventToStr } from '../../lib/util/userEvents'; import { getPOH } from '../lib/abstracted_commands/pohCommand'; -import { MAX_QP } from '../lib/abstracted_commands/questCommand'; import { allUsableItems } from '../lib/abstracted_commands/useCommand'; import { BingoManager } from '../lib/bingo/BingoManager'; import { gearSetupOption } from '../lib/mahojiCommandOptions'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; import { userStatsUpdate } from '../mahojiSettings'; import { fetchBingosThatUserIsInvolvedIn } from './bingo'; import { generateNewTame } from './nursery'; import { tameEquippables, tameImage } from './tames'; export async function giveMaxStats(user: MUser) { - let updates: Prisma.UserUpdateArgs['data'] = {}; + const updates: Prisma.UserUpdateArgs['data'] = {}; for (const skill of Object.values(xp_gains_skill_enum)) { updates[`skills_${skill}`] = convertLVLtoXP(120); } await user.update({ QP: MAX_QP, + slayer_points: 50_000, + nmz_points: 50_000, + volcanic_mine_points: 500_000, + carpenter_points: 5_000_000, + zeal_tokens: 500_000, + lms_points: 500_000, ...updates }); } -async function givePatronLevel(user: MUser, tier: number) { - const tierToGive = tiers[tiers.length - tier]; - const currentBitfield = user.bitfield; - if (!tier || !tierToGive) { - await user.update({ - bitfield: currentBitfield.filter(i => !tiers.map(t => t[1]).includes(i)) - }); - return 'Removed patron perks.'; - } - const newBitField: BitField[] = [...currentBitfield, tierToGive[1]]; - await user.update({ - bitfield: uniqueArr(newBitField) - }); - return `Gave you ${BitFieldData[tierToGive[1]].name}.`; -} - const gearPresets = [ { name: 'ToB', @@ -195,7 +183,7 @@ for (const o of BathhouseOres) { baxBathBank.add(o.logs.id, 100_000); } for (const m of BathwaterMixtures) { - for (const i of m.items) baxBathBank.add(i.id, 100_000); + for (const i of m.items) baxBathBank.add(i, 100_000); } baxBathBank.add('Coal', 100_000); @@ -825,7 +813,7 @@ export const testPotatoCommand: OSBMahojiCommand | null = production if (options.check) { if (options.check.monster_droprates) { const monster = killableMonsters.find(m => - stringMatches(m.name, options.check!.monster_droprates) + stringMatches(m.name, options.check?.monster_droprates) ); if (!monster) return 'Invalid monster'; const qty = 1_000_000; @@ -857,9 +845,9 @@ ${droprates.join('\n')}`), } if (options.set.all_ca_tasks) { await user.update({ - completed_ca_task_ids: Object.values(CombatAchievements) - .map(i => i.tasks.map(t => t.id)) - .flat() + completed_ca_task_ids: Object.values(CombatAchievements).flatMap(i => + i.tasks.map(t => t.id) + ) }); return 'Finished all CA tasks.'; } @@ -872,7 +860,7 @@ ${droprates.join('\n')}`), return `You now ${!current ? 'ARE' : 'ARE NOT'} an ironman.`; } if (options.wipe) { - let { thing } = options.wipe; + const { thing } = options.wipe; if (thing === 'trips') { await prisma.activity.deleteMany({ where: { @@ -1065,7 +1053,7 @@ ${droprates.join('\n')}`), } }); - let options = { + const options = { GP: 5_000_000_000, slayer_points: 100_000, tentacle_charges: 10_000, @@ -1113,9 +1101,6 @@ ${droprates.join('\n')}`), Spawned an adult of each tame, fed them all applicable items, and spawned ALL their equippable items into your bank (but not equipped).`; } - if (options.patron) { - return givePatronLevel(user, Number(options.patron.tier)); - } if (options.gear) { const gear = gearPresets.find(i => stringMatches(i.name, options.gear!.preset))!; await user.update({ @@ -1190,14 +1175,15 @@ Spawned an adult of each tame, fed them all applicable items, and spawned ALL th stringMatches(m.name, options.setmonsterkc?.monster ?? '') ); if (!monster) return 'Invalid monster'; + const stats = await user.fetchStats({ monster_scores: true }); await userStatsUpdate( user.id, - ({ monster_scores }) => ({ + { monster_scores: { - ...(monster_scores as Record), + ...(stats.monster_scores as Record), [monster.id]: options.setmonsterkc?.kc ?? 1 } - }), + }, {} ); return `Set your ${monster.name} KC to ${options.setmonsterkc.kc ?? 1}.`; @@ -1233,7 +1219,7 @@ Spawned an adult of each tame, fed them all applicable items, and spawned ALL th // Set quantity to 50 if user doesn't assign a quantity const quantity = options.setslayertask?.quantity ?? 50; - const assignedTask = selectedMaster!.tasks.find(m => m.monster.id === selectedMonster?.id)!; + const assignedTask = selectedMaster?.tasks.find(m => m.monster.id === selectedMonster?.id)!; if (!selectedMaster) return 'Invalid slayer master.'; if (!selectedMonster) return 'Invalid monster.'; @@ -1276,4 +1262,4 @@ Spawned an adult of each tame, fed them all applicable items, and spawned ALL th return 'Nothin!'; } - }; + }; diff --git a/src/mahoji/commands/tokkulshop.ts b/src/mahoji/commands/tokkulshop.ts index be1478d6687..a3d853ccb7e 100644 --- a/src/mahoji/commands/tokkulshop.ts +++ b/src/mahoji/commands/tokkulshop.ts @@ -1,17 +1,18 @@ +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; import { activity_type_enum } from '@prisma/client'; +import { ApplicationCommandOptionType } from 'discord.js'; import { Time } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; import { Bank, Monsters } from 'oldschooljs'; import TokkulShopItems from '../../lib/data/buyables/tokkulBuyables'; import { KaramjaDiary, userhasDiaryTier } from '../../lib/diaries'; -import { TokkulShopOptions } from '../../lib/types/minions'; +import type { TokkulShopOptions } from '../../lib/types/minions'; import { formatDuration, stringMatches } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; import { updateBankSetting } from '../../lib/util/updateBankSetting'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; const { TzTokJad } = Monsters; @@ -100,8 +101,8 @@ export const tksCommand: OSBMahojiCommand = { // Get user Karamja Diary completion status, item being bought, jad kc, and ironman status const [hasKaramjaDiary] = await userhasDiaryTier(user, KaramjaDiary.easy); const item = TokkulShopItems.find(i => stringMatches(i.name, options.buy?.name ?? options.sell?.name ?? '')); - const hasKilledJad: boolean = (await user.getKC(TzTokJad.id)) >= 1 ? true : false; - const isIronman = user.user.minion_ironman ? true : false; + const hasKilledJad: boolean = (await user.getKC(TzTokJad.id)) >= 1; + const isIronman = !!user.user.minion_ironman; // If the user is buying an invalid item if (!item) return "That's not a valid item."; @@ -114,7 +115,7 @@ export const tksCommand: OSBMahojiCommand = { // User bank, maxTripLength, quantity given const { bank } = user; const maxTripLength = calcMaxTripLength(user, activity_type_enum.TokkulShop); - let quantity = options.buy?.quantity ?? options.sell?.quantity ?? 1; + const quantity = options.buy?.quantity ?? options.sell?.quantity ?? 1; const cost = new Bank(); const loot = new Bank(); @@ -133,7 +134,7 @@ export const tksCommand: OSBMahojiCommand = { } // Calculate the amount of trips needed to buy the quantity given - let amountOfRestocksNeededToBuy = shopStock ? Math.ceil(quantity / shopStock) : null; + const amountOfRestocksNeededToBuy = shopStock ? Math.ceil(quantity / shopStock) : null; // Calculate trip duration const duration = amountOfRestocksNeededToBuy @@ -156,10 +157,10 @@ export const tksCommand: OSBMahojiCommand = { // If the user doesn't have the items or tokkul if (!bank.has(cost)) return `You don't own ${cost}.`; - const action = Boolean(options.buy) ? 'buy' : 'sell'; + const action = options.buy ? 'buy' : 'sell'; // Calculate the max amount of items the user can buy or sell - let maxCanTransact = shopStock ? (maxTripLength / Time.Minute) * shopStock : (maxTripLength / 1000) * 50; + const maxCanTransact = shopStock ? (maxTripLength / Time.Minute) * shopStock : (maxTripLength / 1000) * 50; // If the duration of the trip is longer than the users max allowed trip, give the reason why and the max they can buy or sell if (duration > maxTripLength) { @@ -169,7 +170,7 @@ export const tksCommand: OSBMahojiCommand = { maxCanTransact ? `The max ${item.name.toLowerCase()}s you can ${ action === 'buy' ? 'buy' : 'sell' - } is ${maxCanTransact}` + } is ${maxCanTransact}` : '' }`; } diff --git a/src/mahoji/commands/tools.ts b/src/mahoji/commands/tools.ts index 31fba7e578a..e7dac3176ca 100644 --- a/src/mahoji/commands/tools.ts +++ b/src/mahoji/commands/tools.ts @@ -1,18 +1,22 @@ -import { Activity, User } from '@prisma/client'; -import { ChannelType, EmbedBuilder, userMention } from 'discord.js'; +import { + type CommandResponse, + type CommandRunOptions, + type MahojiUserOption, + PerkTier, + asyncGzip +} from '@oldschoolgg/toolkit'; +import type { Activity, User } from '@prisma/client'; +import { ApplicationCommandOptionType, ChannelType, EmbedBuilder, userMention } from 'discord.js'; import { Time } from 'e'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; -import { MahojiUserOption } from 'mahoji/dist/lib/types'; import { Bank } from 'oldschooljs'; -import { Item, ItemBank } from 'oldschooljs/dist/meta/types'; +import type { Item, ItemBank } from 'oldschooljs/dist/meta/types'; import { ToBUniqueTable } from 'oldschooljs/dist/simulation/misc/TheatreOfBlood'; - -import { ADMIN_IDS, OWNER_IDS, production } from '../../config'; +import { ADMIN_IDS, OWNER_IDS, production } from '../../config.example'; +import { giveBoxResetTime, mahojiUserSettingsUpdate, spawnLampResetTime } from '../../lib/MUser'; import { MysteryBoxes, spookyTable } from '../../lib/bsoOpenables'; import { ClueTiers } from '../../lib/clues/clueTiers'; import { allStashUnitsFlat } from '../../lib/clues/stashUnits'; -import { BitField, Channel, Emoji, PerkTier } from '../../lib/constants'; +import { BitField, Channel, Emoji } from '../../lib/constants'; import { allCLItems, allDroppedItems } from '../../lib/data/Collections'; import { anglerOutfit, @@ -25,13 +29,12 @@ import { import pets from '../../lib/data/pets'; import { addToDoubleLootTimer } from '../../lib/doubleLoot'; import killableMonsters, { effectiveMonsters, NightmareMonster } from '../../lib/minions/data/killableMonsters'; -import { giveBoxResetTime, isPrimaryPatron, mahojiUserSettingsUpdate, spawnLampResetTime } from '../../lib/MUser'; import { getUsersPerkTier } from '../../lib/perkTiers'; -import { MinigameName, Minigames } from '../../lib/settings/minigames'; -import { convertStoredActivityToFlatActivity, prisma } from '../../lib/settings/prisma'; +import type { MinigameName } from '../../lib/settings/minigames'; +import { Minigames } from '../../lib/settings/minigames'; +import { convertStoredActivityToFlatActivity } from '../../lib/settings/prisma'; import Skills from '../../lib/skilling/skills'; import { - asyncGzip, formatDuration, generateXPLevelQuestion, getUsername, @@ -44,6 +47,7 @@ import { roll, stringMatches } from '../../lib/util'; +import { findGroupOfUser } from '../../lib/util/findGroupOfUser'; import { getItem } from '../../lib/util/getOSItem'; import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; import { deferInteraction } from '../../lib/util/interactionReply'; @@ -51,6 +55,7 @@ import { makeBankImage } from '../../lib/util/makeBankImage'; import { repairBrokenItemsFromUser } from '../../lib/util/repairBrokenItems'; import resolveItems from '../../lib/util/resolveItems'; import { LampTable } from '../../lib/xpLamps'; +import { Cooldowns } from '../lib/Cooldowns'; import { getParsedStashUnits, stashUnitBuildAllCommand, @@ -60,9 +65,8 @@ import { } from '../lib/abstracted_commands/stashUnitsCommand'; import { dataPoints, statsCommand } from '../lib/abstracted_commands/statCommand'; import { buttonUserPicker } from '../lib/buttonUserPicker'; -import { Cooldowns } from '../lib/Cooldowns'; import { itemOption, monsterOption, skillOption } from '../lib/mahojiCommandOptions'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; import { patronMsg } from '../mahojiSettings'; const INTERVAL_DAY = 'day'; @@ -77,9 +81,6 @@ function dateDiff(first: number, second: number) { async function giveBox(mahojiUser: MUser, _recipient: MahojiUserOption) { if (!_recipient) return 'You need to specify a user to give a box to.'; const recipient = await mUserFetch(_recipient.user.id); - if (!isPrimaryPatron(mahojiUser)) { - return 'Shared-perk accounts cannot use this.'; - } const currentDate = Date.now(); const lastDate = Number(mahojiUser.user.lastGivenBoxx); @@ -108,9 +109,7 @@ const whereInMassClause = (id: string) => `OR (group_activity = true AND data::jsonb ? 'users' AND data->>'users'::text LIKE '%${id}%')`; async function activityExport(user: User): CommandResponse { - const allActivities = await prisma.$queryRawUnsafe< - Activity[] - >(`SELECT floor(date_part('epoch', start_date)) AS start_date, floor(date_part('epoch', finish_date)) AS finish_date, duration, type, data + const allActivities = await prisma.$queryRawUnsafe(`SELECT floor(date_part('epoch', start_date)) AS start_date, floor(date_part('epoch', finish_date)) AS finish_date, duration, type, data FROM activity WHERE user_id = '${user.id}' OR (group_activity = true AND data::jsonb ? 'users' AND data->>'users'::text LIKE '%${user.id}%');`); @@ -192,13 +191,13 @@ async function clueGains(interval: string, tier?: string, ironmanOnly?: boolean) const clueTier = ClueTiers.find(t => t.name.toLowerCase() === tier.toLowerCase()); if (!clueTier) return 'Invalid clue scroll tier.'; const tierId = clueTier.id; - tierFilter = `AND (a."data"->>'clueID')::int = ${tierId}`; + tierFilter = `AND (a."data"->>'ci')::int = ${tierId}`; title = `Highest ${clueTier.name} clue scroll completions in the past ${interval}`; } else { title = `Highest All clue scroll completions in the past ${interval}`; } - const query = `SELECT a.user_id::text, SUM((a."data"->>'quantity')::int) AS qty, MAX(a.finish_date) AS lastDate + const query = `SELECT a.user_id::text, SUM((a."data"->>'q')::int) AS qty, MAX(a.finish_date) AS lastDate FROM activity a JOIN users u ON a.user_id::text = u.id WHERE a.type = 'ClueCompletion' @@ -219,9 +218,14 @@ async function clueGains(interval: string, tier?: string, ironmanOnly?: boolean) const embed = new EmbedBuilder() .setTitle(title) .setDescription( - res - .map((i: any) => `${++place}. **${getUsername(i.user_id)}**: ${Number(i.qty).toLocaleString()}`) - .join('\n') + ( + await Promise.all( + res.map( + async (i: any) => + `${++place}. **${await getUsername(i.user_id)}**: ${Number(i.qty).toLocaleString()}` + ) + ) + ).join('\n') ); return { embeds: [embed] }; @@ -264,7 +268,7 @@ async function executeXPGainsQuery( } async function xpGains(interval: string, skill?: string, ironmanOnly?: boolean) { - let intervalValue: string = ''; + let intervalValue = ''; switch (interval.toLowerCase()) { case INTERVAL_DAY: @@ -294,19 +298,21 @@ async function xpGains(interval: string, skill?: string, ironmanOnly?: boolean) const embed = new EmbedBuilder() .setTitle(`Highest ${skillObj ? skillObj.name : 'Overall'} XP Gains in the past ${interval}`) .setDescription( - xpRecords - .map( - record => - `${++place}. **${getUsername(record.user)}**: ${Number(record.total_xp).toLocaleString()} XP` + ( + await Promise.all( + xpRecords.map( + async record => + `${++place}. **${await getUsername(record.user)}**: ${Number(record.total_xp).toLocaleString()} XP` + ) ) - .join('\n') + ).join('\n') ); return { embeds: [embed.data] }; } async function kcGains(interval: string, monsterName: string, ironmanOnly?: boolean): CommandResponse { - let intervalValue: string = ''; + let intervalValue = ''; switch (interval.toLowerCase()) { case INTERVAL_DAY: @@ -330,10 +336,10 @@ async function kcGains(interval: string, monsterName: string, ironmanOnly?: bool } const query = ` - SELECT a.user_id::text, SUM((a."data"->>'quantity')::int) AS qty, MAX(a.finish_date) AS lastDate + SELECT a.user_id::text, SUM((a."data"->>'q')::int) AS qty, MAX(a.finish_date) AS lastDate FROM activity a JOIN users u ON a.user_id::text = u.id - WHERE a.type = 'MonsterKilling' AND (a."data"->>'monsterID')::int = ${monster.id} + WHERE a.type = 'MonsterKilling' AND (a."data"->>'mi')::int = ${monster.id} AND a.finish_date >= now() - interval '1 ${intervalValue}' -- Corrected interval usage AND a.completed = true ${ironmanOnly ? ' AND u."minion.ironman" = true' : ''} @@ -350,9 +356,14 @@ async function kcGains(interval: string, monsterName: string, ironmanOnly?: bool const embed = new EmbedBuilder() .setTitle(`Highest ${monster.name} KC gains in the past ${interval}`) .setDescription( - res - .map((i: any) => `${++place}. **${getUsername(i.user_id)}**: ${Number(i.qty).toLocaleString()}`) - .join('\n') + ( + await Promise.all( + res.map( + async (i: any) => + `${++place}. **${await getUsername(i.user_id)}**: ${Number(i.qty).toLocaleString()}` + ) + ) + ).join('\n') ); return { embeds: [embed.data] }; @@ -363,7 +374,7 @@ export function spawnLampIsReady(user: MUser, channelID: string): [true] | [fals return [false, "You can't use spawnlamp in this channel."]; } - const perkTier = getUsersPerkTier(user, true); + const perkTier = user.perkTier(); const isPatron = perkTier >= PerkTier.Four || user.bitfield.includes(BitField.HasPermanentSpawnLamp); if (!isPatron) { return [false, 'You need to be a T3 patron or higher to use this command.']; @@ -382,11 +393,19 @@ export function spawnLampIsReady(user: MUser, channelID: string): [true] | [fals } async function spawnLampCommand(user: MUser, channelID: string): CommandResponse { const isAdmin = OWNER_IDS.includes(user.id) || ADMIN_IDS.includes(user.id); - const [lampIsReady, reason] = isAdmin ? [true, ''] : spawnLampIsReady(user, channelID.toString()); + const [lampIsReady, reason] = isAdmin ? [true, ''] : spawnLampIsReady(user, channelID); if (!lampIsReady && reason) return reason; - await mahojiUserSettingsUpdate(user.id, { - lastSpawnLamp: Date.now() + const group = await findGroupOfUser(user.id); + await prisma.user.updateMany({ + where: { + id: { + in: group + } + }, + data: { + lastSpawnLamp: Date.now() + } }); const { answers, question, explainAnswer } = generateXPLevelQuestion(); @@ -406,7 +425,7 @@ async function spawnLampCommand(user: MUser, channelID: string): CommandResponse return `${winner} got it, and won **${loot}**! ${explainAnswer}`; } async function spawnBoxCommand(user: MUser, channelID: string): CommandResponse { - const perkTier = getUsersPerkTier(user, true); + const perkTier = user.perkTier(); if (perkTier < PerkTier.Four && !user.bitfield.includes(BitField.HasPermanentEventBackgrounds)) { return 'You need to be a T3 patron or higher to use this command.'; } @@ -434,9 +453,9 @@ async function spawnBoxCommand(user: MUser, channelID: string): CommandResponse return `Congratulations, ${winner}! You received: **${loot}**. ${explainAnswer}`; } -const clueItemsOnlyDroppedInOneTier = ClueTiers.map(i => +const clueItemsOnlyDroppedInOneTier = ClueTiers.flatMap(i => i.table.allItems.filter(itemID => ClueTiers.filter(i => i.table.allItems.includes(itemID)).length === 1) -).flat(); +); interface DrystreakMinigame { name: string; @@ -524,9 +543,7 @@ export const dryStreakEntities: DrystreakEntity[] = [ 'Spooky box' ]), run: async ({ item, ironmanOnly }) => { - const result = await prisma.$queryRawUnsafe< - { id: string; val: number }[] - >(`SELECT user_id::text AS id, COUNT(1) as val + const result = await prisma.$queryRawUnsafe<{ id: string; val: number }[]>(`SELECT user_id::text AS id, COUNT(1) as val FROM activity WHERE user_id IN (SELECT id::bigint FROM users WHERE "collectionLogBank"->'${item.id}' IS NULL ${ironmanOnly ? ' AND "minion.ironman" = TRUE' : ''}) @@ -554,9 +571,7 @@ ORDER BY val DESC LIMIT 10`); 'Olmlet' ]), run: async ({ item, ironmanOnly }) => { - const result = await prisma.$queryRawUnsafe< - { id: string; points: number; raids_total_kc: number }[] - >(`SELECT "users"."id", "user_stats".total_cox_points AS points, "minigames"."raids" + "minigames"."raids_challenge_mode" AS raids_total_kc + const result = await prisma.$queryRawUnsafe<{ id: string; points: number; raids_total_kc: number }[]>(`SELECT "users"."id", "user_stats".total_cox_points AS points, "minigames"."raids" + "minigames"."raids_challenge_mode" AS raids_total_kc FROM user_stats INNER JOIN "users" on "users"."id" = "user_stats"."user_id"::text INNER JOIN "minigames" on "minigames"."user_id" = "user_stats"."user_id"::text @@ -619,9 +634,7 @@ LIMIT 10;`); name: 'Guardians of the Rift', items: guardiansOfTheRiftCL, run: async ({ item, ironmanOnly }) => { - const result = await prisma.$queryRawUnsafe< - { id: string; val: number }[] - >(`SELECT users.id, gotr_rift_searches AS val + const result = await prisma.$queryRawUnsafe<{ id: string; val: number }[]>(`SELECT users.id, gotr_rift_searches AS val FROM users INNER JOIN "user_stats" "userstats" on "userstats"."user_id"::text = "users"."id" WHERE "collectionLogBank"->>'${item.id}' IS NULL @@ -716,9 +729,7 @@ LIMIT 10;`); name: 'Superior Slayer Creatures', items: resolveItems(['Imbued heart', 'Eternal gem']), run: async ({ ironmanOnly, item }) => { - const result = await prisma.$queryRawUnsafe< - { id: string; slayer_superior_count: number }[] - >(`SELECT id, slayer_superior_count + const result = await prisma.$queryRawUnsafe<{ id: string; slayer_superior_count: number }[]>(`SELECT id, slayer_superior_count FROM users INNER JOIN "user_stats" ON "user_stats"."user_id"::text = "users"."id" WHERE "collectionLogBank"->>'${item.id}' IS NULL @@ -769,9 +780,11 @@ async function dryStreakCommand(monsterName: string, itemName: string, ironmanOn if (result.length === 0) return 'No results found.'; if (typeof result === 'string') return result; - return `**Dry Streaks for ${item.name} from ${entity.name}:**\n${result - .map(({ id, val }) => `${getUsername(id)}: ${entity.format(val || -1)}`) - .join('\n')}`; + return `**Dry Streaks for ${item.name} from ${entity.name}:**\n${( + await Promise.all( + result.map(async ({ id, val }) => `${await getUsername(id)}: ${entity.format(val || -1)}`) + ) + ).join('\n')}`; } const mon = effectiveMonsters.find(mon => mon.aliases.some(alias => stringMatches(alias, monsterName))); @@ -791,18 +804,23 @@ async function dryStreakCommand(monsterName: string, itemName: string, ironmanOn ORDER BY ("${key}"->>'${id}')::int DESC LIMIT 10;`; - const result = await prisma.$queryRawUnsafe< - { - id: string; - KC: string; - }[] - >(query); + const result = + await prisma.$queryRawUnsafe< + { + id: string; + KC: string; + }[] + >(query); if (result.length === 0) return 'No results found.'; - return `**Dry Streaks for ${item.name} from ${mon.name}:**\n${result - .map(({ id, KC }) => `${getUsername(id) as string}: ${parseInt(KC).toLocaleString()}`) - .join('\n')}`; + return `**Dry Streaks for ${item.name} from ${mon.name}:**\n${( + await Promise.all( + result.map( + async ({ id, KC }) => `${(await getUsername(id)) as string}: ${Number.parseInt(KC).toLocaleString()}` + ) + ) + ).join('\n')}`; } async function mostDrops(user: MUser, itemName: string, filter: string) { @@ -811,8 +829,8 @@ async function mostDrops(user: MUser, itemName: string, filter: string) { filter === 'Irons Only' ? 'AND "minion.ironman" = true' : filter === 'Mains Only' - ? 'AND "minion.ironman" = false' - : ''; + ? 'AND "minion.ironman" = false' + : ''; if (!item) return "That's not a valid item."; if (!allDroppedItems.includes(item.id) && !user.bitfield.includes(BitField.isModerator)) { return "You can't check this item, because it's not on any collection log."; @@ -820,21 +838,24 @@ async function mostDrops(user: MUser, itemName: string, filter: string) { const query = `SELECT "id", "collectionLogBank"->>'${item.id}' AS "qty" FROM users WHERE "collectionLogBank"->>'${item.id}' IS NOT NULL ${ironmanPart} ORDER BY ("collectionLogBank"->>'${item.id}')::int DESC LIMIT 10;`; - const result = await prisma.$queryRawUnsafe< - { - id: string; - qty: string; - }[] - >(query); + const result = + await prisma.$queryRawUnsafe< + { + id: string; + qty: string; + }[] + >(query); if (result.length === 0) return 'No results found.'; - return `**Most '${item.name}' received:**\n${result - .map( - ({ id, qty }) => - `${result.length < 10 ? '(Anonymous)' : getUsername(id)}: ${parseInt(qty).toLocaleString()}` + return `**Most '${item.name}' received:**\n${( + await Promise.all( + result.map( + async ({ id, qty }) => + `${result.length < 10 ? '(Anonymous)' : await getUsername(id)}: ${Number.parseInt(qty).toLocaleString()}` + ) ) - .join('\n')}`; + ).join('\n')}`; } async function checkMassesCommand(guildID: string | undefined) { @@ -901,9 +922,6 @@ async function patronTriggerDoubleLoot(user: MUser) { if (perkTier < PerkTier.Five) { return 'Only T4, T5 or T6 patrons can use this command.'; } - if (!isPrimaryPatron(user)) { - return 'You can only do this from your primary account.'; - } const lastTime = user.user.last_patron_double_time_trigger; const differenceSinceLastUsage = lastTime ? Date.now() - lastTime.getTime() : null; @@ -914,9 +932,19 @@ async function patronTriggerDoubleLoot(user: MUser) { } const time = calcTime(perkTier); - await mahojiUserSettingsUpdate(user.id, { - last_patron_double_time_trigger: new Date() + + const group = await findGroupOfUser(user.id); + await prisma.user.updateMany({ + where: { + id: { + in: group + } + }, + data: { + last_patron_double_time_trigger: new Date() + } }); + await addToDoubleLootTimer( time, `${userMention(user.id)} used their monthly Tier ${perkTier - 1} double loot time` @@ -1381,7 +1409,7 @@ export const toolsCommand: OSBMahojiCommand = { } if (options.user) { if (options.user.mypets) { - let b = new Bank(); + const b = new Bank(); for (const [pet, qty] of Object.entries(mahojiUser.user.pets as ItemBank)) { const petObj = pets.find(i => i.id === Number(pet)); if (!petObj) continue; @@ -1432,7 +1460,7 @@ export const toolsCommand: OSBMahojiCommand = { return `You can view your temporary CL using, for example, \`/cl name:PvM type:Temp\`. You last reset your temporary CL: ${ - Boolean(lastReset?.last_temp_cl_reset) + lastReset?.last_temp_cl_reset ? `` : 'Never' }`; diff --git a/src/mahoji/commands/trade.ts b/src/mahoji/commands/trade.ts index ff2f5319917..3a958d2e9ab 100644 --- a/src/mahoji/commands/trade.ts +++ b/src/mahoji/commands/trade.ts @@ -1,18 +1,19 @@ import { discrimName, mentionCommand, truncateString } from '@oldschoolgg/toolkit'; -import { ApplicationCommandOptionType, CommandRunOptions } from 'mahoji'; -import { MahojiUserOption } from 'mahoji/dist/lib/types'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import type { MahojiUserOption } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; import { Bank } from 'oldschooljs'; import { BLACKLISTED_USERS } from '../../lib/blacklists'; import { Events } from '../../lib/constants'; -import { prisma } from '../../lib/settings/prisma'; + import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; import { deferInteraction } from '../../lib/util/interactionReply'; import itemIsTradeable from '../../lib/util/itemIsTradeable'; import { parseBank } from '../../lib/util/parseStringBank'; import { tradePlayerItems } from '../../lib/util/tradePlayerItems'; import { filterOption } from '../lib/mahojiCommandOptions'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; import { addToGPTaxBalance, mahojiParseNumber } from '../mahojiSettings'; export const tradeCommand: OSBMahojiCommand = { @@ -92,7 +93,7 @@ export const tradeCommand: OSBMahojiCommand = { filters: [options.filter], search: options.search, noDuplicateItems: true - }).filter(i => itemIsTradeable(i.id, true)); + }).filter(i => itemIsTradeable(i.id, true)); const itemsReceived = parseBank({ inputStr: options.receive, maxSize: 70, diff --git a/src/mahoji/commands/trivia.ts b/src/mahoji/commands/trivia.ts index a1ed5a8cbbd..59735cb4e99 100644 --- a/src/mahoji/commands/trivia.ts +++ b/src/mahoji/commands/trivia.ts @@ -1,12 +1,13 @@ -import { TextChannel, userMention } from 'discord.js'; +import type { TextChannel } from 'discord.js'; +import { userMention } from 'discord.js'; +import { ApplicationCommandOptionType } from 'discord.js'; import { shuffleArr, uniqueArr } from 'e'; -import { ApplicationCommandOptionType } from 'mahoji'; -import { CommandRunOptions, MahojiUserOption } from 'mahoji/dist/lib/types'; +import type { CommandRunOptions, MahojiUserOption } from '@oldschoolgg/toolkit'; import { DynamicButtons } from '../../lib/DynamicButtons'; import { getRandomTriviaQuestions } from '../../lib/roboChimp'; import { deferInteraction } from '../../lib/util/interactionReply'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const triviaCommand: OSBMahojiCommand = { name: 'trivia', @@ -46,7 +47,7 @@ export const triviaCommand: OSBMahojiCommand = { buttons.add({ name: q, fn: ({ interaction }) => { - if (question.answers.includes(q) ? true : false) { + if (question.answers.includes(q)) { correctUser = interaction.user.id; } }, diff --git a/src/mahoji/commands/use.ts b/src/mahoji/commands/use.ts index 0d159a4aa3a..a7a573d0351 100644 --- a/src/mahoji/commands/use.ts +++ b/src/mahoji/commands/use.ts @@ -1,8 +1,8 @@ -import { CommandRunOptions } from 'mahoji'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; import { allUsableItems, useCommand } from '../lib/abstracted_commands/useCommand'; import { ownedItemOption } from '../lib/mahojiCommandOptions'; -import { OSBMahojiCommand } from '../lib/util'; +import type { OSBMahojiCommand } from '../lib/util'; export const mahojiUseCommand: OSBMahojiCommand = { name: 'use', diff --git a/src/mahoji/guildSettings.ts b/src/mahoji/guildSettings.ts index 8b7bbc327f1..4ff7301811a 100644 --- a/src/mahoji/guildSettings.ts +++ b/src/mahoji/guildSettings.ts @@ -1,13 +1,8 @@ -import { Guild, Prisma } from '@prisma/client'; -import { Guild as DJSGuild } from 'discord.js'; -import LRUCache from 'lru-cache'; +import type { Guild, Prisma } from '@prisma/client'; +import type { Guild as DJSGuild } from 'discord.js'; +import { LRUCache } from 'lru-cache'; -import { prisma } from '../lib/settings/prisma'; - -type CachedGuild = Pick< - Guild, - 'disabledCommands' | 'id' | 'tweetchannel' | 'jmodComments' | 'petchannel' | 'staffOnlyChannels' ->; +type CachedGuild = Pick; export const untrustedGuildSettingsCache = new LRUCache({ max: 1000 }); export async function mahojiGuildSettingsFetch(guild: string | DJSGuild) { @@ -23,8 +18,6 @@ export async function mahojiGuildSettingsFetch(guild: string | DJSGuild) { select: { disabledCommands: true, id: true, - tweetchannel: true, - jmodComments: true, petchannel: true, staffOnlyChannels: true } diff --git a/src/mahoji/lib/Cooldowns.ts b/src/mahoji/lib/Cooldowns.ts index 5396205313c..8618c0a177b 100644 --- a/src/mahoji/lib/Cooldowns.ts +++ b/src/mahoji/lib/Cooldowns.ts @@ -1,4 +1,4 @@ -import LRUCache from 'lru-cache'; +import { LRUCache } from 'lru-cache'; import { assert } from '../../lib/util'; diff --git a/src/mahoji/lib/abstracted_commands/achievementDiaryCommand.ts b/src/mahoji/lib/abstracted_commands/achievementDiaryCommand.ts index 7e7e38a1fa1..8ed056367e7 100644 --- a/src/mahoji/lib/abstracted_commands/achievementDiaryCommand.ts +++ b/src/mahoji/lib/abstracted_commands/achievementDiaryCommand.ts @@ -3,8 +3,10 @@ import { strikethrough } from 'discord.js'; import { calcWhatPercent } from 'e'; import { Bank, Monsters } from 'oldschooljs'; -import { diaries, DiaryTier, userhasDiaryTier, userhasDiaryTierSync } from '../../../lib/diaries'; -import { Minigames, MinigameScore } from '../../../lib/settings/minigames'; +import type { Minigame } from '@prisma/client'; +import { diaries, userhasDiaryTier, userhasDiaryTierSync } from '../../../lib/diaries'; +import type { DiaryTier } from '../../../lib/minions/types'; +import { Minigames } from '../../../lib/settings/minigames'; import { MUserStats } from '../../../lib/structures/MUserStats'; import { formatSkillRequirements, itemNameFromID, stringMatches } from '../../../lib/util'; @@ -151,19 +153,15 @@ export async function claimAchievementDiaryCommand(user: MUser, diaryName: strin return `You have already completed the entire ${diary.name} diary!`; } -export async function calculateAchievementDiaryProgress( - user: MUser, - stats: MUserStats, - minigameScores: MinigameScore[] -) { +export function calculateAchievementDiaryProgress(user: MUser, stats: MUserStats, minigameScores: Minigame) { let totalDiaries = 0; let totalCompleted = 0; for (const diaryLocation of diaries) { for (const diaryTier of [diaryLocation.easy, diaryLocation.medium, diaryLocation.hard, diaryLocation.elite]) { - const has = userhasDiaryTierSync(user, diaryTier, { stats, minigameScores })[0]; + const { hasDiary } = userhasDiaryTierSync(user, diaryTier, { stats, minigameScores }); totalDiaries++; - if (has) { + if (hasDiary) { totalCompleted++; } } diff --git a/src/mahoji/lib/abstracted_commands/aerialFishingCommand.ts b/src/mahoji/lib/abstracted_commands/aerialFishingCommand.ts index 1b4d82bdc36..b21649e697b 100644 --- a/src/mahoji/lib/abstracted_commands/aerialFishingCommand.ts +++ b/src/mahoji/lib/abstracted_commands/aerialFishingCommand.ts @@ -1,14 +1,14 @@ import { Time } from 'e'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; +import type { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; import { formatDuration, randomVariation } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; export async function aerialFishingCommand(user: MUser, channelID: string) { if (user.skillLevel(SkillsEnum.Fishing) < 43 || user.skillLevel(SkillsEnum.Hunter) < 35) { - return 'You need atleast level 35 Hunter and 43 Fishing to do Aerial fishing.'; + return 'You need at least level 35 Hunter and 43 Fishing to do Aerial fishing.'; } const timePerFish = randomVariation(2, 7.5) * Time.Second; diff --git a/src/mahoji/lib/abstracted_commands/agilityArenaCommand.ts b/src/mahoji/lib/abstracted_commands/agilityArenaCommand.ts index 548a1cbd6b9..5a9d20c3c70 100644 --- a/src/mahoji/lib/abstracted_commands/agilityArenaCommand.ts +++ b/src/mahoji/lib/abstracted_commands/agilityArenaCommand.ts @@ -1,4 +1,4 @@ -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; +import type { CommandResponse } from '@oldschoolgg/toolkit'; import { Bank } from 'oldschooljs'; import { KaramjaDiary, userhasDiaryTier } from '../../../lib/diaries'; @@ -137,7 +137,7 @@ export async function agilityArenaBuyCommand(user: MUser, input: string, qty = 1 export async function agilityArenaRecolorCommand(user: MUser) { const { bank } = user; - let ticketCost = 250; + const ticketCost = 250; if (!bank.has(plainGraceful)) { return mahojiChatHead({ content: "Ye don't have a full set of Graceful in your bank for me to recolor!", @@ -180,9 +180,7 @@ export async function agilityArenaXPCommand(user: MUser, qty: number): CommandRe } const [hasKaramjaMed] = await userhasDiaryTier(user, KaramjaDiary.medium); const xpToGive = determineXPFromTickets(qty, user, hasKaramjaMed); - let str = `Redeemed ${qty}x Agility arena tickets for ${xpToGive.toLocaleString()} Agility XP. (${( - xpToGive / qty - ).toFixed(2)} ea)`; + let str = `Redeemed ${qty}x Agility arena tickets for ${xpToGive.toLocaleString()} Agility XP. (${(xpToGive / qty).toFixed(2)} ea)`; await transactItems({ userID: user.id, itemsToRemove: new Bank().add('Agility arena ticket', qty) }); await user.addXP({ skillName: SkillsEnum.Agility, diff --git a/src/mahoji/lib/abstracted_commands/alchCommand.ts b/src/mahoji/lib/abstracted_commands/alchCommand.ts index 963e1dcb3e0..0ef0c7f8592 100644 --- a/src/mahoji/lib/abstracted_commands/alchCommand.ts +++ b/src/mahoji/lib/abstracted_commands/alchCommand.ts @@ -1,16 +1,16 @@ -import { ChatInputCommandInteraction } from 'discord.js'; -import { clamp, Time } from 'e'; +import type { ChatInputCommandInteraction } from 'discord.js'; +import { Time, clamp } from 'e'; import { Bank } from 'oldschooljs'; import { SkillsEnum } from 'oldschooljs/dist/constants'; -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; +import { resolveItems } from 'oldschooljs/dist/util/util'; import type { AlchingActivityTaskOptions } from '../../../lib/types/minions'; import { formatDuration, toKMB } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; import { getItem } from '../../../lib/util/getOSItem'; import { handleMahojiConfirmation } from '../../../lib/util/handleMahojiConfirmation'; -import resolveItems from '../../../lib/util/resolveItems'; import { updateBankSetting } from '../../../lib/util/updateBankSetting'; const unlimitedFireRuneProviders = resolveItems([ @@ -78,7 +78,7 @@ export async function alchCommand( ...(fireRuneCost > 0 ? { 'Fire rune': fireRuneCost } : {}), 'Nature rune': quantity }); - let speed = speedInput ? clamp(speedInput, 1, 5) : null; + const speed = speedInput ? clamp(speedInput, 1, 5) : null; if (speed && speed > 1 && speed < 6) { consumedItems.multiply(speed); consumedItems.add('Nature rune', Math.floor(consumedItems.amount('Nature rune') * 0.5)); diff --git a/src/mahoji/lib/abstracted_commands/autoSlayCommand.ts b/src/mahoji/lib/abstracted_commands/autoSlayCommand.ts index 2d81c2e2cbe..dadfb684e1d 100644 --- a/src/mahoji/lib/abstracted_commands/autoSlayCommand.ts +++ b/src/mahoji/lib/abstracted_commands/autoSlayCommand.ts @@ -1,13 +1,13 @@ import { isGuildChannel } from '@oldschoolgg/toolkit'; -import { ChatInputCommandInteraction } from 'discord.js'; -import { CommandOptions } from 'mahoji/dist/lib/types'; +import type { CommandOptions } from '@oldschoolgg/toolkit'; +import type { ChatInputCommandInteraction } from 'discord.js'; import { Monsters } from 'oldschooljs'; -import { PvMMethod } from '../../../lib/constants'; +import type { PvMMethod } from '../../../lib/constants'; import killableMonsters from '../../../lib/minions/data/killableMonsters'; import { runCommand } from '../../../lib/settings/settings'; -import { autoslayModes, AutoslayOptionsEnum } from '../../../lib/slayer/constants'; -import { getCommonTaskName, getUsersCurrentSlayerInfo, SlayerMasterEnum } from '../../../lib/slayer/slayerUtil'; +import { AutoslayOptionsEnum, autoslayModes } from '../../../lib/slayer/constants'; +import { SlayerMasterEnum, getCommonTaskName, getUsersCurrentSlayerInfo } from '../../../lib/slayer/slayerUtil'; import { hasSkillReqs, stringMatches } from '../../../lib/util'; import { interactionReply } from '../../../lib/util/interactionReply'; import { slayerNewTaskCommand } from './slayerTaskCommand'; @@ -17,7 +17,7 @@ interface AutoslayLink { // Name and Monster must be specified if either is. efficientName?: string; efficientMonster?: number; - efficientMethod?: PvMMethod; + efficientMethod?: PvMMethod | PvMMethod[]; slayerMasters?: SlayerMasterEnum[]; } @@ -147,7 +147,7 @@ const AutoSlayMaxEfficiencyTable: AutoslayLink[] = [ monsterID: Monsters.SmokeDevil.id, efficientName: Monsters.SmokeDevil.name, efficientMonster: Monsters.SmokeDevil.id, - efficientMethod: 'barrage' + efficientMethod: ['barrage', 'cannon'] }, { monsterID: Monsters.DarkBeast.id, @@ -202,9 +202,173 @@ const AutoSlayMaxEfficiencyTable: AutoslayLink[] = [ efficientName: Monsters.Lizardman.name, efficientMonster: Monsters.Lizardman.id, efficientMethod: 'cannon' + }, + { + monsterID: Monsters.RevenantImp.id, + efficientName: Monsters.RevenantDemon.name, + efficientMonster: Monsters.RevenantDemon.id, + efficientMethod: 'none' } ]; +const WildyAutoSlayMaxEfficiencyTable: AutoslayLink[] = [ + { + monsterID: Monsters.AbyssalDemon.id, + efficientName: Monsters.AbyssalDemon.name, + efficientMonster: Monsters.AbyssalDemon.id, + efficientMethod: ['barrage', 'cannon'] + }, + { + monsterID: Monsters.Ankou.id, + efficientName: Monsters.Ankou.name, + efficientMonster: Monsters.Ankou.id, + efficientMethod: ['barrage', 'cannon'] + }, + { + monsterID: Monsters.BlackDemon.id, + efficientName: Monsters.BlackDemon.name, + efficientMonster: Monsters.BlackDemon.id, + efficientMethod: 'cannon' + }, + { + monsterID: Monsters.BlackKnight.id, + efficientName: Monsters.BlackKnight.name, + efficientMonster: Monsters.BlackKnight.id, + efficientMethod: 'cannon' + }, + { + monsterID: Monsters.Bloodveld.id, + efficientName: Monsters.Bloodveld.name, + efficientMonster: Monsters.Bloodveld.id, + efficientMethod: 'none' + }, + { + monsterID: Monsters.ChaosDruid.id, + efficientName: Monsters.ChaosDruid.name, + efficientMonster: Monsters.ChaosDruid.id, + efficientMethod: 'cannon' + }, + { + monsterID: Monsters.DarkWarrior.id, + efficientName: Monsters.DarkWarrior.name, + efficientMonster: Monsters.DarkWarrior.id, + efficientMethod: 'cannon' + }, + { + monsterID: Monsters.DeadlyRedSpider.id, + efficientName: Monsters.DeadlyRedSpider.name, + efficientMonster: Monsters.DeadlyRedSpider.id, + efficientMethod: 'cannon' + }, + { + monsterID: Monsters.DustDevil.id, + efficientName: Monsters.DustDevil.name, + efficientMonster: Monsters.DustDevil.id, + efficientMethod: ['barrage', 'cannon'] + }, + { + monsterID: Monsters.ElderChaosDruid.id, + efficientName: Monsters.ElderChaosDruid.name, + efficientMonster: Monsters.ElderChaosDruid.id, + efficientMethod: 'cannon' + }, + { + monsterID: Monsters.Ent.id, + efficientName: Monsters.Ent.name, + efficientMonster: Monsters.Ent.id, + efficientMethod: 'cannon' + }, + { + monsterID: Monsters.GreaterDemon.id, + efficientName: Monsters.GreaterDemon.name, + efficientMonster: Monsters.GreaterDemon.id, + efficientMethod: 'cannon' + }, + { + monsterID: Monsters.GreenDragon.id, + efficientName: Monsters.GreenDragon.name, + efficientMonster: Monsters.GreenDragon.id, + efficientMethod: 'cannon' + }, + { + monsterID: Monsters.GuardBandit.id, + efficientName: Monsters.GuardBandit.name, + efficientMonster: Monsters.GuardBandit.id, + efficientMethod: 'cannon' + }, + { + monsterID: Monsters.Hellhound.id, + efficientName: Monsters.Hellhound.name, + efficientMonster: Monsters.Hellhound.id, + efficientMethod: 'cannon' + }, + { + monsterID: Monsters.IceGiant.id, + efficientName: Monsters.IceGiant.name, + efficientMonster: Monsters.IceGiant.id, + efficientMethod: 'cannon' + }, + { + monsterID: Monsters.IceWarrior.id, + efficientName: Monsters.IceWarrior.name, + efficientMonster: Monsters.IceWarrior.id, + efficientMethod: 'cannon' + }, + { + monsterID: Monsters.Jelly.id, + efficientName: Monsters.Jelly.name, + efficientMonster: Monsters.Jelly.id, + efficientMethod: ['barrage', 'cannon'] + }, + { + monsterID: Monsters.LesserDemon.id, + efficientName: Monsters.LesserDemon.name, + efficientMonster: Monsters.LesserDemon.id, + efficientMethod: 'cannon' + }, + { + monsterID: Monsters.MagicAxe.id, + efficientName: Monsters.MagicAxe.name, + efficientMonster: Monsters.MagicAxe.id, + efficientMethod: 'cannon' + }, + { + monsterID: Monsters.Mammoth.id, + efficientName: Monsters.Mammoth.name, + efficientMonster: Monsters.Mammoth.id, + efficientMethod: 'cannon' + }, + { + monsterID: Monsters.MossGiant.id, + efficientName: Monsters.MossGiant.name, + efficientMonster: Monsters.MossGiant.id, + efficientMethod: 'cannon' + }, + { + monsterID: Monsters.GreaterNechryael.id, + efficientName: Monsters.GreaterNechryael.name, + efficientMonster: Monsters.GreaterNechryael.id, + efficientMethod: ['barrage', 'cannon'] + }, + { + monsterID: Monsters.RevenantImp.id, + efficientName: Monsters.RevenantDemon.name, + efficientMonster: Monsters.RevenantDemon.id, + efficientMethod: 'none' + }, + { + monsterID: Monsters.Scorpion.id, + efficientName: Monsters.Scorpion.name, + efficientMonster: Monsters.Scorpion.id, + efficientMethod: 'cannon' + }, + { + monsterID: Monsters.Spider.id, + efficientName: Monsters.Spider.name, + efficientMonster: Monsters.Spider.id, + efficientMethod: 'cannon' + } +]; function determineAutoslayMethod(autoslayOptions: AutoslayOptionsEnum[]) { let method = 'default'; if (autoslayOptions.includes(AutoslayOptionsEnum.MaxEfficiency)) { @@ -264,7 +428,10 @@ export async function autoSlayCommand({ let currentLow = Number.POSITIVE_INFINITY; let currentMonID: number | null = null; - for (const monsterID of usersTask.assignedTask!.monsters) { + if (!usersTask.assignedTask) { + throw new Error('User had no assignedTask?'); + } + for (const monsterID of usersTask.assignedTask.monsters) { const osjsM = Monsters.get(monsterID); if (osjsM && osjsM.data.combatLevel < currentLow) { currentLow = osjsM.data.combatLevel; @@ -285,11 +452,19 @@ export async function autoSlayCommand({ return; } if (method === 'ehp') { - const ehpMonster = AutoSlayMaxEfficiencyTable.find(e => { + let ehpMonster = AutoSlayMaxEfficiencyTable.find(e => { const masterMatch = !e.slayerMasters || e.slayerMasters.includes(usersTask.currentTask!.slayer_master_id); return masterMatch && e.monsterID === usersTask.assignedTask!.monster.id; }); + if (usersTask.currentTask.slayer_master_id === 8) { + ehpMonster = WildyAutoSlayMaxEfficiencyTable.find(e => { + const masterMatch = + !e.slayerMasters || e.slayerMasters.includes(usersTask.currentTask!.slayer_master_id); + return masterMatch && e.monsterID === usersTask.assignedTask!.monster.id; + }); + } + const ehpKillable = killableMonsters.find(m => m.id === ehpMonster?.efficientMonster); // If we don't have the requirements for the efficient monster, revert to default monster @@ -297,7 +472,7 @@ export async function autoSlayCommand({ runCommand({ commandName: 'k', args: { - name: usersTask.assignedTask!.monster.name + name: usersTask.assignedTask?.monster.name }, bypassInhibitors: true, ...cmdRunOptions @@ -305,12 +480,12 @@ export async function autoSlayCommand({ return; } - if (ehpMonster && ehpMonster.efficientName) { - let args: CommandOptions = { + if (ehpMonster?.efficientName) { + const args: CommandOptions = { name: ehpMonster.efficientName }; if (ehpMonster.efficientMethod) { - args.method = ehpMonster.efficientMethod; + args.method = ehpMonster.efficientMethod as unknown as CommandOptions; } runCommand({ commandName: 'k', @@ -323,7 +498,7 @@ export async function autoSlayCommand({ runCommand({ commandName: 'k', args: { - name: usersTask.assignedTask!.monster.name + name: usersTask.assignedTask?.monster.name }, bypassInhibitors: true, ...cmdRunOptions @@ -333,7 +508,7 @@ export async function autoSlayCommand({ if (method === 'boss') { // This code handles the 'highest/boss' setting of autoslay. const myQPs = await user.QP; - let commonName = getCommonTaskName(usersTask.assignedTask!.monster); + const commonName = getCommonTaskName(usersTask.assignedTask!.monster); if (commonName === 'TzHaar') { runCommand({ commandName: 'activities', @@ -345,7 +520,7 @@ export async function autoSlayCommand({ } const allMonsters = killableMonsters.filter(m => { - return usersTask.assignedTask!.monsters.includes(m.id); + return usersTask.assignedTask?.monsters.includes(m.id); }); if (allMonsters.length === 0) return 'Please report this error. No monster variations found.'; let maxDiff = 0; @@ -380,7 +555,7 @@ export async function autoSlayCommand({ } await runCommand({ commandName: 'k', - args: { name: usersTask.assignedTask!.monster.name }, + args: { name: usersTask.assignedTask?.monster.name }, bypassInhibitors: true, ...cmdRunOptions }); diff --git a/src/mahoji/lib/abstracted_commands/bankBgCommand.ts b/src/mahoji/lib/abstracted_commands/bankBgCommand.ts index d29b9869268..15540ea3460 100644 --- a/src/mahoji/lib/abstracted_commands/bankBgCommand.ts +++ b/src/mahoji/lib/abstracted_commands/bankBgCommand.ts @@ -1,10 +1,11 @@ -import { ChatInputCommandInteraction } from 'discord.js'; +import type { ChatInputCommandInteraction } from 'discord.js'; import { Bank } from 'oldschooljs'; +import { resolveItems } from 'oldschooljs/dist/util/util'; import { BitField } from '../../../lib/constants'; -import { formatSkillRequirements, getAllIDsOfUser, stringMatches, toKMB } from '../../../lib/util'; +import { formatSkillRequirements, stringMatches, toKMB } from '../../../lib/util'; +import { findGroupOfUser } from '../../../lib/util/findGroupOfUser'; import { handleMahojiConfirmation } from '../../../lib/util/handleMahojiConfirmation'; -import resolveItems from '../../../lib/util/resolveItems'; import { updateBankSetting } from '../../../lib/util/updateBankSetting'; export async function bankBgCommand(interaction: ChatInputCommandInteraction, user: MUser, name: string) { @@ -20,7 +21,7 @@ export async function bankBgCommand(interaction: ChatInputCommandInteraction, us } const owners = selectedImage.owners ?? []; - let allAccounts = getAllIDsOfUser(user); + const allAccounts = await findGroupOfUser(user.id); if (user.bitfield.includes(BitField.isModerator) || allAccounts.some(a => owners.includes(a))) { await user.update({ bankBackground: selectedImage.id @@ -38,7 +39,7 @@ export async function bankBgCommand(interaction: ChatInputCommandInteraction, us if (selectedImage.sacValueRequired) { const sac = Number(user.user.sacrificedValue); if (sac < selectedImage.sacValueRequired) { - return `You have to have sacrificed atleast ${toKMB( + return `You have to have sacrificed at least ${toKMB( selectedImage.sacValueRequired )} GP worth of items to use this background.`; } @@ -86,7 +87,7 @@ export async function bankBgCommand(interaction: ChatInputCommandInteraction, us /** * If this bank image has a gp or item cost, confirm and charge. */ - let economyCost = new Bank(); + const economyCost = new Bank(); if (selectedImage.gpCost || selectedImage.itemCost) { const userBank = user.bank; diff --git a/src/mahoji/lib/abstracted_commands/barbAssault.ts b/src/mahoji/lib/abstracted_commands/barbAssault.ts index 2efd744b84c..bc8971853de 100644 --- a/src/mahoji/lib/abstracted_commands/barbAssault.ts +++ b/src/mahoji/lib/abstracted_commands/barbAssault.ts @@ -1,6 +1,6 @@ -import { ButtonBuilder, ChatInputCommandInteraction } from 'discord.js'; -import { calcWhatPercent, clamp, reduceNumByPercent, roll, round, Time } from 'e'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; +import type { CommandResponse } from '@oldschoolgg/toolkit'; +import type { ButtonBuilder, ChatInputCommandInteraction } from 'discord.js'; +import { Time, calcWhatPercent, clamp, reduceNumByPercent, round } from 'e'; import { Bank } from 'oldschooljs'; import { buildClueButtons } from '../../../lib/clues/clueUtils'; @@ -9,7 +9,7 @@ import { getMinigameScore } from '../../../lib/settings/settings'; import { HighGambleTable, LowGambleTable, MediumGambleTable } from '../../../lib/simulation/baGamble'; import { maxOtherStats } from '../../../lib/structures/Gear'; import type { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; -import { formatDuration, makeComponents, randomVariation, stringMatches } from '../../../lib/util'; +import { formatDuration, makeComponents, randomVariation, roll, stringMatches } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; import getOSItem from '../../../lib/util/getOSItem'; @@ -140,15 +140,11 @@ export async function barbAssaultBuyCommand( const stats = await user.fetchStats({ honour_points: true }); const balance = stats.honour_points; if (balance < cost * quantity) { - return `You don't have enough Honour Points to buy ${quantity.toLocaleString()}x ${item.name}. You need ${( - cost * quantity - ).toLocaleString()}, but you have only ${balance.toLocaleString()}.`; + return `You don't have enough Honour Points to buy ${quantity.toLocaleString()}x ${item.name}. You need ${(cost * quantity).toLocaleString()}, but you have only ${balance.toLocaleString()}.`; } await handleMahojiConfirmation( interaction, - `Are you sure you want to buy ${quantity.toLocaleString()}x ${item.name}, for ${( - cost * quantity - ).toLocaleString()} honour points?` + `Are you sure you want to buy ${quantity.toLocaleString()}x ${item.name}, for ${(cost * quantity).toLocaleString()} honour points?` ); await userStatsUpdate( user.id, @@ -162,16 +158,14 @@ export async function barbAssaultBuyCommand( await user.addItemsToBank({ items: new Bank().add(item.id, quantity), collectionLog: true }); - return `Successfully purchased ${quantity.toLocaleString()}x ${item.name} for ${( - cost * quantity - ).toLocaleString()} Honour Points.`; + return `Successfully purchased ${quantity.toLocaleString()}x ${item.name} for ${(cost * quantity).toLocaleString()} Honour Points.`; } export async function barbAssaultGambleCommand( interaction: ChatInputCommandInteraction, user: MUser, tier: string, - quantity: number + quantity = 1 ) { const buyable = GambleTiers.find(i => stringMatches(tier, i.name)); if (!buyable) { @@ -180,15 +174,11 @@ export async function barbAssaultGambleCommand( const { honour_points: balance } = await user.fetchStats({ honour_points: true }); const { cost, name, table } = buyable; if (balance < cost * quantity) { - return `You don't have enough Honour Points to do ${quantity.toLocaleString()}x ${name} gamble. You need ${( - cost * quantity - ).toLocaleString()}, but you have only ${balance.toLocaleString()}.`; + return `You don't have enough Honour Points to do ${quantity.toLocaleString()}x ${name} gamble. You need ${(cost * quantity).toLocaleString()}, but you have only ${balance.toLocaleString()}.`; } await handleMahojiConfirmation( interaction, - `Are you sure you want to do ${quantity.toLocaleString()}x ${name} gamble, using ${( - cost * quantity - ).toLocaleString()} honour points?` + `Are you sure you want to do ${quantity.toLocaleString()}x ${name} gamble, using ${(cost * quantity).toLocaleString()} honour points?` ); await userStatsUpdate( user.id, @@ -200,7 +190,7 @@ export async function barbAssaultGambleCommand( name === 'High' ? { increment: quantity - } + } : undefined }, { @@ -214,10 +204,8 @@ export async function barbAssaultGambleCommand( const perkTier = user.perkTier(); const components: ButtonBuilder[] = buildClueButtons(loot, perkTier, user); - let response: Awaited = { - content: `You spent ${( - cost * quantity - ).toLocaleString()} Honour Points for ${quantity.toLocaleString()}x ${name} Gamble, and received...`, + const response: Awaited = { + content: `You spent ${(cost * quantity).toLocaleString()} Honour Points for ${quantity.toLocaleString()}x ${name} Gamble, and received...`, files: [ ( await makeBankImage({ diff --git a/src/mahoji/lib/abstracted_commands/birdhousesCommand.ts b/src/mahoji/lib/abstracted_commands/birdhousesCommand.ts index a911a35dbdf..172dfaaedc2 100644 --- a/src/mahoji/lib/abstracted_commands/birdhousesCommand.ts +++ b/src/mahoji/lib/abstracted_commands/birdhousesCommand.ts @@ -1,11 +1,11 @@ -import { User } from '@prisma/client'; import { time } from 'discord.js'; import { Bank } from 'oldschooljs'; -import birdhouses, { Birdhouse, birdhouseSeeds } from '../../../lib/skilling/skills/hunter/birdHouseTrapping'; -import defaultBirdhouseTrap, { BirdhouseData } from '../../../lib/skilling/skills/hunter/defaultBirdHouseTrap'; -import { BirdhouseActivityTaskOptions } from '../../../lib/types/minions'; -import { birdhouseLimit, formatDuration, stringMatches } from '../../../lib/util'; +import { formatDuration, stringMatches } from '@oldschoolgg/toolkit'; +import birdhouses, { type Birdhouse, birdhouseSeeds } from '../../../lib/skilling/skills/hunter/birdHouseTrapping'; +import defaultBirdhouseTrap, { type BirdhouseData } from '../../../lib/skilling/skills/hunter/defaultBirdHouseTrap'; +import type { BirdhouseActivityTaskOptions } from '../../../lib/types/minions'; +import { birdhouseLimit } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { updateBankSetting } from '../../../lib/util/updateBankSetting'; import { mahojiUsersSettingsFetch, userHasGracefulEquipped } from '../../mahojiSettings'; @@ -18,11 +18,9 @@ interface BirdhouseDetails { readyAt: Date | null; } -export async function calculateBirdhouseDetails(userID: string | bigint): Promise { - const bh = await mahojiUsersSettingsFetch(userID, { - minion_birdhouseTraps: true - }); - if (!bh.minion_birdhouseTraps) { +export function calculateBirdhouseDetails(user: MUser): BirdhouseDetails { + const birdHouseTraps = user.user.minion_birdhouseTraps; + if (!birdHouseTraps) { return { raw: defaultBirdhouseTrap, isReady: false, @@ -32,7 +30,7 @@ export async function calculateBirdhouseDetails(userID: string | bigint): Promis }; } - const details = bh.minion_birdhouseTraps as unknown as BirdhouseData; + const details = birdHouseTraps as unknown as BirdhouseData; const birdHouse = details.lastPlaced ? birdhouses.find(_birdhouse => _birdhouse.name === details.lastPlaced) : null; if (!birdHouse) throw new Error(`Missing ${details.lastPlaced} birdhouse`); @@ -50,8 +48,8 @@ export async function calculateBirdhouseDetails(userID: string | bigint): Promis }; } -export async function birdhouseCheckCommand(user: User) { - const details = await calculateBirdhouseDetails(user.id); +export async function birdhouseCheckCommand(user: MUser) { + const details = calculateBirdhouseDetails(user); if (!details.birdHouse) { return 'You have no birdhouses planted.'; } @@ -67,8 +65,8 @@ export async function birdhouseHarvestCommand(user: MUser, channelID: string, in const birdHouses = birdhouseLimit(user); - const existingBirdhouse = await calculateBirdhouseDetails(user.id); - if (!existingBirdhouse.isReady && existingBirdhouse.raw.lastPlaced) return birdhouseCheckCommand(user.user); + const existingBirdhouse = await calculateBirdhouseDetails(user); + if (!existingBirdhouse.isReady && existingBirdhouse.raw.lastPlaced) return birdhouseCheckCommand(user); let birdhouseToPlant = inputBirdhouseName ? birdhouses.find(_birdhouse => @@ -77,7 +75,7 @@ export async function birdhouseHarvestCommand(user: MUser, channelID: string, in stringMatches(alias, inputBirdhouseName) || stringMatches(alias.split(' ')[0], inputBirdhouseName) ) - ) + ) : undefined; if (!birdhouseToPlant && existingBirdhouse.birdHouse) birdhouseToPlant = existingBirdhouse.birdHouse; @@ -104,7 +102,7 @@ export async function birdhouseHarvestCommand(user: MUser, channelID: string, in duration *= 0.9; } let gotCraft = false; - let removeBank = new Bank(); + const removeBank = new Bank(); const premadeBankCost = birdhouseToPlant.houseItemReq.clone().multiply(birdHouses); if (user.owns(premadeBankCost)) { removeBank.add(premadeBankCost); diff --git a/src/mahoji/lib/abstracted_commands/bonanzaCommand.ts b/src/mahoji/lib/abstracted_commands/bonanzaCommand.ts index d60abad5334..b4746e84ae9 100644 --- a/src/mahoji/lib/abstracted_commands/bonanzaCommand.ts +++ b/src/mahoji/lib/abstracted_commands/bonanzaCommand.ts @@ -1,7 +1,7 @@ import { Time } from 'e'; import { production } from '../../../config'; -import { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; +import type { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; import { formatDuration, randomVariation } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; @@ -16,7 +16,7 @@ export async function bonanzaCommand(user: MUser, channelID: string) { const duration = randomVariation(Time.Minute * 15, 5); - let str = `${ + const str = `${ user.minionName } is now off to participate in Balthazar's Big Bonanza! The total trip will take ${formatDuration(duration)}.`; diff --git a/src/mahoji/lib/abstracted_commands/buryCommand.ts b/src/mahoji/lib/abstracted_commands/buryCommand.ts index c0a4455d364..4cd8be7c572 100644 --- a/src/mahoji/lib/abstracted_commands/buryCommand.ts +++ b/src/mahoji/lib/abstracted_commands/buryCommand.ts @@ -3,7 +3,7 @@ import { Bank } from 'oldschooljs'; import Prayer from '../../../lib/skilling/skills/prayer'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { BuryingActivityTaskOptions } from '../../../lib/types/minions'; +import type { BuryingActivityTaskOptions } from '../../../lib/types/minions'; import { formatDuration, stringMatches } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; diff --git a/src/mahoji/lib/abstracted_commands/butlerCommand.ts b/src/mahoji/lib/abstracted_commands/butlerCommand.ts index 3526f05b4fc..255027bf10e 100644 --- a/src/mahoji/lib/abstracted_commands/butlerCommand.ts +++ b/src/mahoji/lib/abstracted_commands/butlerCommand.ts @@ -1,13 +1,13 @@ -import { clamp, Time } from 'e'; +import { Time, clamp } from 'e'; import { Bank } from 'oldschooljs'; +import { resolveItems } from 'oldschooljs/dist/util/util'; import { Planks } from '../../../lib/minions/data/planks'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { ButlerActivityTaskOptions } from '../../../lib/types/minions'; +import type { ButlerActivityTaskOptions } from '../../../lib/types/minions'; import { formatDuration, itemNameFromID, stringMatches, toKMB } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; -import resolveItems from '../../../lib/util/resolveItems'; import { updateBankSetting } from '../../../lib/util/updateBankSetting'; const unlimitedEarthRuneProviders = resolveItems([ @@ -48,7 +48,7 @@ export async function butlerCommand(user: MUser, plankName: string, quantity: nu return 'You need level 50 Construction to use the demon butler.'; } - let timePerPlank = (Time.Second * 15) / 26; + const timePerPlank = (Time.Second * 15) / 26; const maxTripLength = calcMaxTripLength(user, 'Butler'); @@ -67,9 +67,9 @@ export async function butlerCommand(user: MUser, plankName: string, quantity: nu } const { GP } = user; - let cost = plank!.gpCost * quantity; + let cost = plank?.gpCost * quantity; - let inventories = Math.ceil(quantity / 26); + const inventories = Math.ceil(quantity / 26); cost += 1250 * inventories; @@ -130,7 +130,7 @@ export async function butlerCommand(user: MUser, plankName: string, quantity: nu )}.`; } - const costBank = new Bank(consumedItems).add('Coins', cost).add(plank!.inputItem, quantity); + const costBank = new Bank(consumedItems).add('Coins', cost).add(plank?.inputItem, quantity); await user.removeItemsFromBank(costBank); await updateBankSetting('construction_cost_bank', new Bank().add('Coins', cost)); @@ -138,7 +138,7 @@ export async function butlerCommand(user: MUser, plankName: string, quantity: nu await addSubTaskToActivityTask({ type: 'Butler', duration, - plankID: plank!.outputItem, + plankID: plank?.outputItem, plankQuantity: quantity, userID: user.id, channelID: channelID.toString() diff --git a/src/mahoji/lib/abstracted_commands/buyFossilIslandNotes.ts b/src/mahoji/lib/abstracted_commands/buyFossilIslandNotes.ts index 04de7549175..cb8a978eb62 100644 --- a/src/mahoji/lib/abstracted_commands/buyFossilIslandNotes.ts +++ b/src/mahoji/lib/abstracted_commands/buyFossilIslandNotes.ts @@ -1,4 +1,4 @@ -import { ChatInputCommandInteraction } from 'discord.js'; +import type { ChatInputCommandInteraction } from 'discord.js'; import { randArrItem } from 'e'; import { Bank } from 'oldschooljs'; @@ -24,7 +24,7 @@ export async function buyFossilIslandNotes(user: MUser, interaction: ChatInputCo ); const tempClWithNewUniques = user.cl ? user.cl.clone() : new Bank(); - let loot = new Bank(); + const loot = new Bank(); for (let i = 0; i < quantity; i++) { const filteredPages = fossilIslandNotesCL.filter(page => !tempClWithNewUniques.has(page)); const outPage = diff --git a/src/mahoji/lib/abstracted_commands/camdozaalCommand.ts b/src/mahoji/lib/abstracted_commands/camdozaalCommand.ts index dfe6ee42ac0..969c0352a46 100644 --- a/src/mahoji/lib/abstracted_commands/camdozaalCommand.ts +++ b/src/mahoji/lib/abstracted_commands/camdozaalCommand.ts @@ -1,4 +1,4 @@ -import { increaseNumByPercent, reduceNumByPercent, Time } from 'e'; +import { Time, increaseNumByPercent, reduceNumByPercent } from 'e'; import { Bank } from 'oldschooljs'; import { SkillsEnum } from 'oldschooljs/dist/constants'; @@ -6,7 +6,7 @@ import { determineMiningTime } from '../../../lib/skilling/functions/determineMi import { pickaxes } from '../../../lib/skilling/functions/miningBoosts'; import Fishing from '../../../lib/skilling/skills/fishing'; import Mining from '../../../lib/skilling/skills/mining'; -import { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; +import type { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; import { formatDuration, itemNameFromID, randomVariation } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; @@ -45,7 +45,7 @@ async function miningCommand(user: MUser, channelID: string, quantity: number | const barroniteRocks = Mining.CamdozaalMine; // Calculate the time it takes to mine specific quantity or as many as possible - let [duration, newQuantity] = determineMiningTime({ + const [duration, newQuantity] = determineMiningTime({ quantity, ore: barroniteRocks, ticksBetweenRolls: currentPickaxe.ticksBetweenRolls, @@ -138,7 +138,7 @@ async function fishingCommand(user: MUser, channelID: string, quantity: number | const maxTripLength = calcMaxTripLength(user, 'CamdozaalFishing'); const camdozaalfish = Fishing.camdozaalFishes.find(_fish => _fish.name === 'Raw guppy')!; - let timePerFish = camdozaalfish.timePerFish * Time.Second; + const timePerFish = camdozaalfish.timePerFish * Time.Second; if (!quantity) { quantity = Math.floor(maxTripLength / timePerFish); diff --git a/src/mahoji/lib/abstracted_commands/cancelGEListingCommand.ts b/src/mahoji/lib/abstracted_commands/cancelGEListingCommand.ts index 5562e2eb70b..e8a1dd14ba5 100644 --- a/src/mahoji/lib/abstracted_commands/cancelGEListingCommand.ts +++ b/src/mahoji/lib/abstracted_commands/cancelGEListingCommand.ts @@ -1,13 +1,12 @@ -import { UserError } from '@oldschoolgg/toolkit/dist/lib/UserError'; +import { UserError } from '@oldschoolgg/toolkit'; import { Bank } from 'oldschooljs'; import { GrandExchange } from '../../../lib/grandExchange'; -import { prisma } from '../../../lib/settings/prisma'; + import { makeTransactFromTableBankQueries } from '../../../lib/tableBank'; import { logError } from '../../../lib/util/logError'; export async function cancelUsersListings(user: MUser) { - await user.sync(); const activeListings = await prisma.gEListing.findMany({ where: { user_id: user.id, @@ -49,7 +48,12 @@ export async function cancelGEListingCommand(user: MUser, idToCancel: string) { const listing = await prisma.gEListing.findFirst({ where: { user_id: user.id, - userfacing_id: idToCancel + userfacing_id: idToCancel, + cancelled_at: null, + fulfilled_at: null, + quantity_remaining: { + gt: 0 + } } }); if (!listing) { @@ -77,25 +81,26 @@ export async function cancelGEListingCommand(user: MUser, idToCancel: string) { return 'Something went wrong, please try again later.'; } - await prisma.$transaction([ - prisma.gEListing.update({ - where: { - id: listing.id - }, - data: { - cancelled_at: new Date() - } - }), - ...makeTransactFromTableBankQueries({ bankToRemove: refundBank }) + await Promise.all([ + prisma.$transaction([ + prisma.gEListing.update({ + where: { + id: listing.id + }, + data: { + cancelled_at: new Date() + } + }), + ...makeTransactFromTableBankQueries({ bankToRemove: refundBank }) + ]), + user.addItemsToBank({ + items: refundBank, + collectionLog: false, + dontAddToTempCL: true, + neverUpdateHistory: true + }) ]); - await user.addItemsToBank({ - items: refundBank, - collectionLog: false, - dontAddToTempCL: true, - neverUpdateHistory: true - }); - return `Successfully cancelled your listing, you have been refunded ${refundBank}.`; }); } diff --git a/src/mahoji/lib/abstracted_commands/cancelTaskCommand.ts b/src/mahoji/lib/abstracted_commands/cancelTaskCommand.ts index 60b6c6b1236..fc42952fa52 100644 --- a/src/mahoji/lib/abstracted_commands/cancelTaskCommand.ts +++ b/src/mahoji/lib/abstracted_commands/cancelTaskCommand.ts @@ -1,8 +1,7 @@ -import { ChatInputCommandInteraction } from 'discord.js'; +import type { ChatInputCommandInteraction } from 'discord.js'; import { randArrItem } from 'e'; - import { cancelTask } from '../../../lib/settings/settings'; -import { NexTaskOptions, RaidsOptions } from '../../../lib/types/minions'; +import type { NexTaskOptions, RaidsOptions } from '../../../lib/types/minions'; import { handleMahojiConfirmation } from '../../../lib/util/handleMahojiConfirmation'; import { getActivityOfUser } from '../../../lib/util/minionIsBusy'; diff --git a/src/mahoji/lib/abstracted_commands/capegamble.ts b/src/mahoji/lib/abstracted_commands/capegamble.ts index 5ba803aea5d..bb7bbc509da 100644 --- a/src/mahoji/lib/abstracted_commands/capegamble.ts +++ b/src/mahoji/lib/abstracted_commands/capegamble.ts @@ -1,5 +1,5 @@ import { formatOrdinal } from '@oldschoolgg/toolkit'; -import { ChatInputCommandInteraction } from 'discord.js'; +import type { ChatInputCommandInteraction } from 'discord.js'; import { Bank } from 'oldschooljs'; import { Events } from '../../../lib/constants'; @@ -18,15 +18,62 @@ export async function capeGambleStatsCommand(user: MUser) { **Infernal Cape's Gambled:** ${stats.infernal_cape_sacrifices}`; } +const itemGambles = [ + { + type: 'fire', + item: getOSItem('Fire cape'), + trackerKey: 'firecapes_sacrificed', + chatHead: 'mejJal', + chance: 200, + success: { + loot: getOSItem('Tzrek-Jad'), + message: 'You lucky. Better train him good else TzTok-Jad find you, JalYt.' + }, + failMessage: (newSacrificedCount: number) => + `You not lucky. Maybe next time, JalYt. This is the ${formatOrdinal( + newSacrificedCount + )} time you gamble cape.` + }, + { + type: 'infernal', + item: getOSItem('Infernal cape'), + trackerKey: 'infernal_cape_sacrifices', + chatHead: 'ketKeh', + chance: 100, + success: { + loot: getOSItem('Jal-nib-rek'), + message: 'Luck be a TzHaar tonight. Jal-Nib-Rek is yours.' + }, + failMessage: (newSacrificedCount: number) => + `No Jal-Nib-Rek for you. This is the ${formatOrdinal(newSacrificedCount)} time you gamble cape.` + }, + { + type: 'quiver', + item: getOSItem("Dizana's quiver (uncharged)"), + trackerKey: 'quivers_sacrificed', + chatHead: 'minimus', + chance: 200, + success: { + loot: getOSItem('Smol heredit'), + message: 'He seems to like you. Smol heredit is yours.' + }, + failMessage: (newSacrificedCount: number) => + `He doesn't want to go with you. Sorry. This is the ${formatOrdinal( + newSacrificedCount + )} time you gambled a quiver.` + } +] as const; + export async function capeGambleCommand( user: MUser, type: string, interaction: ChatInputCommandInteraction, - autoconfirm: boolean = false + autoconfirm = false ) { - const item = getOSItem(type === 'fire' ? 'Fire cape' : 'Infernal cape'); - const key: 'infernal_cape_sacrifices' | 'firecapes_sacrificed' = - type === 'fire' ? 'firecapes_sacrificed' : 'infernal_cape_sacrifices'; + const src = itemGambles.find(i => i.type === type); + if (!src) return 'Invalid type. You can only gamble fire capes, infernal capes, or quivers.'; + const { item } = src; + const key = src.trackerKey; const capesOwned = user.bank.amount(item.id); if (capesOwned < 1) return `You have no ${item.name}'s to gamble!`; @@ -48,13 +95,14 @@ export async function capeGambleCommand( }, { infernal_cape_sacrifices: true, - firecapes_sacrificed: true + firecapes_sacrificed: true, + quivers_sacrificed: true } ); const newSacrificedCount = newStats[key]; - const chance = type === 'fire' ? 200 : 100; - const pet = getOSItem(type === 'fire' ? 'Tzrek-Jad' : 'Jal-nib-rek'); + const { chance } = src; + const pet = src.success.loot; const gotPet = roll(chance); const loot = gotPet ? new Bank().add(pet.id) : undefined; @@ -72,11 +120,8 @@ export async function capeGambleCommand( { name: 'image.jpg', attachment: await newChatHeadImage({ - content: - type === 'fire' - ? 'You lucky. Better train him good else TzTok-Jad find you, JalYt.' - : 'Luck be a TzHaar tonight. Jal-Nib-Rek is yours.', - head: type === 'fire' ? 'mejJal' : 'ketKeh' + content: src.success.message, + head: src.chatHead }) } ] @@ -88,15 +133,8 @@ export async function capeGambleCommand( { name: 'image.jpg', attachment: await newChatHeadImage({ - content: - type === 'fire' - ? `You not lucky. Maybe next time, JalYt. This is the ${formatOrdinal( - newSacrificedCount - )} time you gamble cape.` - : `No Jal-Nib-Rek for you. This is the ${formatOrdinal( - newSacrificedCount - )} time you gamble cape.`, - head: type === 'fire' ? 'mejJal' : 'ketKeh' + content: src.failMessage(newSacrificedCount), + head: src.chatHead }) } ] diff --git a/src/mahoji/lib/abstracted_commands/castCommand.ts b/src/mahoji/lib/abstracted_commands/castCommand.ts index 1fe1bc1eabe..2466abe113c 100644 --- a/src/mahoji/lib/abstracted_commands/castCommand.ts +++ b/src/mahoji/lib/abstracted_commands/castCommand.ts @@ -1,8 +1,8 @@ -import { reduceNumByPercent, Time } from 'e'; +import { Time, reduceNumByPercent } from 'e'; import { SkillsEnum } from 'oldschooljs/dist/constants'; import { Castables } from '../../../lib/skilling/skills/magic/castables'; -import { CastingActivityTaskOptions } from '../../../lib/types/minions'; +import type { CastingActivityTaskOptions } from '../../../lib/types/minions'; import { formatDuration, stringMatches } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; diff --git a/src/mahoji/lib/abstracted_commands/castleWarsCommand.ts b/src/mahoji/lib/abstracted_commands/castleWarsCommand.ts index 011b039013f..f2d48ca64f6 100644 --- a/src/mahoji/lib/abstracted_commands/castleWarsCommand.ts +++ b/src/mahoji/lib/abstracted_commands/castleWarsCommand.ts @@ -1,8 +1,8 @@ import { Time } from 'e'; +import { formatDuration } from '@oldschoolgg/toolkit'; import { getMinigameEntity } from '../../../lib/settings/settings'; import type { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; -import { formatDuration } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; diff --git a/src/mahoji/lib/abstracted_commands/chargeGloriesCommand.ts b/src/mahoji/lib/abstracted_commands/chargeGloriesCommand.ts index 04eda1a0824..11905414e12 100644 --- a/src/mahoji/lib/abstracted_commands/chargeGloriesCommand.ts +++ b/src/mahoji/lib/abstracted_commands/chargeGloriesCommand.ts @@ -1,8 +1,8 @@ import { Bank } from 'oldschooljs'; import { gloriesInventorySize, gloriesInventoryTime } from '../../../lib/constants'; -import { userhasDiaryTier, WildernessDiary } from '../../../lib/diaries'; -import { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; +import { WildernessDiary, userhasDiaryTier } from '../../../lib/diaries'; +import type { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; import { formatDuration } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; diff --git a/src/mahoji/lib/abstracted_commands/chargeWealthCommand.ts b/src/mahoji/lib/abstracted_commands/chargeWealthCommand.ts index fb84087a65d..504371674e6 100644 --- a/src/mahoji/lib/abstracted_commands/chargeWealthCommand.ts +++ b/src/mahoji/lib/abstracted_commands/chargeWealthCommand.ts @@ -1,8 +1,8 @@ import { Bank } from 'oldschooljs'; import { wealthInventorySize, wealthInventoryTime } from '../../../lib/constants'; -import { userhasDiaryTier, WildernessDiary } from '../../../lib/diaries'; -import { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; +import { WildernessDiary, userhasDiaryTier } from '../../../lib/diaries'; +import type { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; import { formatDuration } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; diff --git a/src/mahoji/lib/abstracted_commands/chompyHuntCommand.ts b/src/mahoji/lib/abstracted_commands/chompyHuntCommand.ts index 0093a48bb60..49341819e69 100644 --- a/src/mahoji/lib/abstracted_commands/chompyHuntCommand.ts +++ b/src/mahoji/lib/abstracted_commands/chompyHuntCommand.ts @@ -1,11 +1,11 @@ -import { percentChance, reduceNumByPercent, Time } from 'e'; +import { Time, percentChance, reduceNumByPercent } from 'e'; import { Bank } from 'oldschooljs'; +import { formatDuration } from '@oldschoolgg/toolkit'; import { chompyHats } from '../../../lib/constants'; -import { userhasDiaryTier, WesternProv } from '../../../lib/diaries'; +import { WesternProv, userhasDiaryTier } from '../../../lib/diaries'; import { getMinigameScore } from '../../../lib/settings/minigames'; import type { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; -import { formatDuration } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; import getOSItem from '../../../lib/util/getOSItem'; @@ -40,7 +40,7 @@ export async function chompyHuntClaimCommand(user: MUser) { export async function chompyHuntCommand(user: MUser, channelID: string) { if (user.QP < 10) { - return 'You need atleast 10 QP to hunt Chompy birds.'; + return 'You need at least 10 QP to hunt Chompy birds.'; } const rangeGear = user.gear.range; @@ -50,7 +50,7 @@ export async function chompyHuntCommand(user: MUser, channelID: string) { const tripLength = calcMaxTripLength(user, 'BigChompyBirdHunting'); - let boosts = []; + const boosts = []; let quantity = Math.floor((baseChompyPerHour / Time.Hour) * tripLength); for (const [diary, boost] of diaryBoosts) { const [hasDiary] = await userhasDiaryTier(user, diary); diff --git a/src/mahoji/lib/abstracted_commands/collectCommand.ts b/src/mahoji/lib/abstracted_commands/collectCommand.ts index 09b68ff0c0d..6caac97fd6a 100644 --- a/src/mahoji/lib/abstracted_commands/collectCommand.ts +++ b/src/mahoji/lib/abstracted_commands/collectCommand.ts @@ -1,186 +1,16 @@ import { Time } from 'e'; import { Bank } from 'oldschooljs'; -import { Item } from 'oldschooljs/dist/meta/types'; -import { userhasDiaryTier, WildernessDiary } from '../../../lib/diaries'; -import { SkillsEnum } from '../../../lib/skilling/types'; -import { Skills } from '../../../lib/types'; -import { CollectingOptions } from '../../../lib/types/minions'; +import { WildernessDiary, userhasDiaryTier } from '../../../lib/diaries'; +import type { SkillsEnum } from '../../../lib/skilling/types'; +import type { CollectingOptions } from '../../../lib/types/minions'; import { formatDuration, stringMatches } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; -import getOSItem from '../../../lib/util/getOSItem'; import { updateBankSetting } from '../../../lib/util/updateBankSetting'; +import { collectables } from '../collectables'; import { getPOH } from './pohCommand'; -export interface Collectable { - item: Item; - skillReqs?: Skills; - itemCost?: Bank; - quantity: number; - duration: number; - qpRequired?: number; - onlyTamesCan?: boolean; -} - -export const collectables: Collectable[] = [ - { - item: getOSItem('Blue dragon scale'), - quantity: 26, - itemCost: new Bank({ - 'Water rune': 1, - 'Air rune': 3, - 'Law rune': 1 - }), - skillReqs: { - agility: 70, - magic: 37 - }, - duration: Time.Minute * 2 - }, - { - item: getOSItem('Mort myre fungus'), - quantity: 100, - itemCost: new Bank({ - 'Prayer potion(4)': 1, - 'Ring of dueling(8)': 1 - }), - skillReqs: { - prayer: 50 - }, - duration: Time.Minute * 8.3, - qpRequired: 32 - }, - { - item: getOSItem('Flax'), - quantity: 28, - duration: Time.Minute * 1.68 - }, - { - item: getOSItem('Swamp toad'), - quantity: 28, - duration: Time.Minute * 1.68 - }, - { - item: getOSItem("Red spiders' eggs"), - quantity: 80, - itemCost: new Bank({ - 'Stamina potion(4)': 1 - }), - duration: Time.Minute * 8.5 - }, - { - item: getOSItem('Wine of zamorak'), - quantity: 27, - itemCost: new Bank({ - 'Law rune': 27, - 'Air rune': 27 - }), - skillReqs: { - magic: 33 - }, - duration: Time.Minute * 3.12 - }, - { - item: getOSItem('White berries'), - quantity: 27, - qpRequired: 22, - skillReqs: { - ranged: 60, - thieving: 50, - agility: 56, - crafting: 10, - fletching: 5, - cooking: 30 - }, - duration: Time.Minute * 4.05 - }, - { - item: getOSItem('Snape grass'), - quantity: 120, - itemCost: new Bank({ - 'Law rune': 12, - 'Astral rune': 12 - }), - duration: Time.Minute * 6.5, - qpRequired: 72 - }, - { - item: getOSItem('Snake weed'), - quantity: 150, - itemCost: new Bank({ - 'Ring of dueling(8)': 1 - }), - duration: Time.Minute * 30, - qpRequired: 3 - }, - { - item: getOSItem('Bucket of sand'), - quantity: 30, - itemCost: new Bank({ - 'Law rune': 1, - Coins: 30 * 25 - }), - duration: Time.Minute, - qpRequired: 30 - }, - { - item: getOSItem('Jangerberries'), - quantity: 224, - itemCost: new Bank({ - 'Ring of dueling(8)': 1 - }), - skillReqs: { - agility: 10 - }, - duration: Time.Minute * 24 - }, - // Miniquest to get Tarn's diary for Salve amulet (e)/(ei) - { - item: getOSItem("Tarn's diary"), - quantity: 1, - itemCost: new Bank({ - 'Prayer potion(4)': 2 - }), - skillReqs: { - slayer: 40, - attack: 60, - strength: 60, - ranged: 60, - defence: 60, - magic: 60 - }, - duration: 10 * Time.Minute, - qpRequired: 100 - }, - { - item: getOSItem('Neem drupe'), - quantity: 5, - itemCost: new Bank({ - 'Astral rune': 26, - 'Cosmic rune': 12 - }), - skillReqs: { - magic: 82, - herblore: 82, - agility: 92 - }, - duration: 5 * Time.Minute, - qpRequired: 82 - }, - { - item: getOSItem('Orange'), - quantity: 1, - duration: 2 * Time.Minute, - onlyTamesCan: true - }, - { - item: getOSItem('Cabbage'), - quantity: 28, - duration: 1.2 * Time.Minute - } -]; - export async function collectCommand( user: MUser, channelID: string, @@ -241,11 +71,9 @@ export async function collectCommand( let cost: Bank = new Bank(); if (collectable.itemCost) { - { - cost = collectable.itemCost.clone().multiply(quantity); - if (cost.has('Ring of dueling(8)') && hasJewelleryBox) - cost.remove('Ring of dueling(8)', cost.amount('Ring of dueling(8)')); - } + cost = collectable.itemCost.clone().multiply(quantity); + if (cost.has('Ring of dueling(8)') && hasJewelleryBox) + cost.remove('Ring of dueling(8)', cost.amount('Ring of dueling(8)')); if (cost.has('Stamina potion(4)') && no_stams) { // 50% longer trip time for not using stamina potion (4) duration *= 1.5; diff --git a/src/mahoji/lib/abstracted_commands/combatRingCommand.ts b/src/mahoji/lib/abstracted_commands/combatRingCommand.ts new file mode 100644 index 00000000000..f1061e764b8 --- /dev/null +++ b/src/mahoji/lib/abstracted_commands/combatRingCommand.ts @@ -0,0 +1,20 @@ +import { Time } from 'e'; + +import type { ActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; +import { randomVariation } from '../../../lib/util'; +import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; + +export async function combatRingCommand(user: MUser, channelID: string) { + if (user.minionIsBusy) { + return 'Your minion is busy.'; + } + + await addSubTaskToActivityTask({ + userID: user.id, + channelID: channelID.toString(), + duration: randomVariation(Time.Minute * 5, 5), + type: 'CombatRing' + }); + + return `${user.minionName} is now fighting in the Shayzien Combat Ring!`; +} diff --git a/src/mahoji/lib/abstracted_commands/coxCommand.ts b/src/mahoji/lib/abstracted_commands/coxCommand.ts index 5efe925822b..2584d5bb986 100644 --- a/src/mahoji/lib/abstracted_commands/coxCommand.ts +++ b/src/mahoji/lib/abstracted_commands/coxCommand.ts @@ -13,16 +13,16 @@ import { } from '../../../lib/data/cox'; import { degradeItem } from '../../../lib/degradeableItems'; import { + InventionID, canAffordInventionBoost, inventionBoosts, - InventionID, inventionItemBoost } from '../../../lib/invention/inventions'; import { trackLoot } from '../../../lib/lootTrack'; import { setupParty } from '../../../lib/party'; import { getMinigameScore } from '../../../lib/settings/minigames'; -import { MakePartyOptions } from '../../../lib/types'; -import { RaidsOptions } from '../../../lib/types/minions'; +import type { MakePartyOptions } from '../../../lib/types'; +import type { RaidsOptions } from '../../../lib/types/minions'; import { channelIsSendable, formatDuration, randomVariation } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; @@ -68,9 +68,7 @@ export async function coxStatsCommand(user: MUser) { )}%, Team: ${Emoji.Skull} ${(await createTeam(Array(2).fill(user), false))[0].deathChance.toFixed(1)}% ${ Emoji.CombatSword } ${calcWhatPercent(normalTeam.reductions[user.id], normalTeam.maxUserReduction).toFixed(1)}%) -**Challenge Mode:** ${minigameScores.raids_challenge_mode} KC (Solo: ${Emoji.Skull} ${( - await createTeam([user], true) - )[0].deathChance.toFixed(1)}% ${Emoji.CombatSword} ${calcWhatPercent( +**Challenge Mode:** ${minigameScores.raids_challenge_mode} KC (Solo: ${Emoji.Skull} ${(await createTeam([user], true))[0].deathChance.toFixed(1)}% ${Emoji.CombatSword} ${calcWhatPercent( cmSolo.reductions[user.id], cmSolo.maxUserReduction ).toFixed(1)}%, Team: ${Emoji.Skull} ${(await createTeam(Array(2).fill(user), true))[0].deathChance.toFixed(1)}% ${ @@ -103,14 +101,14 @@ export async function coxCommand( if (isChallengeMode) { const normalKC = await getMinigameScore(user.id, 'raids'); if (normalKC < 200) { - return 'You need atleast 200 completions of the Chambers of Xeric before you can attempt Challenge Mode.'; + return 'You need at least 200 completions of the Chambers of Xeric before you can attempt Challenge Mode.'; } } if (user.minionIsBusy) { return "Your minion is busy, so you can't start a raid."; } - let maxSize = mahojiParseNumber({ input: maxSizeInput, min: 2, max: 15 }) ?? 15; + const maxSize = mahojiParseNumber({ input: maxSizeInput, min: 2, max: 15 }) ?? 15; const partyOptions: MakePartyOptions = { leader: user, @@ -186,7 +184,7 @@ export async function coxCommand( } // This gives a normal duration distribution. Better than (raidDuration * quantity) +/- 5% - let duration = sumArr( + const duration = sumArr( Array(quantity) .fill(raidDuration) .map(d => randomVariation(d, 5)) @@ -267,12 +265,12 @@ export async function coxCommand( let str = isSolo ? `${user.minionName} is now doing ${quantity > 1 ? quantity : 'a'} Chambers of Xeric raid${ quantity > 1 ? 's' : '' - }. The total trip will take ${formatDuration(duration)}.` + }. The total trip will take ${formatDuration(duration)}.` : `${partyOptions.leader.usernameOrMention}'s party (${users .map(u => u.usernameOrMention) .join(', ')}) is now off to do ${quantity > 1 ? quantity : 'a'} Chambers of Xeric raid${ quantity > 1 ? 's' : '' - } - the total trip will take ${formatDuration(duration)}.`; + } - the total trip will take ${formatDuration(duration)}.`; str += ` \n\n${debugStr}`; diff --git a/src/mahoji/lib/abstracted_commands/crackerCommand.ts b/src/mahoji/lib/abstracted_commands/crackerCommand.ts index 3dfd143dd0e..be047d8cf9f 100644 --- a/src/mahoji/lib/abstracted_commands/crackerCommand.ts +++ b/src/mahoji/lib/abstracted_commands/crackerCommand.ts @@ -1,4 +1,4 @@ -import { ChatInputCommandInteraction, User } from 'discord.js'; +import type { ChatInputCommandInteraction, User } from 'discord.js'; import { shuffleArr } from 'e'; import { Bank, LootTable } from 'oldschooljs'; diff --git a/src/mahoji/lib/abstracted_commands/cutLeapingFishCommand.ts b/src/mahoji/lib/abstracted_commands/cutLeapingFishCommand.ts index caad11a58b5..0d2a0f0d586 100644 --- a/src/mahoji/lib/abstracted_commands/cutLeapingFishCommand.ts +++ b/src/mahoji/lib/abstracted_commands/cutLeapingFishCommand.ts @@ -2,7 +2,7 @@ import { Time } from 'e'; import { Bank } from 'oldschooljs'; import LeapingFish from '../../../lib/skilling/skills/cooking/leapingFish'; -import { CutLeapingFishActivityTaskOptions } from '../../../lib/types/minions'; +import type { CutLeapingFishActivityTaskOptions } from '../../../lib/types/minions'; import { formatDuration, stringMatches } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; @@ -27,9 +27,9 @@ export async function cutLeapingFishCommand({ if (!barbarianFish) return 'That is not a valid fish to cut.'; - let requiredItems = barbarianFish.item.name; + const requiredItems = barbarianFish.item.name; - let timeToCutSingleItem = barbarianFish.tickRate * Time.Second * 0.6; + const timeToCutSingleItem = barbarianFish.tickRate * Time.Second * 0.6; const maxTripLength = calcMaxTripLength(user, 'Cooking'); diff --git a/src/mahoji/lib/abstracted_commands/dailyCommand.ts b/src/mahoji/lib/abstracted_commands/dailyCommand.ts index a8f22ac3d94..4d505fe3445 100644 --- a/src/mahoji/lib/abstracted_commands/dailyCommand.ts +++ b/src/mahoji/lib/abstracted_commands/dailyCommand.ts @@ -1,15 +1,15 @@ -import { ChatInputCommandInteraction, TextChannel } from 'discord.js'; -import { roll, shuffleArr, uniqueArr } from 'e'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; +import type { CommandResponse } from '@oldschoolgg/toolkit'; +import type { ChatInputCommandInteraction, TextChannel } from 'discord.js'; +import { shuffleArr, uniqueArr } from 'e'; import { Bank } from 'oldschooljs'; import { SupportServer } from '../../../config'; -import { COINS_ID, Emoji } from '../../../lib/constants'; import { DynamicButtons } from '../../../lib/DynamicButtons'; import { dailyResetTime } from '../../../lib/MUser'; +import { COINS_ID, Emoji } from '../../../lib/constants'; import { getRandomTriviaQuestions } from '../../../lib/roboChimp'; import dailyRoll from '../../../lib/simulation/dailyTable'; -import { channelIsSendable, formatDuration, isWeekend } from '../../../lib/util'; +import { channelIsSendable, formatDuration, isWeekend, roll } from '../../../lib/util'; import { deferInteraction } from '../../../lib/util/interactionReply'; import { makeBankImage } from '../../../lib/util/makeBankImage'; import { updateClientGPTrackSetting, userStatsUpdate } from '../../mahojiSettings'; @@ -151,7 +151,7 @@ export async function dailyCommand( buttons.add({ name: answer, fn: ({ interaction }) => { - if (question.answers.includes(answer) ? true : false) { + if (question.answers.includes(answer)) { correctUser = interaction.user.id; } }, diff --git a/src/mahoji/lib/abstracted_commands/diceCommand.ts b/src/mahoji/lib/abstracted_commands/diceCommand.ts index 82b8064a1d6..e022d70b6bf 100644 --- a/src/mahoji/lib/abstracted_commands/diceCommand.ts +++ b/src/mahoji/lib/abstracted_commands/diceCommand.ts @@ -1,4 +1,4 @@ -import { ChatInputCommandInteraction } from 'discord.js'; +import type { ChatInputCommandInteraction } from 'discord.js'; import { Bank, Util } from 'oldschooljs'; import { cryptoRand, percentChance } from '../../../lib/util'; @@ -29,13 +29,13 @@ export async function diceCommand(user: MUser, interaction: ChatInputCommandInte } if (amount < 1_000_000) { - return 'You have to dice atleast 1,000,000.'; + return 'You have to dice at least 1,000,000.'; } const gp = user.GP; if (amount > gp) return "You don't have enough GP."; const won = roll >= 55; - let amountToAdd = won ? amount : -amount; + const amountToAdd = won ? amount : -amount; await updateClientGPTrackSetting('gp_dice', amountToAdd); await updateGPTrackSetting('gp_dice', amountToAdd, user); diff --git a/src/mahoji/lib/abstracted_commands/driftNetCommand.ts b/src/mahoji/lib/abstracted_commands/driftNetCommand.ts index f4708a4d937..ea6175aa7e5 100644 --- a/src/mahoji/lib/abstracted_commands/driftNetCommand.ts +++ b/src/mahoji/lib/abstracted_commands/driftNetCommand.ts @@ -1,8 +1,8 @@ -import { randFloat, reduceNumByPercent, Time } from 'e'; +import { Time, randFloat, reduceNumByPercent } from 'e'; import { Bank } from 'oldschooljs'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; +import type { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; import { formatDuration } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; @@ -21,7 +21,7 @@ export async function driftNetCommand( } if (user.skillLevel(SkillsEnum.Fishing) < 47 || user.skillLevel(SkillsEnum.Hunter) < 44) { - return 'You need atleast level 44 Hunter and 47 Fishing to do Drift net fishing.'; + return 'You need at least level 44 Hunter and 47 Fishing to do Drift net fishing.'; } if ( @@ -35,9 +35,10 @@ export async function driftNetCommand( return 'You need a trident equipped to do Drift net fishing. Example of tridents are Merfolk trident and Uncharged trident.'; } - if (minutes < 1 || !Number.isInteger(minutes) || isNaN(minutes)) return 'Please specify a valid number of minutes.'; + if (minutes < 1 || !Number.isInteger(minutes) || Number.isNaN(minutes)) + return 'Please specify a valid number of minutes.'; - let tripLength = Time.Minute * minutes; + const tripLength = Time.Minute * minutes; if (tripLength > maxTripLength) { return `${user.minionName} can't go on trips longer than ${formatDuration( diff --git a/src/mahoji/lib/abstracted_commands/duelCommand.ts b/src/mahoji/lib/abstracted_commands/duelCommand.ts index 4d6425e37a6..7612279817d 100644 --- a/src/mahoji/lib/abstracted_commands/duelCommand.ts +++ b/src/mahoji/lib/abstracted_commands/duelCommand.ts @@ -1,12 +1,13 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ChatInputCommandInteraction } from 'discord.js'; -import { noOp, sleep, Time } from 'e'; -import { MahojiUserOption } from 'mahoji/dist/lib/types'; +import type { MahojiUserOption } from '@oldschoolgg/toolkit'; +import type { ChatInputCommandInteraction } from 'discord.js'; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js'; +import { Time, noOp, sleep } from 'e'; import { Bank, Util } from 'oldschooljs'; +import { MUserClass } from '../../../lib/MUser'; import { BLACKLISTED_USERS } from '../../../lib/blacklists'; import { Emoji, Events } from '../../../lib/constants'; -import { MUserClass } from '../../../lib/MUser'; -import { prisma } from '../../../lib/settings/prisma'; + import { awaitMessageComponentInteraction, channelIsSendable } from '../../../lib/util'; import { deferInteraction } from '../../../lib/util/interactionReply'; import { mahojiParseNumber, userStatsUpdate } from '../../mahojiSettings'; diff --git a/src/mahoji/lib/abstracted_commands/enchantCommand.ts b/src/mahoji/lib/abstracted_commands/enchantCommand.ts index b79de60c37b..13aa62ac5d7 100644 --- a/src/mahoji/lib/abstracted_commands/enchantCommand.ts +++ b/src/mahoji/lib/abstracted_commands/enchantCommand.ts @@ -1,10 +1,12 @@ import { Time } from 'e'; +import { formatDuration, stringMatches } from '@oldschoolgg/toolkit'; +import { itemID } from 'oldschooljs/dist/util'; import { BitField } from '../../../lib/constants'; import { Enchantables } from '../../../lib/skilling/skills/magic/enchantables'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { EnchantingActivityTaskOptions } from '../../../lib/types/minions'; -import { formatDuration, itemID, itemNameFromID, stringMatches } from '../../../lib/util'; +import type { EnchantingActivityTaskOptions } from '../../../lib/types/minions'; +import { itemNameFromID } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; import { determineRunes } from '../../../lib/util/determineRunes'; @@ -34,7 +36,7 @@ export async function enchantCommand(user: MUser, channelID: string, name: strin const maxTripLength = calcMaxTripLength(user, 'Enchanting'); - let timeToEnchantTen = 3 * Time.Second * 0.6 + Time.Second / 4; + const timeToEnchantTen = 3 * Time.Second * 0.6 + Time.Second / 4; if (!quantity) { quantity = Math.floor(maxTripLength / timeToEnchantTen); diff --git a/src/mahoji/lib/abstracted_commands/farmingCommand.ts b/src/mahoji/lib/abstracted_commands/farmingCommand.ts index 76592f44d37..6f54f8d39fd 100644 --- a/src/mahoji/lib/abstracted_commands/farmingCommand.ts +++ b/src/mahoji/lib/abstracted_commands/farmingCommand.ts @@ -1,19 +1,20 @@ -import { CropUpgradeType } from '@prisma/client'; -import { ChatInputCommandInteraction } from 'discord.js'; -import { percentChance, reduceNumByPercent, Time } from 'e'; import { Bank } from 'oldschooljs'; -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; +import type { CropUpgradeType } from '@prisma/client'; +import type { ChatInputCommandInteraction } from 'discord.js'; +import { Time, reduceNumByPercent } from 'e'; import { BitField } from '../../../lib/constants'; import { superCompostables } from '../../../lib/data/filterables'; import { ArdougneDiary, userhasDiaryTier } from '../../../lib/diaries'; -import { prisma } from '../../../lib/settings/prisma'; + import { calcNumOfPatches } from '../../../lib/skilling/functions/calcsFarming'; import { getFarmingInfo } from '../../../lib/skilling/functions/getFarmingInfo'; import Farming from '../../../lib/skilling/skills/farming'; -import { Plant, SkillsEnum } from '../../../lib/skilling/types'; -import { FarmingActivityTaskOptions } from '../../../lib/types/minions'; -import { formatDuration, stringMatches } from '../../../lib/util'; +import type { Plant } from '../../../lib/skilling/types'; +import { SkillsEnum } from '../../../lib/skilling/types'; +import type { FarmingActivityTaskOptions } from '../../../lib/types/minions'; +import { formatDuration, percentChance, stringMatches } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; import { farmingPatchNames, findPlant, isPatchName } from '../../../lib/util/farmingHelpers'; @@ -165,7 +166,7 @@ export async function farmingPlantCommand({ )}. *Make sure you are not attempting to farm 0 crops.*`; } - let wantsToPay = (pay || alwaysPay) && plant.canPayFarmer; + const wantsToPay = (pay || alwaysPay) && plant.canPayFarmer; if (user.skillLevel(SkillsEnum.Farming) < plant.level) { return `${user.minionName} needs ${plant.level} Farming to plant ${plant.name}.`; @@ -205,7 +206,7 @@ export async function farmingPlantCommand({ return `There are not enough ${plant.seedType} patches to plant that many. The max amount of patches to plant in is ${numOfPatches}.`; } - let duration: number = 0; + let duration = 0; if (patchType.patchPlanted) { duration = patchType.lastQuantity * (timePerPatchTravel + timePerPatchPlant + timePerPatchHarvest); if (quantity > patchType.lastQuantity) { @@ -326,7 +327,7 @@ export async function farmingPlantCommand({ } }); - await userStatsBankUpdate(user.id, 'farming_plant_cost_bank', cost); + await userStatsBankUpdate(user, 'farming_plant_cost_bank', cost); await addSubTaskToActivityTask({ plantsName: plant.name, diff --git a/src/mahoji/lib/abstracted_commands/farmingContractCommand.ts b/src/mahoji/lib/abstracted_commands/farmingContractCommand.ts index b2b19eb9a92..366b5568254 100644 --- a/src/mahoji/lib/abstracted_commands/farmingContractCommand.ts +++ b/src/mahoji/lib/abstracted_commands/farmingContractCommand.ts @@ -1,9 +1,13 @@ -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; +import type { CommandResponse } from '@oldschoolgg/toolkit'; import { defaultFarmingContract } from '../../../lib/minions/farming'; -import { ContractOption, FarmingContract, FarmingContractDifficultyLevel } from '../../../lib/minions/farming/types'; +import type { + ContractOption, + FarmingContract, + FarmingContractDifficultyLevel +} from '../../../lib/minions/farming/types'; import { getPlantToGrow } from '../../../lib/skilling/functions/calcFarmingContracts'; -import { getFarmingInfo } from '../../../lib/skilling/functions/getFarmingInfo'; +import { getFarmingInfoFromUser } from '../../../lib/skilling/functions/getFarmingInfo'; import { plants } from '../../../lib/skilling/skills/farming'; import { makeComponents, makeEasierFarmingContractButton, roughMergeMahojiResponse } from '../../../lib/util'; import { newChatHeadImage } from '../../../lib/util/chatHeadImage'; @@ -38,6 +42,13 @@ export async function farmingContractCommand(userID: string, input?: ContractOpt const plant = currentContract.hasContract ? findPlant(currentContract.plantToGrow) : null; if (!input) { + if (!currentContract.hasContract) { + const bestTier = bestFarmingContractUserCanDo(user); + if (bestTier !== undefined) { + return farmingContractCommand(user.id, bestTier); + } + } + return `You have completed a total of ${currentContract.contractsCompleted} Farming Contracts! **Current Contract:** ${plant ? `${plant.name} - ${currentContract.difficultyLevel}` : 'None'}`; } @@ -144,7 +155,7 @@ export async function farmingContractCommand(userID: string, input?: ContractOpt }; } -export async function canRunAutoContract(user: MUser) { +export function canRunAutoContract(user: MUser) { // Must be above 45 farming if (user.skillLevel('farming') < 45) return false; @@ -152,26 +163,27 @@ export async function canRunAutoContract(user: MUser) { const contract = user.user.minion_farmingContract as FarmingContract | null; if (!contract || !contract.hasContract) return true; - const farmingDetails = await getFarmingInfo(user.id); + const farmingDetails = getFarmingInfoFromUser(user.user); // If the patch we're contracted to is ready, we can auto contract const contractedPatch = farmingDetails.patchesDetailed.find( - p => p.patchName === plants.find(p => p.name === contract.plantToGrow)?.seedType + p => p.patchName === plants.find(pl => pl.name === contract.plantToGrow)?.seedType ); return contractedPatch?.ready; } -export async function autoContract(user: MUser, channelID: string, userID: string): CommandResponse { - const [farmingDetails, mahojiUser] = await Promise.all([ - getFarmingInfo(userID), - mahojiUsersSettingsFetch(userID, { minion_farmingContract: true }) - ]); - const contract = mahojiUser.minion_farmingContract as FarmingContract | null; - const plant = contract?.hasContract ? findPlant(contract?.plantToGrow) : null; - const patch = farmingDetails.patchesDetailed.find(p => p.plant === plant); - const bestContractTierCanDo = Object.entries(contractToFarmingLevel) +function bestFarmingContractUserCanDo(user: MUser) { + return Object.entries(contractToFarmingLevel) .sort((a, b) => b[1] - a[1]) .find(a => user.skillLevel('farming') >= a[1])?.[0] as ContractOption | undefined; +} + +export async function autoContract(user: MUser, channelID: string, userID: string): CommandResponse { + const farmingDetails = getFarmingInfoFromUser(user.user); + const contract = user.farmingContract(); + const plant = contract?.contract ? findPlant(contract?.contract.plantToGrow) : null; + const patch = farmingDetails.patchesDetailed.find(p => p.plant === plant); + const bestContractTierCanDo = bestFarmingContractUserCanDo(user); if (user.owns('Seed pack')) { const openResponse = await abstractedOpenCommand(null, user.id, ['seed pack'], 'auto'); @@ -180,14 +192,14 @@ export async function autoContract(user: MUser, channelID: string, userID: strin } // If they have no contract, get them a contract, recurse. - if (!contract || !contract.hasContract) { + if (!contract || !contract.contract) { const contractResult = await farmingContractCommand(userID, bestContractTierCanDo); const newUser = await mahojiUsersSettingsFetch(userID, { minion_farmingContract: true }); - const contract = newUser.minion_farmingContract as FarmingContract | null; - if (!contract || !contract.plantToGrow) return contractResult; + const newContract = (newUser.minion_farmingContract ?? defaultFarmingContract) as FarmingContract; + if (!newContract.hasContract || !newContract.plantToGrow) return contractResult; return farmingPlantCommand({ userID: user.id, - plantName: contract.plantToGrow, + plantName: newContract.plantToGrow, pay: false, autoFarmed: false, quantity: null, @@ -207,7 +219,7 @@ export async function autoContract(user: MUser, channelID: string, userID: strin }); } - // If they have a contract, and its planted, and its ready, harvest it. + // If they have a contract, and its planted, and it's ready, harvest it. if (patch.ready) { return harvestCommand({ user, channelID, seedType: patch.patchName }); } diff --git a/src/mahoji/lib/abstracted_commands/fightCavesCommand.ts b/src/mahoji/lib/abstracted_commands/fightCavesCommand.ts index 5b9a959d28c..a306e7dea31 100644 --- a/src/mahoji/lib/abstracted_commands/fightCavesCommand.ts +++ b/src/mahoji/lib/abstracted_commands/fightCavesCommand.ts @@ -1,12 +1,12 @@ -import { calcWhatPercent, percentChance, randInt, reduceNumByPercent, Time } from 'e'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; +import type { CommandResponse } from '@oldschoolgg/toolkit'; +import { Time, calcWhatPercent, percentChance, randInt, reduceNumByPercent } from 'e'; import { Bank, Monsters } from 'oldschooljs'; import TzTokJad from 'oldschooljs/dist/simulation/monsters/special/TzTokJad'; import { itemID } from 'oldschooljs/dist/util'; import { getMinigameScore } from '../../../lib/settings/minigames'; import { getUsersCurrentSlayerInfo } from '../../../lib/slayer/slayerUtil'; -import { FightCavesActivityTaskOptions } from '../../../lib/types/minions'; +import type { FightCavesActivityTaskOptions } from '../../../lib/types/minions'; import { formatDuration } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { newChatHeadImage } from '../../../lib/util/chatHeadImage'; @@ -86,11 +86,11 @@ function checkGear(user: MUser): string | undefined { } if (!user.owns(fightCavesCost)) { - return `JalYt, you need supplies to have a chance in the caves...come back with ${fightCavesCost}.`; + return `JalYt, you need supplies to have a chance in the caves... Come back with ${fightCavesCost}.`; } if (user.skillLevel('prayer') < 43) { - return 'JalYt, come back when you have atleast 43 Prayer, TzTok-Jad annihilate you without protection from gods.'; + return 'JalYt, come back when you have at least 43 Prayer, TzTok-Jad annihilate you without protection from gods.'; } } @@ -99,7 +99,10 @@ export async function fightCavesCommand(user: MUser, channelID: string): Command if (gearFailure) { return { files: [ - { attachment: await newChatHeadImage({ content: gearFailure, head: 'mejJal' }), name: 'fightcaves.jpg' } + { + attachment: await newChatHeadImage({ content: gearFailure, head: 'mejJal' }), + name: 'fightcaves.jpg' + } ] }; } @@ -132,8 +135,8 @@ export async function fightCavesCommand(user: MUser, channelID: string): Command const isOnTask = usersTask.currentTask !== null && usersTask.currentTask !== undefined && - usersTask.currentTask!.monster_id === Monsters.TzHaarKet.id && - usersTask.currentTask!.quantity_remaining === usersTask.currentTask!.quantity; + usersTask.currentTask?.monster_id === Monsters.TzHaarKet.id && + usersTask.currentTask?.quantity_remaining === usersTask.currentTask?.quantity; // 15% boost for on task if (isOnTask && user.hasEquippedOrInBank('Black mask (i)')) { diff --git a/src/mahoji/lib/abstracted_commands/fishingContestCommand.ts b/src/mahoji/lib/abstracted_commands/fishingContestCommand.ts index f07ee2247c8..bcb1f55502c 100644 --- a/src/mahoji/lib/abstracted_commands/fishingContestCommand.ts +++ b/src/mahoji/lib/abstracted_commands/fishingContestCommand.ts @@ -10,7 +10,7 @@ import { } from '../../../lib/fishingContest'; import { trackLoot } from '../../../lib/lootTrack'; import { getMinigameScore } from '../../../lib/settings/minigames'; -import { FishingContestOptions } from '../../../lib/types/minions'; +import type { FishingContestOptions } from '../../../lib/types/minions'; import { formatDuration, stringMatches } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { updateBankSetting } from '../../../lib/util/updateBankSetting'; @@ -19,13 +19,13 @@ export async function fishingContestStartCommand(user: MUser, channelID: string, const currentFishType = getCurrentFishType(); const validLocs = getValidLocationsForFishType(currentFishType); let quantity = 1; - let duration = Math.floor(quantity * Time.Minute * 1.69); - let quantityBoosts = []; + const duration = Math.floor(quantity * Time.Minute * 1.69); + const quantityBoosts = []; const tackleBoxes = ["Champion's tackle box", 'Professional tackle box', 'Standard tackle box', 'Basic tackle box']; for (let i = 0; i < tackleBoxes.length; i++) { if (user.hasEquippedOrInBank(tackleBoxes[i])) { - let num = tackleBoxes.length - i; + const num = tackleBoxes.length - i; quantityBoosts.push(`${num} for ${tackleBoxes[i]}`); quantity += num; break; diff --git a/src/mahoji/lib/abstracted_commands/fishingTrawler.ts b/src/mahoji/lib/abstracted_commands/fishingTrawler.ts index 2e52cf789ec..21657215d88 100644 --- a/src/mahoji/lib/abstracted_commands/fishingTrawler.ts +++ b/src/mahoji/lib/abstracted_commands/fishingTrawler.ts @@ -1,14 +1,14 @@ -import { calcWhatPercent, reduceNumByPercent, Time } from 'e'; +import { Time, calcWhatPercent, reduceNumByPercent } from 'e'; +import { formatDuration } from '@oldschoolgg/toolkit'; import { getMinigameScore } from '../../../lib/settings/minigames'; import type { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; -import { formatDuration } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; export async function fishingTrawlerCommand(user: MUser, channelID: string) { if (user.skillLevel('fishing') < 15) { - return 'You need atleast level 15 Fishing to do the Fishing Trawler.'; + return 'You need at least level 15 Fishing to do the Fishing Trawler.'; } const tripsDone = await getMinigameScore(user.id, 'fishing_trawler'); diff --git a/src/mahoji/lib/abstracted_commands/fistOfGuthix.ts b/src/mahoji/lib/abstracted_commands/fistOfGuthix.ts index 12c69bfb19e..de23163b01f 100644 --- a/src/mahoji/lib/abstracted_commands/fistOfGuthix.ts +++ b/src/mahoji/lib/abstracted_commands/fistOfGuthix.ts @@ -1,6 +1,6 @@ import { Time } from 'e'; -import { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; +import type { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; import { formatDuration, randomVariation } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; @@ -12,7 +12,7 @@ export async function fistOfGuthixCommand(user: MUser, channelID: string) { const quantity = Math.floor(calcMaxTripLength(user, 'FistOfGuthix') / gameTime); const duration = randomVariation(quantity * gameTime, 5); - let str = `${ + const str = `${ user.minionName } is now off to do ${quantity} Fist of Guthix games. The total trip will take ${formatDuration(duration)}.`; diff --git a/src/mahoji/lib/abstracted_commands/gauntletCommand.ts b/src/mahoji/lib/abstracted_commands/gauntletCommand.ts index 7dff8c15d60..2c5e40bd0a8 100644 --- a/src/mahoji/lib/abstracted_commands/gauntletCommand.ts +++ b/src/mahoji/lib/abstracted_commands/gauntletCommand.ts @@ -1,10 +1,10 @@ import { toTitleCase } from '@oldschoolgg/toolkit'; -import { calcWhatPercent, reduceNumByPercent, Time } from 'e'; +import { Time, calcWhatPercent, reduceNumByPercent } from 'e'; import { BitField } from '../../../lib/constants'; import { getMinigameScore } from '../../../lib/settings/minigames'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { GauntletOptions } from '../../../lib/types/minions'; +import type { GauntletOptions } from '../../../lib/types/minions'; import { formatDuration, formatSkillRequirements, randomVariation } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; @@ -44,7 +44,7 @@ const corruptedRequirements = { export async function gauntletCommand(user: MUser, channelID: string, type: 'corrupted' | 'normal' = 'normal') { if (user.minionIsBusy) return `${user.minionName} is busy.`; if (user.QP < 200) { - return 'You need atleast 200 QP to do the Gauntlet.'; + return 'You need at least 200 QP to do the Gauntlet.'; } const readableName = `${toTitleCase(type)} Gauntlet`; const requiredSkills = type === 'corrupted' ? corruptedRequirements : standardRequirements; @@ -120,7 +120,7 @@ export async function gauntletCommand(user: MUser, channelID: string, type: 'cor } // Add a 5% variance to account for randomness of gauntlet - let gauntletLength = randomVariation(baseLength, 5); + const gauntletLength = randomVariation(baseLength, 5); const maxTripLength = calcMaxTripLength(user, 'Gauntlet'); @@ -146,9 +146,7 @@ export async function gauntletCommand(user: MUser, channelID: string, type: 'cor const boostsStr = boosts.length > 0 ? `**Boosts:** ${boosts.join(', ')}` : ''; - return `${user.minionName} is now doing ${quantity}x ${readableName}. The trip will take ${formatDuration( - duration - )}. + return `${user.minionName} is now doing ${quantity}x ${readableName}. The trip will take ${formatDuration(duration)}. ${boostsStr} `; } diff --git a/src/mahoji/lib/abstracted_commands/gearCommands.ts b/src/mahoji/lib/abstracted_commands/gearCommands.ts index c910a2b46e2..b3a98117ce6 100644 --- a/src/mahoji/lib/abstracted_commands/gearCommands.ts +++ b/src/mahoji/lib/abstracted_commands/gearCommands.ts @@ -1,27 +1,27 @@ -import { toTitleCase } from '@oldschoolgg/toolkit'; -import { GearPreset } from '@prisma/client'; -import { ChatInputCommandInteraction } from 'discord.js'; +import { PerkTier, toTitleCase } from '@oldschoolgg/toolkit'; +import type { CommandResponse } from '@oldschoolgg/toolkit'; +import type { GearPreset } from '@prisma/client'; +import type { ChatInputCommandInteraction } from 'discord.js'; import { objectValues } from 'e'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; import { Bank } from 'oldschooljs'; -import { MAX_INT_JAVA, PATRON_ONLY_GEAR_SETUP, PerkTier } from '../../../lib/constants'; +import { MAX_INT_JAVA, PATRON_ONLY_GEAR_SETUP } from '../../../lib/constants'; import { generateAllGearImage, generateGearImage } from '../../../lib/gear/functions/generateGearImage'; -import { GearSetup, GearSetupType, GearStat } from '../../../lib/gear/types'; +import type { GearSetup, GearSetupType } from '../../../lib/gear/types'; +import { GearStat } from '../../../lib/gear/types'; import getUserBestGearFromBank from '../../../lib/minions/functions/getUserBestGearFromBank'; import { unEquipAllCommand } from '../../../lib/minions/functions/unequipAllCommand'; -import { prisma } from '../../../lib/settings/prisma'; -import { defaultGear, Gear, globalPresets } from '../../../lib/structures/Gear'; + +import { Gear, defaultGear, globalPresets } from '../../../lib/structures/Gear'; import { assert, formatSkillRequirements, isValidGearSetup, stringMatches } from '../../../lib/util'; import calculateGearLostOnDeathWilderness from '../../../lib/util/calculateGearLostOnDeathWilderness'; import { gearEquipMultiImpl } from '../../../lib/util/equipMulti'; import { getItem } from '../../../lib/util/getOSItem'; import { handleMahojiConfirmation } from '../../../lib/util/handleMahojiConfirmation'; import { minionIsBusy } from '../../../lib/util/minionIsBusy'; -import { transactItemsFromBank } from '../../../lib/util/transactItemsFromBank'; import { mahojiParseNumber } from '../../mahojiSettings'; -export async function gearPresetEquipCommand(user: MUser, gearSetup: string, presetName: string): CommandResponse { +async function gearPresetEquipCommand(user: MUser, gearSetup: string, presetName: string): CommandResponse { if (user.minionIsBusy) { return `${user.minionName} is currently out on a trip, so you can't change their gear!`; } @@ -109,7 +109,7 @@ export async function gearPresetEquipCommand(user: MUser, gearSetup: string, pre }; } -export async function gearEquipMultiCommand( +async function gearEquipMultiCommand( user: MUser, interaction: ChatInputCommandInteraction, setup: string, @@ -223,7 +223,7 @@ export async function gearEquipCommand(args: { const result = currentEquippedGear.equip(itemToEquip, quantity); - await transactItemsFromBank({ + await transactItems({ userID: user.id, collectionLog: false, dontAddToTempCL: true, @@ -293,7 +293,7 @@ export async function gearUnequipCommand( }; } -export async function autoEquipCommand(user: MUser, gearSetup: GearSetupType, equipmentType: string): CommandResponse { +async function autoEquipCommand(user: MUser, gearSetup: GearSetupType, equipmentType: string): CommandResponse { if (gearSetup === 'other' && user.perkTier() < PerkTier.Four) { return PATRON_ONLY_GEAR_SETUP; } @@ -335,7 +335,7 @@ export async function gearStatsCommand(user: MUser, input: string): CommandRespo const gear = { ...defaultGear }; for (const name of input.split(',')) { const item = getItem(name); - if (item && item.equipment) { + if (item?.equipment) { gear[item.equipment.slot] = { item: item.id, quantity: 1 }; } } @@ -353,7 +353,7 @@ export async function gearViewCommand(user: MUser, input: string, text: boolean) .join('\n') ), name: 'gear.txt' - } + } : { attachment: await generateAllGearImage(user), name: 'osbot.png' }; return { content: 'Here are all your gear setups', @@ -406,7 +406,7 @@ export async function gearViewCommand(user: MUser, input: string, text: boolean) }) .join('\n\n'); - const updatedContent = `${content}\n\nThese assume you have atleast 25 prayer for the protect item prayer.`; + const updatedContent = `${content}\n\nThese assume you have at least 25 prayer for the protect item prayer.`; return { content: updatedContent }; } diff --git a/src/mahoji/lib/abstracted_commands/giantsFoundryCommand.ts b/src/mahoji/lib/abstracted_commands/giantsFoundryCommand.ts index 258b2f74ec4..c1f1d96fee6 100644 --- a/src/mahoji/lib/abstracted_commands/giantsFoundryCommand.ts +++ b/src/mahoji/lib/abstracted_commands/giantsFoundryCommand.ts @@ -1,20 +1,20 @@ -import { ChatInputCommandInteraction } from 'discord.js'; -import { calcWhatPercent, reduceNumByPercent, Time } from 'e'; import { Bank } from 'oldschooljs'; +import type { ChatInputCommandInteraction } from 'discord.js'; +import { Time, calcWhatPercent, reduceNumByPercent } from 'e'; import { TOTAL_GIANT_WEAPONS } from '../../../lib/giantsFoundry'; import { trackLoot } from '../../../lib/lootTrack'; import { getMinigameEntity } from '../../../lib/settings/minigames'; import Smithing from '../../../lib/skilling/skills/smithing'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { GiantsFoundryActivityTaskOptions } from '../../../lib/types/minions'; +import type { GiantsFoundryActivityTaskOptions } from '../../../lib/types/minions'; import { formatDuration, stringMatches } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; import { handleMahojiConfirmation } from '../../../lib/util/handleMahojiConfirmation'; import { updateBankSetting } from '../../../lib/util/updateBankSetting'; import { userStatsBankUpdate, userStatsUpdate } from '../../mahojiSettings'; -import { GiantsFoundryBank } from './../../../lib/giantsFoundry'; +import type { GiantsFoundryBank } from './../../../lib/giantsFoundry'; export const giantsFoundryAlloys = [ { @@ -172,14 +172,14 @@ export async function giantsFoundryStartCommand( } if (userSmithingLevel < alloy.level) { - return `${user.minionName} needs atleast level ${alloy.level} Smithing to user ${alloy.name} alloy in the Giants' Foundry.`; + return `${user.minionName} needs at least level ${alloy.level} Smithing to user ${alloy.name} alloy in the Giants' Foundry.`; } // If they have the entire Smiths' Uniform, give an extra 15% speed bonus let setBonus = 0; if ( user.hasEquippedOrInBank( - Object.keys(Smithing.smithsUniformItems).map(i => parseInt(i)), + Object.keys(Smithing.smithsUniformItems).map(i => Number.parseInt(i)), 'every' ) ) { @@ -187,7 +187,7 @@ export async function giantsFoundryStartCommand( } else { // For each Smiths' Uniform item, check if they have it, give its' set boost and covert to 15% speed bonus later. for (const [itemID] of Object.entries(Smithing.smithsUniformItems)) { - if (user.hasEquippedOrInBank(parseInt(itemID))) { + if (user.hasEquippedOrInBank(Number.parseInt(itemID))) { setBonus += 3; } } @@ -238,7 +238,7 @@ export async function giantsFoundryStartCommand( } ] }); - await userStatsBankUpdate(user.id, 'gf_cost', totalCost); + await userStatsBankUpdate(user, 'gf_cost', totalCost); await addSubTaskToActivityTask({ quantity, @@ -310,7 +310,5 @@ export async function giantsFoundryShopCommand( { foundry_reputation: true } ); - return `You successfully bought **${quantity.toLocaleString()}x ${shopItem.name}** for ${( - shopItem.cost * quantity - ).toLocaleString()} Foundry Reputation.\nYou now have ${newRep} Foundry Reputation left.`; + return `You successfully bought **${quantity.toLocaleString()}x ${shopItem.name}** for ${(shopItem.cost * quantity).toLocaleString()} Foundry Reputation.\nYou now have ${newRep} Foundry Reputation left.`; } diff --git a/src/mahoji/lib/abstracted_commands/gnomeRestaurantCommand.ts b/src/mahoji/lib/abstracted_commands/gnomeRestaurantCommand.ts index 0b2eb8657b4..a077a6016e8 100644 --- a/src/mahoji/lib/abstracted_commands/gnomeRestaurantCommand.ts +++ b/src/mahoji/lib/abstracted_commands/gnomeRestaurantCommand.ts @@ -1,11 +1,11 @@ -import { calcWhatPercent, randInt, reduceNumByPercent, Time } from 'e'; +import { Time, calcWhatPercent, randInt, reduceNumByPercent } from 'e'; import { Bank } from 'oldschooljs'; import { SkillsEnum } from 'oldschooljs/dist/constants'; import { Emoji } from '../../../lib/constants'; import { getPOHObject } from '../../../lib/poh'; import { getMinigameScore } from '../../../lib/settings/minigames'; -import { GnomeRestaurantActivityTaskOptions } from '../../../lib/types/minions'; +import type { GnomeRestaurantActivityTaskOptions } from '../../../lib/types/minions'; import { formatDuration, randomVariation } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; @@ -19,7 +19,7 @@ export async function gnomeRestaurantCommand(user: MUser, channelID: string) { const itemsToRemove = new Bank(); const gp = user.GP; if (gp < 5000) { - return 'You need atleast 5k GP to work at the Gnome Restaurant.'; + return 'You need at least 5k GP to work at the Gnome Restaurant.'; } itemsToRemove.add('Coins', 5000); diff --git a/src/mahoji/lib/abstracted_commands/guardiansOfTheRiftCommand.ts b/src/mahoji/lib/abstracted_commands/guardiansOfTheRiftCommand.ts index d6447979358..eecc0116ce5 100644 --- a/src/mahoji/lib/abstracted_commands/guardiansOfTheRiftCommand.ts +++ b/src/mahoji/lib/abstracted_commands/guardiansOfTheRiftCommand.ts @@ -5,7 +5,7 @@ import { trackLoot } from '../../../lib/lootTrack'; import { pickaxes, varrockArmours } from '../../../lib/skilling/functions/miningBoosts'; import Runecraft from '../../../lib/skilling/skills/runecraft'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { GuardiansOfTheRiftActivityTaskOptions } from '../../../lib/types/minions'; +import type { GuardiansOfTheRiftActivityTaskOptions } from '../../../lib/types/minions'; import { formatDuration, itemID, itemNameFromID, randomVariation } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; @@ -23,8 +23,8 @@ export async function guardiansOfTheRiftStartCommand( return 'You need 27 Runecraft to access the Temple of the Eye.'; } - let timePerGame = Time.Minute * 10; - let maxTripLength = calcMaxTripLength(user, 'GuardiansOfTheRift'); + const timePerGame = Time.Minute * 10; + const maxTripLength = calcMaxTripLength(user, 'GuardiansOfTheRift'); const quantity = Math.floor(maxTripLength / timePerGame); const duration = quantity * timePerGame; // Being reduced with ticks @@ -133,7 +133,7 @@ export async function guardiansOfTheRiftStartCommand( rolls += 1; } - let removeRunesAndNecks = new Bank(); + const removeRunesAndNecks = new Bank(); if (combinationRunes) { const tomeOfFire = user.hasEquipped(['Tome of fire', 'Tome of fire (empty)']) ? 0 : 7; const tomeOfWater = user.hasEquipped(['Tome of water', 'Tome of water (empty)']) ? 0 : 7; diff --git a/src/mahoji/lib/abstracted_commands/hammyCommand.ts b/src/mahoji/lib/abstracted_commands/hammyCommand.ts index b7ac456d063..5cbc3b40167 100644 --- a/src/mahoji/lib/abstracted_commands/hammyCommand.ts +++ b/src/mahoji/lib/abstracted_commands/hammyCommand.ts @@ -1,4 +1,4 @@ -import { ChatInputCommandInteraction } from 'discord.js'; +import type { ChatInputCommandInteraction } from 'discord.js'; import { randArrItem } from 'e'; import { Bank } from 'oldschooljs'; @@ -62,7 +62,7 @@ export async function feedHammyCommand(interaction: ChatInputCommandInteraction, } if (roll(chanceToDouble)) { - let loot = new Bank(); + const loot = new Bank(); loot.add(firstItem.id); await user.addItemsToBank({ items: loot, collectionLog: false }); return randArrItem(hammyDoubleMessages).replace(/\{item\}/g, firstItem.name); diff --git a/src/mahoji/lib/abstracted_commands/hotColdCommand.ts b/src/mahoji/lib/abstracted_commands/hotColdCommand.ts index b043d7e2ecd..b7ecc647ba9 100644 --- a/src/mahoji/lib/abstracted_commands/hotColdCommand.ts +++ b/src/mahoji/lib/abstracted_commands/hotColdCommand.ts @@ -1,11 +1,12 @@ -import { ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; +import type { CommandResponse } from '@oldschoolgg/toolkit'; +import type { ChatInputCommandInteraction } from 'discord.js'; +import { EmbedBuilder } from 'discord.js'; import { LootTable } from 'oldschooljs'; import { toKMB } from 'oldschooljs/dist/util'; +import { resolveItems } from 'oldschooljs/dist/util/util'; import { mahojiClientSettingsUpdate } from '../../../lib/util/clientSettings'; import { handleMahojiConfirmation } from '../../../lib/util/handleMahojiConfirmation'; -import resolveItems from '../../../lib/util/resolveItems'; import { mahojiParseNumber, userStatsUpdate } from '../../mahojiSettings'; export const flowerTable = new LootTable() @@ -40,8 +41,8 @@ export async function hotColdCommand( if (!amount || !choice || !['hot', 'cold'].includes(choice) || !Number.isInteger(amount)) return explanation; if (amount < 10_000_000 || amount > 500_000_000) return 'You must gamble between 10m and 500m.'; if (user.GP < amount) return "You can't afford to gamble that much."; - let flowerLoot = flowerTable.roll(); - let flower = flowerLoot.items()[0][0]; + const flowerLoot = flowerTable.roll(); + const flower = flowerLoot.items()[0][0]; await handleMahojiConfirmation( interaction, diff --git a/src/mahoji/lib/abstracted_commands/igneCommand.ts b/src/mahoji/lib/abstracted_commands/igneCommand.ts index 4198720e572..4b5b8a39276 100644 --- a/src/mahoji/lib/abstracted_commands/igneCommand.ts +++ b/src/mahoji/lib/abstracted_commands/igneCommand.ts @@ -1,4 +1,4 @@ -import { ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; +import { type ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; import { Time } from 'e'; import { Bank } from 'oldschooljs'; @@ -127,7 +127,7 @@ ${bossUsers.map(u => `**${u.user.usernameOrMention}**: ${u.debugStr}`).join('\n\ return { embeds: [embed.data], - content: instance.boosts.length > 0 ? `**Boosts:** ${instance.boosts.join(', ')}.` : undefined + content: instance.boosts.length > 0 ? `**Boosts:** ${instance.boosts.join(', ')}.` : 'No boosts.' }; } catch (err: any) { return `The mass failed to start for this reason: ${err}.`; diff --git a/src/mahoji/lib/abstracted_commands/infernoCommand.ts b/src/mahoji/lib/abstracted_commands/infernoCommand.ts index f68b4c87c74..55f83f0aaeb 100644 --- a/src/mahoji/lib/abstracted_commands/infernoCommand.ts +++ b/src/mahoji/lib/abstracted_commands/infernoCommand.ts @@ -1,23 +1,23 @@ -import { calcPercentOfNum, increaseNumByPercent, percentChance, randInt, roll, sumArr, Time } from 'e'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; +import { type CommandResponse, formatDuration } from '@oldschoolgg/toolkit'; +import { Time, calcPercentOfNum, increaseNumByPercent, percentChance, randInt, roll, sumArr } from 'e'; import { Bank, Monsters } from 'oldschooljs'; -import { ItemBank } from 'oldschooljs/dist/meta/types'; -import { itemID } from 'oldschooljs/dist/util'; +import type { ItemBank } from 'oldschooljs/dist/meta/types'; +import { itemID, randomVariation } from 'oldschooljs/dist/util'; import { BitField, Emoji, projectiles } from '../../../lib/constants'; import { gorajanArcherOutfit, gorajanOccultOutfit, gorajanWarriorOutfit } from '../../../lib/data/CollectionsExport'; import { getSimilarItems } from '../../../lib/data/similarItems'; import { blowpipeDarts } from '../../../lib/minions/functions/blowpipeCommand'; -import { BlowpipeData } from '../../../lib/minions/types'; +import type { BlowpipeData } from '../../../lib/minions/types'; import { getMinigameEntity, getMinigameScore } from '../../../lib/settings/minigames'; -import { prisma } from '../../../lib/settings/prisma'; + import { SkillsEnum } from '../../../lib/skilling/types'; import { getUsersCurrentSlayerInfo } from '../../../lib/slayer/slayerUtil'; -import { Gear } from '../../../lib/structures/Gear'; +import type { Gear } from '../../../lib/structures/Gear'; import { PercentCounter } from '../../../lib/structures/PercentCounter'; -import { Skills } from '../../../lib/types'; -import { InfernoOptions } from '../../../lib/types/minions'; -import { determineProjectileTypeFromGear, formatDuration, itemNameFromID, randomVariation } from '../../../lib/util'; +import type { Skills } from '../../../lib/types'; +import type { InfernoOptions } from '../../../lib/types/minions'; +import { determineProjectileTypeFromGear, itemNameFromID } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { newChatHeadImage } from '../../../lib/util/chatHeadImage'; import getOSItem from '../../../lib/util/getOSItem'; @@ -192,14 +192,14 @@ async function infernoRun({ hitpoints: 100, ranged: 107, prayer: 105 - } + } : { defence: 92, magic: 94, hitpoints: 92, ranged: 92, prayer: 77 - }; + }; const hasSkillReqs = user.hasSkillReqs(skillReqs); if (!hasSkillReqs) { return `You not meet skill requirements, you need ${Object.entries(skillReqs) @@ -330,7 +330,7 @@ async function infernoRun({ const dartIndex = blowpipeDarts.indexOf(dartItem); const percent = dartIndex >= 3 ? dartIndex * 0.9 : -(4 * (4 - dartIndex)); if (dartIndex < 5) { - return 'Your darts are simply too weak, to work in the Inferno!'; + return 'Your darts are simply too weak to work in the Inferno!'; } if (isEmergedZuk) { if (!['Dragon dart', 'Rune dart', 'Amethyst dart'].includes(dartItem.name)) { @@ -359,8 +359,7 @@ async function infernoRun({ const weapon = setup.equippedWeapon(); const validWeapons = Object.keys(weapons) .map(itemID) - .map(id => getSimilarItems(id)) - .flat(); + .flatMap(id => getSimilarItems(id)); if (!weapon || !validWeapons.includes(weapon.id)) { return `You need one of these weapons in your ${name} setup: ${Object.keys(weapons).join(', ')}.`; } @@ -463,9 +462,9 @@ async function infernoRun({ const isOnTask = usersTask.currentTask !== null && usersTask.currentTask !== undefined && - usersTask.currentTask!.monster_id === Monsters.TzHaarKet.id && + usersTask.currentTask?.monster_id === Monsters.TzHaarKet.id && score > 0 && - usersTask.currentTask!.quantity_remaining === usersTask.currentTask!.quantity; + usersTask.currentTask?.quantity_remaining === usersTask.currentTask?.quantity; duration.add(isOnTask && user.hasEquippedOrInBank('Black mask (i)'), -9, `${Emoji.Slayer} Slayer Task`); @@ -497,7 +496,7 @@ async function infernoRun({ const projectilesForTheirType = projectiles[projectileType].items; if (!projectilesForTheirType.includes(projectile.item)) { return `You're using incorrect projectiles, you're using a ${ - rangeGear.equippedWeapon()!.name + rangeGear.equippedWeapon()?.name }, which uses ${projectileType}s, so you should be using one of these: ${projectilesForTheirType .map(itemNameFromID) .join(', ')}.`; @@ -690,14 +689,14 @@ export async function infernoStartCommand(user: MUser, channelID: string, emerge }); updateBankSetting('inferno_cost', realCost); - let emergedZukDeathMsg = emerged + const emergedZukDeathMsg = emerged ? `**Emerged Zuk Death Chance:** ${emergedZukDeathChance.value.toFixed( 1 - )}% ${emergedZukDeathChance.messages.join(', ')} ${ + )}% ${emergedZukDeathChance.messages.join(', ')} ${ emergedZukDeathChance.missed.length === 0 ? '' : `*(You didn't get these: ||${emergedZukDeathChance.missed.join(', ')}||)*` - }` + }` : ''; return { content: ` @@ -725,7 +724,7 @@ ${emergedZukDeathMsg} { name: 'image.jpg', attachment: await newChatHeadImage({ - content: "You're on your own now JalYt, you face certain death... prepare to fight for your life.", + content: "You're on your own now JalYt, you face certain death... Prepare to fight for your life.", head: 'ketKeh' }) } diff --git a/src/mahoji/lib/abstracted_commands/ironmanCommand.ts b/src/mahoji/lib/abstracted_commands/ironmanCommand.ts index 35c3d94d35a..21c26e38c9a 100644 --- a/src/mahoji/lib/abstracted_commands/ironmanCommand.ts +++ b/src/mahoji/lib/abstracted_commands/ironmanCommand.ts @@ -1,12 +1,11 @@ import { mentionCommand } from '@oldschoolgg/toolkit'; -import { Prisma } from '@prisma/client'; -import { ChatInputCommandInteraction } from 'discord.js'; -import { ItemBank } from 'oldschooljs/dist/meta/types'; +import type { Prisma } from '@prisma/client'; +import type { ChatInputCommandInteraction } from 'discord.js'; +import type { ItemBank } from 'oldschooljs/dist/meta/types'; import { BitField } from '../../../lib/constants'; -import { GrandExchange } from '../../../lib/grandExchange'; import { roboChimpUserFetch } from '../../../lib/roboChimp'; -import { prisma } from '../../../lib/settings/prisma'; + import { assert } from '../../../lib/util'; import { handleMahojiConfirmation } from '../../../lib/util/handleMahojiConfirmation'; import { minionIsBusy } from '../../../lib/util/minionIsBusy'; @@ -38,8 +37,25 @@ export async function ironmanCommand(user: MUser, interaction: ChatInputCommandI return "You can't become an ironman because you have active bingos."; } - const activeGEListings = await GrandExchange.fetchActiveListings(); - if ([...activeGEListings.buyListings, ...activeGEListings.sellListings].some(i => i.user_id === user.id)) { + const activeListings = await prisma.gEListing.findMany({ + where: { + user_id: user.id, + quantity_remaining: { + gt: 0 + }, + fulfilled_at: null, + cancelled_at: null + }, + include: { + buyTransactions: true, + sellTransactions: true + }, + orderBy: { + created_at: 'desc' + } + }); + // Return early if no active listings. + if (activeListings.length !== 0) { return `You can't become an ironman because you have active Grand Exchange listings. Cancel them and try again: ${mentionCommand( globalClient, 'ge', @@ -65,13 +81,9 @@ Type \`confirm permanent ironman\` if you understand the above information, and const mUser = (await mUserFetch(user.id)).user; type KeysThatArentReset = - | 'ironman_alts' - | 'main_account' | 'bank_bg_hex' | 'bank_sort_weightings' | 'bank_sort_method' - | 'premium_balance_expiry_date' - | 'premium_balance_tier' | 'minion_bought_date' | 'id' | 'pets' @@ -85,27 +97,21 @@ Type \`confirm permanent ironman\` if you understand the above information, and BitField.IsPatronTier4, BitField.IsPatronTier5, BitField.isModerator, - BitField.isContributor, BitField.BypassAgeRestriction, BitField.HasPermanentEventBackgrounds, BitField.HasPermanentTierOne, BitField.DisabledRandomEvents, BitField.AlwaysSmallBank, - BitField.IsWikiContributor, BitField.IsPatronTier6 ]; const createOptions: Required> = { id: user.id, - main_account: mUser.main_account, - ironman_alts: mUser.ironman_alts, bank_bg_hex: mUser.bank_bg_hex, bank_sort_method: mUser.bank_sort_method, bank_sort_weightings: mUser.bank_sort_weightings as ItemBank, minion_bought_date: mUser.minion_bought_date, RSN: mUser.RSN, - premium_balance_expiry_date: mUser.premium_balance_expiry_date, - premium_balance_tier: mUser.premium_balance_tier, pets: mUser.pets as ItemBank, bitfield: bitFieldsToKeep.filter(i => user.bitfield.includes(i)) }; diff --git a/src/mahoji/lib/abstracted_commands/kgCommand.ts b/src/mahoji/lib/abstracted_commands/kgCommand.ts index f7b919d1f3d..e8d72bdd054 100644 --- a/src/mahoji/lib/abstracted_commands/kgCommand.ts +++ b/src/mahoji/lib/abstracted_commands/kgCommand.ts @@ -1,4 +1,4 @@ -import { ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; +import { type ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; import { Time } from 'e'; import { Bank } from 'oldschooljs'; import { toKMB } from 'oldschooljs/dist/util'; @@ -91,7 +91,7 @@ ${bossUsers.map(u => `**${u.user.usernameOrMention}**: ${u.debugStr}`).join('\n\ return { embeds: [embed.data], - content: instance.boosts.length > 0 ? `**Boosts:** ${instance.boosts.join(', ')}.` : undefined + content: instance.boosts.length > 0 ? `**Boosts:** ${instance.boosts.join(', ')}.` : 'No boosts.' }; } catch (err: any) { return `The mass failed to start for this reason: ${err}.`; diff --git a/src/mahoji/lib/abstracted_commands/kkCommand.ts b/src/mahoji/lib/abstracted_commands/kkCommand.ts index f53e49c1357..c1f6a84135b 100644 --- a/src/mahoji/lib/abstracted_commands/kkCommand.ts +++ b/src/mahoji/lib/abstracted_commands/kkCommand.ts @@ -1,6 +1,5 @@ -import { ChatInputCommandInteraction } from 'discord.js'; -import { increaseNumByPercent, reduceNumByPercent, round, Time } from 'e'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; +import type { ChatInputCommandInteraction } from 'discord.js'; +import { Time, increaseNumByPercent, reduceNumByPercent, round } from 'e'; import { Bank } from 'oldschooljs'; import { calcBossFood } from '../../../lib/bso/calcBossFood'; @@ -8,11 +7,11 @@ import { gorajanWarriorOutfit, torvaOutfit } from '../../../lib/data/Collections import { trackLoot } from '../../../lib/lootTrack'; import { KalphiteKingMonster } from '../../../lib/minions/data/killableMonsters/custom/bosses/KalphiteKing'; import { calculateMonsterFood } from '../../../lib/minions/functions'; -import { KillableMonster } from '../../../lib/minions/types'; +import type { KillableMonster } from '../../../lib/minions/types'; import { setupParty } from '../../../lib/party'; import { Gear } from '../../../lib/structures/Gear'; -import { MakePartyOptions } from '../../../lib/types'; -import { BossActivityTaskOptions } from '../../../lib/types/minions'; +import type { MakePartyOptions } from '../../../lib/types'; +import type { BossActivityTaskOptions } from '../../../lib/types/minions'; import { channelIsSendable, formatDuration, isWeekend } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import calcDurQty from '../../../lib/util/calcMassDurationQuantity'; @@ -61,7 +60,7 @@ export async function kkCommand( channelID: string, inputName: string, inputQuantity: number | undefined -): CommandResponse { +) { if (interaction) await deferInteraction(interaction); const failureRes = await checkReqs([user], KalphiteKingMonster, 2); if (failureRes) return failureRes; @@ -134,7 +133,7 @@ export async function kkCommand( users.map(u => u.id) ); debugStr += `**${user.usernameOrMention}**: `; - let msgs = []; + const msgs = []; // Special inquisitor outfit damage boost const meleeGear = user.gear.melee; @@ -262,7 +261,7 @@ export async function kkCommand( if (users.length === 5) minDuration = 1.2; if (users.length >= 6) minDuration = 1; - let durQtyRes = await calcDurQty( + const durQtyRes = await calcDurQty( users, { ...KalphiteKingMonster, timeToFinish: effectiveTime }, inputQuantity, @@ -270,12 +269,12 @@ export async function kkCommand( Time.Minute * 30 ); if (typeof durQtyRes === 'string') return durQtyRes; - let [quantity, duration, perKillTime] = durQtyRes; + const [quantity, duration, perKillTime] = durQtyRes; const secondCheck = await checkReqs(users, KalphiteKingMonster, quantity); if (secondCheck) return secondCheck; let foodString = 'Removed brews/restores from users: '; - let foodRemoved: string[] = []; + const foodRemoved: string[] = []; for (const user of users) { const food = await calcBossFood(user, KalphiteKingMonster, users.length, quantity); if (!user.bank.has(food.bank)) { diff --git a/src/mahoji/lib/abstracted_commands/lampCommand.ts b/src/mahoji/lib/abstracted_commands/lampCommand.ts index 131242da636..d7e788bbf70 100644 --- a/src/mahoji/lib/abstracted_commands/lampCommand.ts +++ b/src/mahoji/lib/abstracted_commands/lampCommand.ts @@ -1,9 +1,9 @@ import { clamp, objectValues } from 'e'; import { Bank } from 'oldschooljs'; -import { Item, ItemBank } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { Skills } from '../../../lib/types'; +import type { ItemBank, Skills } from '../../../lib/types'; import { assert, isValidSkill, itemID } from '../../../lib/util'; import { getItem } from '../../../lib/util/getOSItem'; import resolveItems from '../../../lib/util/resolveItems'; @@ -180,6 +180,13 @@ export const Lampables: IXPObject[] = [ skills[skill] = data.user.skillLevel(skill) * ([ + SkillsEnum.Attack, + SkillsEnum.Strength, + SkillsEnum.Defence, + SkillsEnum.Magic, + SkillsEnum.Ranged, + SkillsEnum.Hitpoints, + SkillsEnum.Prayer, SkillsEnum.Mining, SkillsEnum.Woodcutting, SkillsEnum.Herblore, @@ -303,18 +310,16 @@ export async function lampCommand(user: MUser, itemToUse: string, skill: string, ]!}** in ${skill} to receive it.`; } - let amount = skillsToReceive[skill]!; + const amount = skillsToReceive[skill]!; assert(typeof amount === 'number' && amount > 0); - userStatsUpdate(user.id, u => { - let newLampedXp = { - ...(u.lamped_xp as ItemBank) - }; - if (!newLampedXp[skill]) newLampedXp[skill] = amount; - else newLampedXp[skill] += amount; - - return { - lamped_xp: newLampedXp - }; + const stats = await user.fetchStats({ lamped_xp: true }); + const newLampedXp = { + ...(stats.lamped_xp as ItemBank) + }; + if (!newLampedXp[skill]) newLampedXp[skill] = amount; + else newLampedXp[skill] += amount; + userStatsUpdate(user.id, { + lamped_xp: newLampedXp }); await user.removeItemsFromBank(toRemoveFromBank); diff --git a/src/mahoji/lib/abstracted_commands/lmsCommand.ts b/src/mahoji/lib/abstracted_commands/lmsCommand.ts index 1b29cd91ae3..2284cb31f6b 100644 --- a/src/mahoji/lib/abstracted_commands/lmsCommand.ts +++ b/src/mahoji/lib/abstracted_commands/lmsCommand.ts @@ -1,9 +1,8 @@ -import { ChatInputCommandInteraction } from 'discord.js'; +import type { ChatInputCommandInteraction } from 'discord.js'; import { Time } from 'e'; import { Bank } from 'oldschooljs'; import { LMSBuyables } from '../../../lib/data/CollectionsExport'; -import { lmsSimCommand } from '../../../lib/minions/functions/lmsSimCommand'; import type { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; import { formatDuration, randomVariation, stringMatches } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; @@ -16,7 +15,6 @@ export async function lmsCommand( stats?: {}; start?: {}; buy?: { name?: string; quantity?: number }; - simulate?: { names?: string }; }, user: MUser, channelID: string, @@ -35,13 +33,6 @@ export async function lmsCommand( **Total Matches:** ${stats.totalGames}`; } - if (options.simulate) { - lmsSimCommand(globalClient.channels.cache.get(channelID.toString()), options.simulate.names); - return { - content: 'Starting simulation...' - }; - } - if (options.buy) { const itemToBuy = LMSBuyables.find(i => stringMatches(i.item.name, options.buy?.name ?? '')); if (!itemToBuy) { @@ -53,7 +44,7 @@ export async function lmsCommand( return `You don't have enough points. ${quantity}x ${itemToBuy.item.name} costs ${cost}, but you have ${stats.points}.`; } if (itemToBuy.wins && stats.gamesWon < itemToBuy.wins) { - return `You are not worthy! You need to have won atleast ${itemToBuy.wins} games to buy the ${itemToBuy.item.name}.`; + return `You are not worthy! You need to have won at least ${itemToBuy.wins} games to buy the ${itemToBuy.item.name}.`; } const loot = new Bank().add(itemToBuy.item.id, quantity * (itemToBuy.quantity ?? 1)); await handleMahojiConfirmation(interaction, `Are you sure you want to spend ${cost} points on buying ${loot}?`); diff --git a/src/mahoji/lib/abstracted_commands/luckyPickCommand.ts b/src/mahoji/lib/abstracted_commands/luckyPickCommand.ts index 23754c5007a..e6712eb7921 100644 --- a/src/mahoji/lib/abstracted_commands/luckyPickCommand.ts +++ b/src/mahoji/lib/abstracted_commands/luckyPickCommand.ts @@ -1,32 +1,25 @@ -import { - ActionRowBuilder, - BaseMessageOptions, - ButtonBuilder, - ButtonStyle, - ChatInputCommandInteraction, - MessageComponentInteraction -} from 'discord.js'; -import { chunk, noOp, roll, shuffleArr, Time } from 'e'; +import type { BaseMessageOptions, ButtonInteraction, CacheType, ChatInputCommandInteraction } from 'discord.js'; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js'; +import { Time, chunk, noOp, roll, shuffleArr } from 'e'; import { Bank } from 'oldschooljs'; import { toKMB } from 'oldschooljs/dist/util'; import { SILENT_ERROR } from '../../../lib/constants'; import { awaitMessageComponentInteraction, channelIsSendable } from '../../../lib/util'; -import { handleMahojiConfirmation } from '../../../lib/util/handleMahojiConfirmation'; +import { handleMahojiConfirmation, silentButtonAck } from '../../../lib/util/handleMahojiConfirmation'; +import { deferInteraction } from '../../../lib/util/interactionReply'; import { logError } from '../../../lib/util/logError'; import { mahojiParseNumber, updateClientGPTrackSetting, updateGPTrackSetting } from '../../mahojiSettings'; -export async function luckyPickCommand( - user: MUser, - luckypickamount: string, - interaction: ChatInputCommandInteraction -): Promise { +export async function luckyPickCommand(user: MUser, luckypickamount: string, interaction: ChatInputCommandInteraction) { const amount = mahojiParseNumber({ input: luckypickamount, min: 1_000_000, max: 3_000_000_000 }); if (!amount) { return 'amount must be between 1000000 and 3000000000 exclusively.'; } + await deferInteraction(interaction); + interface Button { name: string; mod: (qty: number) => number; @@ -61,23 +54,38 @@ export async function luckyPickCommand( ]; function getButtons(): ButtonInstance[] { - // prettier-ignore - let buttonsToShow = ['0', '0', '0', - '2x', '1.5x', '0', '1.5x', '0', - '1.5x', '1.5x', '2x', '0', '3x', - '2x', '0', '0', '2x', '0']; + const buttonsToShow = [ + '0', + '0', + '0', + '2x', + '1.5x', + '0', + '1.5x', + '0', + '1.5x', + '1.5x', + '2x', + '0', + '3x', + '2x', + '0', + '0', + '2x', + '0' + ]; buttonsToShow.push(roll(10) ? '10x' : '0'); buttonsToShow.push(roll(10) ? '5x' : '0'); return shuffleArr(buttonsToShow.map(n => buttons.find(i => i.name === n)!)).map((item, index) => ({ ...item, picked: false, - id: index + id: `LP_${index}` })); } interface ButtonInstance extends Button { - id: number; + id: string; picked: boolean; } if (user.isIronman) { @@ -96,11 +104,11 @@ export async function luckyPickCommand( await user.removeItemsFromBank(new Bank().add('Coins', amount)); const buttonsToShow = getButtons(); function getCurrentButtons({ showTrueNames }: { showTrueNames: boolean }): BaseMessageOptions['components'] { - let chunkedButtons = chunk(buttonsToShow, 5); + const chunkedButtons = chunk(buttonsToShow, 5); return chunkedButtons.map(c => new ActionRowBuilder().addComponents( c.map(b => { - let button = new ButtonBuilder() + const button = new ButtonBuilder() .setCustomId(b.id.toString()) .setStyle( @@ -129,26 +137,25 @@ export async function luckyPickCommand( const channel = globalClient.channels.cache.get(interaction.channelId); if (!channelIsSendable(channel)) throw new Error('Channel for confirmation not found.'); const sentMessage = await channel.send({ - content: 'Pick *one* button!', + content: `${user}, Pick *one* button!`, components: getCurrentButtons({ showTrueNames: false }) }); const finalize = async ({ - button, - interaction + button }: { button: ButtonInstance; - interaction: MessageComponentInteraction; }) => { - let amountReceived = Math.floor(button.mod(amount)); - await user.addItemsToBank({ items: new Bank().add('Coins', amountReceived) }); + const amountReceived = Math.floor(button.mod(amount)); + if (amountReceived > 0) { + await user.addItemsToBank({ items: new Bank().add('Coins', amountReceived) }); + } await updateClientGPTrackSetting('gp_luckypick', amountReceived - amount); await updateGPTrackSetting('gp_luckypick', amountReceived - amount, user); - - await interaction.update({ components: getCurrentButtons({ showTrueNames: true }) }).catch(noOp); + await sentMessage.edit({ components: getCurrentButtons({ showTrueNames: true }) }).catch(noOp); return amountReceived === 0 - ? 'Unlucky, you picked the wrong button and lost your bet!' - : `You won ${toKMB(amountReceived)}!`; + ? `${user} picked the wrong button and lost ${toKMB(amount)}!` + : `${user} won ${toKMB(amountReceived)}!`; }; const cancel = async () => { @@ -172,13 +179,19 @@ export async function luckyPickCommand( }, time: Time.Second * 10 }); + sentMessage.delete().catch(noOp); - const pickedButton = buttonsToShow.find(b => b.id.toString() === selection.customId)!; - buttonsToShow[pickedButton.id].picked = true; + const pickedButton = buttonsToShow.find(b => b.id === selection.customId)!; + const index = Number.parseInt(pickedButton.id.split('_')[1]); + buttonsToShow[index].picked = true; try { - const result = await finalize({ button: pickedButton, interaction: selection }); - return result; + await silentButtonAck(selection as ButtonInteraction); + const result = await finalize({ button: pickedButton }); + return { + content: result, + components: getCurrentButtons({ showTrueNames: true }) + }; } catch (err) { logError(err); return 'Error.'; diff --git a/src/mahoji/lib/abstracted_commands/mageTrainingArenaCommand.ts b/src/mahoji/lib/abstracted_commands/mageTrainingArenaCommand.ts index eedd85797bb..3c71531837c 100644 --- a/src/mahoji/lib/abstracted_commands/mageTrainingArenaCommand.ts +++ b/src/mahoji/lib/abstracted_commands/mageTrainingArenaCommand.ts @@ -1,8 +1,7 @@ +import type { CommandResponse } from '@oldschoolgg/toolkit'; import { Time } from 'e'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; import { Bank, LootTable } from 'oldschooljs'; -import { prisma } from '../../../lib/settings/prisma'; import { getNewUser } from '../../../lib/settings/settings'; import type { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; import { formatDuration, stringMatches } from '../../../lib/util'; @@ -114,7 +113,7 @@ ${mageTrainingArenaBuyables .map(i => `${i.item.name} - ${i.cost} pts - ${formatDuration((i.cost / pizazzPointsPerHour) * (Time.Minute * 60))}`) .join('\n')} -Hint: Magic Training Arena is combined into 1 room, and 1 set of points - rewards take approximately the same amount of time to get. To get started use **/minigames mage_training_arena train**. You can buy rewards using **/minigames mage_training_arena buy**.`; +Hint: Magic Training Arena is combined into 1 room, and 1 set of points - rewards take approximately the same amount of time to get. To get started use **/minigames mage_training_arena start**. You can buy rewards using **/minigames mage_training_arena buy**.`; } export async function mageTrainingArenaStartCommand(user: MUser, channelID: string): CommandResponse { diff --git a/src/mahoji/lib/abstracted_commands/mahoganyHomesCommand.ts b/src/mahoji/lib/abstracted_commands/mahoganyHomesCommand.ts index 1de253c36d4..678772b0cfe 100644 --- a/src/mahoji/lib/abstracted_commands/mahoganyHomesCommand.ts +++ b/src/mahoji/lib/abstracted_commands/mahoganyHomesCommand.ts @@ -1,11 +1,11 @@ -import { calcPercentOfNum, calcWhatPercent, randArrItem, randInt, roll, Time } from 'e'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; +import type { CommandResponse } from '@oldschoolgg/toolkit'; +import { Time, calcPercentOfNum, calcWhatPercent, randArrItem, randInt, roll } from 'e'; import { Bank } from 'oldschooljs'; import { getMinigameScore } from '../../../lib/settings/minigames'; import { Plank } from '../../../lib/skilling/skills/construction/constructables'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { MahoganyHomesActivityTaskOptions } from '../../../lib/types/minions'; +import type { MahoganyHomesActivityTaskOptions } from '../../../lib/types/minions'; import { formatDuration, stringMatches } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; @@ -16,7 +16,7 @@ interface IContract { name: string; tier: number; level: number; - plank: Plank; + plank: number; xp: number; points: number; plankXP: number[]; @@ -75,7 +75,7 @@ function calcTrip( const lenPerQty = maxLen / qtyPerMaxLen; const qty = Math.floor(maxLen / lenPerQty); - let itemsNeeded = new Bank(); + const itemsNeeded = new Bank(); let xp = 0; for (let i = 0; i < qty; i++) { diff --git a/src/mahoji/lib/abstracted_commands/minionBuyCommand.ts b/src/mahoji/lib/abstracted_commands/minionBuyCommand.ts index 1ecfa551820..58cab597a95 100644 --- a/src/mahoji/lib/abstracted_commands/minionBuyCommand.ts +++ b/src/mahoji/lib/abstracted_commands/minionBuyCommand.ts @@ -1,11 +1,8 @@ -import { ComponentType, User } from 'discord.js'; +import { type CommandResponse, isAtleastThisOld } from '@oldschoolgg/toolkit'; +import { ComponentType, type User } from 'discord.js'; import { Time } from 'e'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; import { Bank } from 'oldschooljs'; - import { mahojiInformationalButtons } from '../../../lib/constants'; -import { clArrayUpdate } from '../../../lib/handleNewCLItems'; -import { isAtleastThisOld } from '../../../lib/util'; export async function minionBuyCommand(apiUser: User, user: MUser, ironman: boolean): CommandResponse { if (user.user.minion_hasBought) return 'You already have a minion!'; @@ -31,14 +28,22 @@ export async function minionBuyCommand(apiUser: User, user: MUser, ironman: bool 'Clue scroll (beginner)': 10, 'Equippable mystery box': 1, 'Pet Mystery box': 1 - }) + }) : null; if (starter) { await user.addItemsToBank({ items: starter, collectionLog: false }); } // Ensure user has a userStats row - await clArrayUpdate(user, user.cl); + await prisma.userStats.upsert({ + where: { + user_id: BigInt(user.id) + }, + create: { + user_id: BigInt(user.id) + }, + update: {} + }); return { content: `You have successfully got yourself a minion, and you're ready to use the bot now! Please check out the links below for information you should read. diff --git a/src/mahoji/lib/abstracted_commands/minionKill.ts b/src/mahoji/lib/abstracted_commands/minionKill.ts index 2020f6e70f7..c4c7f1c651d 100644 --- a/src/mahoji/lib/abstracted_commands/minionKill.ts +++ b/src/mahoji/lib/abstracted_commands/minionKill.ts @@ -1,7 +1,7 @@ -import { Prisma } from '@prisma/client'; -import { ChartConfiguration } from 'chart.js'; -import { bold, ChatInputCommandInteraction, InteractionReplyOptions } from 'discord.js'; +import type { GearSetupType, Prisma } from '@prisma/client'; +import { type ChatInputCommandInteraction, type InteractionReplyOptions, bold } from 'discord.js'; import { + Time, calcPercentOfNum, calcWhatPercent, increaseNumByPercent, @@ -10,24 +10,25 @@ import { reduceNumByPercent, round, sumArr, - Time, uniqueArr } from 'e'; import { Bank, Monsters } from 'oldschooljs'; import { MonsterAttribute } from 'oldschooljs/dist/meta/monsterData'; import { itemID } from 'oldschooljs/dist/util'; -import { BitField, PeakTier, PvMMethod, YETI_ID } from '../../../lib/constants'; +import { colosseumCommand } from '../../../lib/colosseum'; +import { BitField, PeakTier, type PvMMethod, YETI_ID } from '../../../lib/constants'; import { gorajanArcherOutfit, gorajanOccultOutfit, gorajanWarriorOutfit } from '../../../lib/data/CollectionsExport'; import { Eatables } from '../../../lib/data/eatables'; import { getSimilarItems } from '../../../lib/data/similarItems'; -import { checkUserCanUseDegradeableItem, degradeablePvmBoostItems, degradeItem } from '../../../lib/degradeableItems'; -import { Diary, DiaryTier, userhasDiaryTier } from '../../../lib/diaries'; -import { readableStatName } from '../../../lib/gear'; -import { GearSetupType, GearStat } from '../../../lib/gear/types'; -import { canAffordInventionBoost, InventionID, inventionItemBoost } from '../../../lib/invention/inventions'; +import { checkUserCanUseDegradeableItem, degradeItem, degradeablePvmBoostItems } from '../../../lib/degradeableItems'; +import { userhasDiaryTier } from '../../../lib/diaries'; +import { type GearStat, maxOffenceStats } from '../../../lib/gear'; +import { InventionID, canAffordInventionBoost, inventionItemBoost } from '../../../lib/invention/inventions'; import { trackLoot } from '../../../lib/lootTrack'; +import type { CombatOptionsEnum } from '../../../lib/minions/data/combatConstants'; import { + SlayerActivityConstants, boostCannon, boostCannonMulti, boostIceBarrage, @@ -37,33 +38,27 @@ import { cannonBanks, cannonMultiConsumables, cannonSingleConsumables, - CombatOptionsEnum, iceBarrageConsumables, iceBurstConsumables, - SlayerActivityConstants, superiorCannonSingleConsumables } from '../../../lib/minions/data/combatConstants'; import { BSOMonsters } from '../../../lib/minions/data/killableMonsters/custom/customMonsters'; import { revenantMonsters } from '../../../lib/minions/data/killableMonsters/revs'; -import { - AttackStyles, - calculateMonsterFood, - convertAttackStylesToSetup, - resolveAttackStyles -} from '../../../lib/minions/functions'; +import { quests } from '../../../lib/minions/data/quests'; +import type { AttackStyles } from '../../../lib/minions/functions'; +import { calculateMonsterFood, convertAttackStylesToSetup, resolveAttackStyles } from '../../../lib/minions/functions'; import reducedTimeFromKC from '../../../lib/minions/functions/reducedTimeFromKC'; import removeFoodFromUser from '../../../lib/minions/functions/removeFoodFromUser'; -import { Consumable } from '../../../lib/minions/types'; +import type { Consumable } from '../../../lib/minions/types'; import { calcPOHBoosts } from '../../../lib/poh'; import { SkillsEnum } from '../../../lib/skilling/types'; import { SlayerTaskUnlocksEnum } from '../../../lib/slayer/slayerUnlocks'; -import { determineBoostChoice, getUsersCurrentSlayerInfo } from '../../../lib/slayer/slayerUtil'; -import { addStatsOfItemsTogether, maxOffenceStats } from '../../../lib/structures/Gear'; -import { Peak } from '../../../lib/tickers'; -import { MonsterActivityTaskOptions } from '../../../lib/types/minions'; +import { determineCombatBoosts, getUsersCurrentSlayerInfo } from '../../../lib/slayer/slayerUtil'; +import { addStatsOfItemsTogether } from '../../../lib/structures/Gear'; +import type { Peak } from '../../../lib/tickers'; +import type { MonsterActivityTaskOptions } from '../../../lib/types/minions'; import { calculateSimpleMonsterDeathChance, - calculateTripConsumableCost, checkRangeGearWeapon, convertAttackStyleToGearSetup, convertPvmStylesToGearSetup, @@ -75,12 +70,13 @@ import { isWeekend, itemNameFromID, randomVariation, + readableStatName, stringMatches } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; import { calcWildyPKChance, increaseWildEvasionXp } from '../../../lib/util/calcWildyPkChance'; -import { generateChart } from '../../../lib/util/chart'; +import { calculateTripConsumableCost } from '../../../lib/util/calculateTripConsumableCost'; import findMonster from '../../../lib/util/findMonster'; import getOSItem from '../../../lib/util/getOSItem'; import { handleMahojiConfirmation } from '../../../lib/util/handleMahojiConfirmation'; @@ -97,7 +93,6 @@ import { naxxusCommand } from './naxxusCommand'; import { nexCommand } from './nexCommand'; import { nightmareCommand } from './nightmareCommand'; import { getPOH } from './pohCommand'; -import { quests } from './questCommand'; import { temporossCommand } from './temporossCommand'; import { vasaCommand } from './vasaCommand'; import { wintertodtCommand } from './wintertodtCommand'; @@ -111,6 +106,12 @@ const revSpecialWeapons = { mage: getOSItem("Thammaron's sceptre") } as const; +const revUpgradedWeapons = { + melee: getOSItem('Ursine chainmace'), + range: getOSItem('Webweaver bow'), + mage: getOSItem('Accursed sceptre') +} as const; + function formatMissingItems(consumables: Consumable[], timeToFinish: number) { const str = []; @@ -162,8 +163,10 @@ export async function minionKillCommand( channelID: string, name: string, quantity: number | undefined, - method: PvMMethod | undefined -) { + method: PvMMethod | PvMMethod[] | undefined, + wilderness: boolean | undefined, + _solo: boolean | undefined +): Promise { if (user.minionIsBusy) { return 'Your minion is busy.'; } @@ -174,10 +177,11 @@ export async function minionKillCommand( const key = ({ melee: 'attack_crush', mage: 'attack_magic', range: 'attack_ranged' } as const)[style]; const boosts = []; - let messages: string[] = []; + const messages: string[] = []; if (!name) return invalidMonsterMsg; + if (stringMatches(name, 'colosseum')) return colosseumCommand(user, channelID); if (user.usingPet('Ishi')) { sendToChannelID(channelID.toString(), { content: `${user} Ishi Says: Let's kill some ogress warriors instead? 🥰 🐳` @@ -223,6 +227,16 @@ export async function minionKillCommand( return `You can't kill ${monster.name}, because you're not on a slayer task.`; } + if (monster.canBePked && wilderness === false) { + return `You can't kill ${monster.name} outside the wilderness.`; + } + + const isInWilderness = wilderness || (isOnTask && usersTask.assignedTask?.wilderness) || monster.canBePked; + + if (!monster.wildy && isInWilderness) { + return `You can't kill ${monster.name} in the wilderness.`; + } + const wildyGearStat = wildyGear.getStats()[key]; const revGearPercent = Math.max(0, calcWhatPercent(wildyGearStat, maxOffenceStats[key])); @@ -237,25 +251,31 @@ export async function minionKillCommand( } } - // Set chosen boost based on priority: + // Add jelly & bloodveld check as can barrage in wilderness + const jelly = monster.id === Monsters.Jelly.id; + const wildyBurst = jelly && isInWilderness; + + // determines what pvm methods the user can use const myCBOpts = user.combatOptions; - const boostChoice = determineBoostChoice({ + const methods = [method] as PvMMethod[]; + const combatMethods = determineCombatBoosts({ cbOpts: myCBOpts as CombatOptionsEnum[], user, monster, - method, - isOnTask + methods, + isOnTask, + wildyBurst }); - // Check requirements const [hasReqs, reason] = hasMonsterRequirements(user, monster); - if (!hasReqs) return reason ?? "You don't have the requirements to fight this monster"; + if (!hasReqs) { + return typeof reason === 'string' ? reason : "You don't have the requirements to fight this monster"; + } if (monster.diaryRequirement) { - const [diary, tier]: [Diary, DiaryTier] = monster.diaryRequirement; - const [hasDiary] = await userhasDiaryTier(user, tier); + const [hasDiary, _, diaryGroup] = await userhasDiaryTier(user, monster.diaryRequirement); if (!hasDiary) { - return `${user.minionName} is missing the ${diary.name} ${tier.name} diary to kill ${monster.name}.`; + return `${user.minionName} is missing the ${diaryGroup.name} ${monster.diaryRequirement[1]} diary to kill ${monster.name}.`; } } @@ -294,7 +314,7 @@ export async function minionKillCommand( const [, osjsMon, attackStyles] = resolveAttackStyles(user, { monsterID: monster.id, - boostMethod: boostChoice + boostMethod: combatMethods }); const [newTime, skillBoostMsg] = applySkillBoost(user, timeToFinish, attackStyles); @@ -314,20 +334,20 @@ export async function minionKillCommand( } } - for (const [itemID, boostAmount] of Object.entries(resolveAvailableItemBoosts(user, monster))) { + for (const [itemID, boostAmount] of Object.entries(resolveAvailableItemBoosts(user, monster, isInWilderness))) { timeToFinish *= (100 - boostAmount) / 100; - boosts.push(`${boostAmount}% for ${itemNameFromID(parseInt(itemID))}`); + boosts.push(`${boostAmount}% for ${itemNameFromID(Number.parseInt(itemID))}`); } if (user.usingPet('Gregoyle') && [Monsters.Gargoyle.id, Monsters.GrotesqueGuardians.id].includes(monster.id)) { timeToFinish = reduceNumByPercent(timeToFinish, 20); boosts.push('20% boost for Gregoyle'); } - if (user.hasEquipped('Dwarven warhammer') && !monster.wildy) { + if (user.hasEquipped('Dwarven warhammer') && !isInWilderness) { timeToFinish = reduceNumByPercent(timeToFinish, 40); boosts.push('40% boost for Dwarven warhammer'); } - if (user.gear.wildy.hasEquipped(['Hellfire bow']) && monster.wildy) { + if (user.gear.wildy.hasEquipped(['Hellfire bow']) && isInWilderness) { timeToFinish /= 3; boosts.push('3x boost for Hellfire bow'); } @@ -343,44 +363,52 @@ export async function minionKillCommand( let virtusBoost = 0; let virtusBoostMsg = ''; - const dragonBoost = 15; // Common boost percentage for dragon-related gear + let dragonBoost = 0; + let dragonBoostMsg = ''; + let revBoost = 0; + let revBoostMsg = ''; const isUndead = osjsMon?.data?.attributes?.includes(MonsterAttribute.Undead); const isDragon = osjsMon?.data?.attributes?.includes(MonsterAttribute.Dragon); - function applyDragonBoost() { - const hasDragonLance = monster?.canBePked - ? wildyGear.hasEquipped('Dragon hunter lance') - : user.hasEquippedOrInBank('Dragon hunter lance'); - const hasDragonCrossbow = monster?.canBePked - ? wildyGear.hasEquipped('Dragon hunter crossbow') - : user.hasEquippedOrInBank('Dragon hunter crossbow'); + function applyRevWeaponBoost() { + const style = convertAttackStylesToSetup(user.user.attack_style); + const specialWeapon = revSpecialWeapons[style]; + const upgradedWeapon = revUpgradedWeapons[style]; + if (wildyGear.hasEquipped(specialWeapon.name)) { + revBoost = 12.5; + timeToFinish = reduceNumByPercent(timeToFinish, revBoost); + revBoostMsg = `${revBoost}% for ${specialWeapon.name}`; + } + + if (wildyGear.hasEquipped(upgradedWeapon.name)) { + revBoost = 17.5; + timeToFinish = reduceNumByPercent(timeToFinish, revBoost); + revBoostMsg = `${revBoost}% for ${upgradedWeapon.name}`; + } + } + + function applyDragonBoost() { + const hasDragonLance = user.hasEquippedOrInBank('Dragon hunter lance'); + const hasDragonCrossbow = user.hasEquippedOrInBank('Dragon hunter crossbow'); if ( (hasDragonLance && !attackStyles.includes(SkillsEnum.Ranged) && !attackStyles.includes(SkillsEnum.Magic)) || (hasDragonCrossbow && attackStyles.includes(SkillsEnum.Ranged)) ) { - const boostMessage = hasDragonLance + dragonBoost = 15; // Common boost percentage for dragon-related gear + dragonBoostMsg = hasDragonLance ? `${dragonBoost}% for Dragon hunter lance` : `${dragonBoost}% for Dragon hunter crossbow`; timeToFinish = reduceNumByPercent(timeToFinish, dragonBoost); - boosts.push(boostMessage); } } function applyBlackMaskBoost() { - const hasInfernalSlayerHelmI = monster?.canBePked - ? wildyGear.hasEquipped('Infernal slayer helmet(i)') - : user.hasEquippedOrInBank('Infernal slayer helmet(i)'); - const hasInfernalSlayerHelm = monster?.canBePked - ? wildyGear.hasEquipped('Infernal slayer helmet') - : user.hasEquippedOrInBank('Infernal slayer helmet'); - const hasBlackMask = monster?.canBePked - ? wildyGear.hasEquipped('Black mask') - : user.hasEquippedOrInBank('Black mask'); - const hasBlackMaskI = monster?.canBePked - ? wildyGear.hasEquipped('Black mask (i)') - : user.hasEquippedOrInBank('Black mask (i)'); + const hasInfernalSlayerHelmI = user.hasEquippedOrInBank('Infernal slayer helmet(i)'); + const hasInfernalSlayerHelm = user.hasEquippedOrInBank('Infernal slayer helmet'); + const hasBlackMask = user.hasEquippedOrInBank('Black mask'); + const hasBlackMaskI = user.hasEquippedOrInBank('Black mask (i)'); if (attackStyles.includes(SkillsEnum.Ranged) || attackStyles.includes(SkillsEnum.Magic)) { if (hasBlackMaskI) { @@ -406,12 +434,8 @@ export async function minionKillCommand( let salveEnhanced = false; const style = attackStyles[0]; if (style === 'ranged' || style === 'magic') { - salveBoost = monster?.canBePked - ? wildyGear.hasEquipped('Salve amulet(i)') - : user.hasEquippedOrInBank('Salve amulet (i)'); - salveEnhanced = monster?.canBePked - ? wildyGear.hasEquipped('Salve amulet(ei)') - : user.hasEquippedOrInBank('Salve amulet (ei)'); + salveBoost = user.hasEquippedOrInBank('Salve amulet (i)'); + salveEnhanced = user.hasEquippedOrInBank('Salve amulet (ei)'); if (salveBoost) { salveAmuletBoost = salveEnhanced ? 20 : oneSixthBoost; salveAmuletBoostMsg = `${salveAmuletBoost}% for Salve amulet${ @@ -419,12 +443,8 @@ export async function minionKillCommand( } on non-melee task`; } } else { - salveBoost = monster?.canBePked - ? wildyGear.hasEquipped('Salve amulet') - : user.hasEquippedOrInBank('Salve amulet'); - salveEnhanced = monster?.canBePked - ? wildyGear.hasEquipped('Salve amulet (e)') - : user.hasEquippedOrInBank('Salve amulet (e)'); + salveBoost = user.hasEquippedOrInBank('Salve amulet'); + salveEnhanced = user.hasEquippedOrInBank('Salve amulet (e)'); if (salveBoost) { salveAmuletBoost = salveEnhanced ? 20 : oneSixthBoost; salveAmuletBoostMsg = `${salveAmuletBoost}% for Salve amulet${ @@ -434,8 +454,13 @@ export async function minionKillCommand( } } + if (isInWilderness && monster.revsWeaponBoost) { + applyRevWeaponBoost(); + } + function calculateVirtusBoost() { let virtusPiecesEquipped = 0; + for (const item of resolveItems(['Virtus mask', 'Virtus robe top', 'Virtus robe legs'])) { if (user.gear.mage.hasEquipped(item)) { virtusPiecesEquipped += blackMaskBoost !== 0 && itemNameFromID(item) === 'Virtus mask' ? 0 : 1; @@ -446,9 +471,9 @@ export async function minionKillCommand( virtusBoostMsg = virtusPiecesEquipped > 1 ? ` with ${virtusPiecesEquipped} Virtus pieces` - : virtusPiecesEquipped > 0 - ? ` with ${virtusPiecesEquipped} Virtus piece` - : ''; + : virtusPiecesEquipped === 1 + ? ` with ${virtusPiecesEquipped} Virtus piece` + : ''; } if (isDragon && monster.name.toLowerCase() !== 'vorkath') { @@ -474,9 +499,20 @@ export async function minionKillCommand( } } + // Only choose greater boost: + if (dragonBoost || revBoost) { + if (revBoost > dragonBoost) { + timeToFinish = reduceNumByPercent(timeToFinish, revBoost); + boosts.push(revBoostMsg); + } else { + timeToFinish = reduceNumByPercent(timeToFinish, dragonBoost); + boosts.push(dragonBoostMsg); + } + } + if (revenants) { timeToFinish = reduceNumByPercent(timeToFinish, revGearPercent / 4); - boosts.push(`${(revGearPercent / 4).toFixed(2)}% (out of a possible 25%) for ${key}`); + boosts.push(`$(revGearPercent / 4).toFixed(2)% (out of a possible 25%) for ${key}`); const specialWeapon = revSpecialWeapons[style]; if (wildyGear.hasEquipped(specialWeapon.name)) { @@ -499,19 +535,18 @@ export async function minionKillCommand( if (!isOnTask && method && method !== 'none') { return 'You can only burst/barrage/cannon while on task in BSO.'; } - if ((method === 'burst' || method === 'barrage') && !monster!.canBarrage) { + if (!wildyBurst && (method === 'burst' || method === 'barrage') && !monster!.canBarrage) { return `${monster!.name} cannot be barraged or burst.`; } if (method === 'cannon' && !hasCannon) { return "You don't own a Dwarf multicannon, so how could you use one?"; } - if (method === 'cannon' && !monster!.canCannon) { - return `${monster!.name} cannot be killed with a cannon.`; - } - if (boostChoice === 'barrage' && user.skillLevel(SkillsEnum.Magic) < 94) { + + // Check for stats + if (combatMethods.includes('barrage') && user.skillLevel(SkillsEnum.Magic) < 94) { return `You need 94 Magic to use Ice Barrage. You have ${user.skillLevel(SkillsEnum.Magic)}`; } - if (boostChoice === 'burst' && user.skillLevel(SkillsEnum.Magic) < 70) { + if (combatMethods.includes('burst') && user.skillLevel(SkillsEnum.Magic) < 70) { return `You need 70 Magic to use Ice Burst. You have ${user.skillLevel(SkillsEnum.Magic)}`; } @@ -519,17 +554,48 @@ export async function minionKillCommand( const { canAfford } = await canAffordInventionBoost(user, InventionID.SuperiorDwarfMultiCannon, timeToFinish); const canAffordSuperiorCannonBoost = hasSuperiorCannon ? canAfford : false; - if (boostChoice === 'chinning' && user.skillLevel(SkillsEnum.Ranged) < 65) { - return `You need 65 Ranged to use Chinning method. You have ${user.skillLevel(SkillsEnum.Ranged)}`; + + // Wildy monster cannon checks + if (isInWilderness === true && combatMethods.includes('cannon')) { + if (monster.id === Monsters.HillGiant.id || monster.id === Monsters.MossGiant.id) { + usingCannon = isInWilderness; + } + if (monster.id === Monsters.Spider.id || Monsters.Scorpion.id) { + usingCannon = isInWilderness; + cannonMulti = isInWilderness; + } + if (monster.wildySlayerCave) { + usingCannon = isInWilderness; + cannonMulti = isInWilderness; + if (monster.id === Monsters.AbyssalDemon.id && !isOnTask) { + usingCannon = false; + cannonMulti = false; + } + } + } + + // Burst/barrage check with wilderness conditions + if ((method === 'burst' || method === 'barrage') && !monster?.canBarrage) { + if (jelly) { + if (!isInWilderness) { + return `${monster.name} can only be barraged or burst in the wilderness.`; + } + } else return `${monster!.name} cannot be barraged or burst.`; + } + + if (!usingCannon) { + if (method === 'cannon' && !monster!.canCannon) { + return `${monster!.name} cannot be killed with a cannon.`; + } } if ( - boostChoice === 'cannon' && + combatMethods.includes('barrage') && !user.user.disabled_inventions.includes(InventionID.SuperiorDwarfMultiCannon) && canAffordSuperiorCannonBoost && (monster.canCannon || monster.cannonMulti) ) { - let qty = quantity || floor(maxTripLength / timeToFinish); + const qty = quantity || floor(maxTripLength / timeToFinish); const res = await inventionItemBoost({ user, inventionID: InventionID.SuperiorDwarfMultiCannon, @@ -538,29 +604,37 @@ export async function minionKillCommand( if (res.success) { usingCannon = true; consumableCosts.push(superiorCannonSingleConsumables); - let boost = monster.cannonMulti ? boostSuperiorCannonMulti : boostSuperiorCannon; + const boost = monster.cannonMulti ? boostSuperiorCannonMulti : boostSuperiorCannon; timeToFinish = reduceNumByPercent(timeToFinish, boost); boosts.push(`${boost}% for Superior Cannon (${res.messages})`); } - } else if (boostChoice === 'barrage' && attackStyles.includes(SkillsEnum.Magic) && monster!.canBarrage) { + } else if ( + combatMethods.includes('barrage') && + attackStyles.includes(SkillsEnum.Magic) && + (monster!.canBarrage || wildyBurst) + ) { consumableCosts.push(iceBarrageConsumables); calculateVirtusBoost(); timeToFinish = reduceNumByPercent(timeToFinish, boostIceBarrage + virtusBoost); boosts.push(`${boostIceBarrage + virtusBoost}% for Ice Barrage${virtusBoostMsg}`); burstOrBarrage = SlayerActivityConstants.IceBarrage; - } else if (boostChoice === 'burst' && attackStyles.includes(SkillsEnum.Magic) && monster!.canBarrage) { + } else if ( + combatMethods.includes('burst') && + attackStyles.includes(SkillsEnum.Magic) && + (monster!.canBarrage || wildyBurst) + ) { consumableCosts.push(iceBurstConsumables); calculateVirtusBoost(); timeToFinish = reduceNumByPercent(timeToFinish, boostIceBurst + virtusBoost); boosts.push(`${boostIceBurst + virtusBoost}% for Ice Burst${virtusBoostMsg}`); burstOrBarrage = SlayerActivityConstants.IceBurst; - } else if (boostChoice === 'cannon' && hasCannon && monster!.cannonMulti) { + } else if ((combatMethods.includes('cannon') && hasCannon && monster!.cannonMulti) || cannonMulti) { usingCannon = true; cannonMulti = true; consumableCosts.push(cannonMultiConsumables); timeToFinish = reduceNumByPercent(timeToFinish, boostCannonMulti); boosts.push(`${boostCannonMulti}% for Cannon in multi`); - } else if (boostChoice === 'cannon' && hasCannon && monster!.canCannon) { + } else if ((combatMethods.includes('cannon') && hasCannon && monster!.canCannon) || usingCannon) { usingCannon = true; consumableCosts.push(cannonSingleConsumables); timeToFinish = reduceNumByPercent(timeToFinish, boostCannon); @@ -570,7 +644,7 @@ export async function minionKillCommand( // Check what Chinchompa to use const chinchompas = ['Black chinchompa', 'Red chinchompa', 'Chinchompa']; let chinchompa = 'Black chinchompa'; - for (let chin of chinchompas) { + for (const chin of chinchompas) { if (user.owns(chin) && user.bank.amount(chin) > 5000) { chinchompa = chin; break; @@ -601,7 +675,7 @@ export async function minionKillCommand( timeToFinish *= 0.8; boosts.push('20% for Dwarven blessing'); } - if (monster.wildy && hasZealotsAmulet) { + if (isInWilderness && hasZealotsAmulet) { timeToFinish *= 0.95; boosts.push('5% for Amulet of zealots'); } @@ -642,9 +716,7 @@ export async function minionKillCommand( for (const set of monster.degradeableItemUsage) { const equippedInThisSet = set.items.find(item => user.gear[set.gearSetup].hasEquipped(item.itemID)); if (set.required && !equippedInThisSet) { - return `You need one of these items equipped in your ${set.gearSetup} setup to kill ${ - monster.name - }: ${set.items + return `You need one of these items equipped in your ${set.gearSetup} setup to kill ${monster.name}: ${set.items .map(i => i.itemID) .map(itemNameFromID) .join(', ')}.`; @@ -656,21 +728,22 @@ export async function minionKillCommand( degItemBeingUsed.push(degItem); } } - } else { + } else for (const degItem of degradeablePvmBoostItems) { const isUsing = convertPvmStylesToGearSetup(attackStyles).includes(degItem.attackStyle) && - user.gear[degItem.attackStyle].hasEquipped(degItem.item.id) && (monster.setupsUsed ? monster.setupsUsed.includes(degItem.attackStyle) : true); - if (isUsing) { + + const gearCheck = user.gear[degItem.attackStyle].hasEquipped(degItem.item.id); + + if (isUsing && gearCheck) { // We assume they have enough charges, add the boost, and degrade at the end to avoid doing it twice. degItemBeingUsed.push(degItem); } } - for (const degItem of degItemBeingUsed) { - boosts.push(`${degItem.boost}% for ${degItem.item.name}`); - timeToFinish = reduceNumByPercent(timeToFinish, degItem.boost); - } + for (const degItem of degItemBeingUsed) { + boosts.push(`${degItem.boost}% for ${degItem.item.name}`); + timeToFinish = reduceNumByPercent(timeToFinish, degItem.boost); } if (monster.equippedItemBoosts) { @@ -697,13 +770,13 @@ export async function minionKillCommand( quantity = Math.max(1, quantity); if (isOnTask) { - let effectiveQtyRemaining = usersTask.currentTask!.quantity_remaining; + let effectiveQtyRemaining = usersTask.currentTask?.quantity_remaining; if ( monster.id === Monsters.KrilTsutsaroth.id && - usersTask.currentTask!.monster_id !== Monsters.KrilTsutsaroth.id + usersTask.currentTask?.monster_id !== Monsters.KrilTsutsaroth.id ) { effectiveQtyRemaining = Math.ceil(effectiveQtyRemaining / 2); - } else if (monster.id === Monsters.Kreearra.id && usersTask.currentTask!.monster_id !== Monsters.Kreearra.id) { + } else if (monster.id === Monsters.Kreearra.id && usersTask.currentTask?.monster_id !== Monsters.Kreearra.id) { effectiveQtyRemaining = Math.ceil(effectiveQtyRemaining / 4); } else if ( monster.id === Monsters.GrotesqueGuardians.id && @@ -949,11 +1022,11 @@ export async function minionKillCommand( return 'This monster is temporarily unable to be killed with a Deathtouched dart.'; } usedDart = true; - await userStatsUpdate(user.id, () => ({ + await userStatsUpdate(user.id, { death_touched_darts_used: { increment: 1 } - })); + }); } if (monster.name === 'Koschei the deathless') { return 'You send your minion off to fight Koschei, before they even get close, they feel an immense, powerful fear and return back.'; @@ -965,7 +1038,7 @@ export async function minionKillCommand( let hasDied: boolean | undefined = undefined; let hasWildySupplies = undefined; - if (monster.canBePked) { + if (isInWilderness && ![BSOMonsters.Malygos.id, BSOMonsters.Treebeard.id].includes(monster.id)) { await increaseWildEvasionXp(user, duration); thePkCount = 0; hasDied = false; @@ -996,7 +1069,7 @@ export async function minionKillCommand( } else { antiPKSupplies.add('Super restore(4)', antiPkRestoresNeeded); } - if (user.bank.amount('Blighted karambwan') >= antiPkKarambwanNeeded) { + if (user.bank.amount('Blighted karambwan') >= antiPkKarambwanNeeded + 20) { antiPKSupplies.add('Blighted karambwan', antiPkKarambwanNeeded); } else { antiPKSupplies.add('Cooked karambwan', antiPkKarambwanNeeded); @@ -1022,7 +1095,8 @@ export async function minionKillCommand( wildyPeak!, monster, duration, - hasWildySupplies + hasWildySupplies, + cannonMulti ); thePkCount = pkCount; hasDied = died; @@ -1030,7 +1104,7 @@ export async function minionKillCommand( } // Check food - let foodStr: string = ''; + let foodStr = ''; // Find best eatable boost and add 1% extra const noFoodBoost = Math.floor(Math.max(...Eatables.map(eatable => eatable.pvmBoost ?? 0)) + 1); if (monster.healAmountNeeded && monster.attackStyleToUse && monster.attackStylesUsed) { @@ -1038,7 +1112,7 @@ export async function minionKillCommand( foodStr += foodMessages; let gearToCheck: GearSetupType = convertAttackStyleToGearSetup(monster.attackStyleToUse); - if (monster.wildy) gearToCheck = 'wildy'; + if (isInWilderness) gearToCheck = 'wildy'; try { const { foodRemoved, reductions, reductionRatio } = await removeFoodFromUser({ @@ -1046,11 +1120,11 @@ export async function minionKillCommand( totalHealingNeeded: healAmountNeeded * quantity, healPerAction: Math.ceil(healAmountNeeded / quantity), activityName: monster.name, - attackStylesUsed: monster.wildy + attackStylesUsed: isInWilderness ? ['wildy'] : uniqueArr([...objectKeys(monster.minimumGearRequirements ?? {}), gearToCheck]), learningPercentage: percentReduced, - isWilderness: monster.wildy, + isWilderness: isInWilderness, minimumHealAmount: monster.minimumFoodHealAmount }); @@ -1105,8 +1179,8 @@ export async function minionKillCommand( // Remove items after food calc to prevent losing items if the user doesn't have the right amount of food. Example: Mossy key if (lootToRemove.length > 0) { - updateBankSetting('economyStats_PVMCost', lootToRemove); - await user.specialRemoveItems(lootToRemove, { wildy: monster.wildy ? true : false }); + await updateBankSetting('economyStats_PVMCost', lootToRemove); + await user.specialRemoveItems(lootToRemove, { wildy: !!isInWilderness }); totalCost.add(lootToRemove); } @@ -1126,20 +1200,21 @@ export async function minionKillCommand( } await addSubTaskToActivityTask({ - monsterID: monster.id, + mi: monster.id, userID: user.id, channelID: channelID.toString(), - quantity, + q: quantity, iQty: inputQuantity, duration, type: 'MonsterKilling', usingCannon: !usingCannon ? undefined : usingCannon, cannonMulti: !cannonMulti ? undefined : cannonMulti, chinning: !chinning ? undefined : chinning, - burstOrBarrage: !burstOrBarrage ? undefined : burstOrBarrage, + bob: !burstOrBarrage ? undefined : burstOrBarrage, died: hasDied, pkEncounters: thePkCount, - hasWildySupplies + hasWildySupplies, + isInWilderness }); if (usedDart) { @@ -1197,7 +1272,7 @@ export async function monsterInfo(user: MUser, name: string): Promise 0) { timeToFinish = reduceNumByPercent(timeToFinish, boostPercent); - let boostString = messages.join(' ').replace(RegExp('[0-9]{2}% for '), ''); + const boostString = messages.join(' ').replace(/[0-9]{2}% for /, ''); ownedBoostItems.push(`${boostString}`); totalItemBoost += boostPercent; } @@ -1269,7 +1344,7 @@ export async function monsterInfo(user: MUser, name: string): Promise 0) { itemRequirements.push(`**Items Required:** ${formatItemReqs(monster.itemsRequired)}\n`); } @@ -1280,8 +1355,8 @@ export async function monsterInfo(user: MUser, name: string): Promise 0) { itemRequirements.push( `**Healing Required:** ${gearReductions}\nYou require ${ @@ -1293,7 +1368,7 @@ export async function monsterInfo(user: MUser, name: string): Promise `${i[0]}KC`), - datasets: [ - { - data: values.map(i => i[1]) - } - ] - }, - options: { - plugins: { - title: { display: true, text: 'Death Chance vs Kill Count' }, - datalabels: { - font: { - weight: 'bolder' - }, - formatter(value) { - return `${value}%`; - } - }, - legend: { - display: false - } - }, - scales: { - y: { - min: 1, - max: 100, - ticks: { - callback(value) { - return `${value}%`; - } - } - } - } - } - }; - const chart = await generateChart(options); - response.files = [chart]; - } - return response; } diff --git a/src/mahoji/lib/abstracted_commands/minionStatusCommand.ts b/src/mahoji/lib/abstracted_commands/minionStatusCommand.ts index 54223ac2734..52842e7a322 100644 --- a/src/mahoji/lib/abstracted_commands/minionStatusCommand.ts +++ b/src/mahoji/lib/abstracted_commands/minionStatusCommand.ts @@ -1,13 +1,13 @@ import { toTitleCase } from '@oldschoolgg/toolkit'; -import { BaseMessageOptions, ButtonBuilder, ButtonStyle, ComponentType } from 'discord.js'; +import type { BaseMessageOptions } from 'discord.js'; +import { ButtonBuilder, ButtonStyle, ComponentType } from 'discord.js'; import { roll, stripNonAlphanumeric } from 'e'; import { ClueTiers } from '../../../lib/clues/clueTiers'; -import { BitField, Emoji, minionBuyButton, PerkTier } from '../../../lib/constants'; +import { BitField, Emoji, PerkTier, minionBuyButton } from '../../../lib/constants'; import { getUsersFishingContestDetails } from '../../../lib/fishingContest'; -import { clArrayUpdate } from '../../../lib/handleNewCLItems'; import { roboChimpSyncData, roboChimpUserFetch } from '../../../lib/roboChimp'; -import { prisma } from '../../../lib/settings/prisma'; + import { makeComponents } from '../../../lib/util'; import { makeAutoContractButton, @@ -60,18 +60,16 @@ async function fetchPinnedTrips(userID: string) { export async function minionStatusCommand(user: MUser, channelID: string): Promise { const { minionIsBusy } = user; - const [roboChimpUser, birdhouseDetails, gearPresetButtons, pinnedTripButtons, fishingResult, dailyIsReady] = - await Promise.all([ - roboChimpUserFetch(user.id), - minionIsBusy ? { isReady: false } : calculateBirdhouseDetails(user.id), - minionIsBusy ? [] : fetchFavoriteGearPresets(user.id), - minionIsBusy ? [] : fetchPinnedTrips(user.id), - getUsersFishingContestDetails(user), - isUsersDailyReady(user) - ]); - - roboChimpSyncData(user); - await clArrayUpdate(user, user.cl); + const birdhouseDetails = minionIsBusy ? { isReady: false } : calculateBirdhouseDetails(user); + const [roboChimpUser, gearPresetButtons, pinnedTripButtons, fishingResult, dailyIsReady] = await Promise.all([ + roboChimpUserFetch(user.id), + minionIsBusy ? [] : fetchFavoriteGearPresets(user.id), + minionIsBusy ? [] : fetchPinnedTrips(user.id), + getUsersFishingContestDetails(user), + isUsersDailyReady(user) + ]); + + await roboChimpSyncData(user); if (user.user.cached_networth_value === null || roll(100)) { await user.update({ cached_networth_value: (await user.calculateNetWorth()).value diff --git a/src/mahoji/lib/abstracted_commands/moktangCommand.ts b/src/mahoji/lib/abstracted_commands/moktangCommand.ts index 889bc00fd75..3de0a3908d3 100644 --- a/src/mahoji/lib/abstracted_commands/moktangCommand.ts +++ b/src/mahoji/lib/abstracted_commands/moktangCommand.ts @@ -6,7 +6,7 @@ import { dwarvenOutfit } from '../../../lib/data/CollectionsExport'; import { trackLoot } from '../../../lib/lootTrack'; import { SkillsEnum } from '../../../lib/skilling/types'; import { PercentCounter } from '../../../lib/structures/PercentCounter'; -import { MoktangTaskOptions } from '../../../lib/types/minions'; +import type { MoktangTaskOptions } from '../../../lib/types/minions'; import { formatDuration, itemNameFromID } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; diff --git a/src/mahoji/lib/abstracted_commands/monkeyRumbleCommand.ts b/src/mahoji/lib/abstracted_commands/monkeyRumbleCommand.ts index 5545460b516..98ec87fd315 100644 --- a/src/mahoji/lib/abstracted_commands/monkeyRumbleCommand.ts +++ b/src/mahoji/lib/abstracted_commands/monkeyRumbleCommand.ts @@ -1,21 +1,21 @@ -import { randArrItem, reduceNumByPercent, Time } from 'e'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; +import type { CommandResponse } from '@oldschoolgg/toolkit'; +import { Time, randArrItem, reduceNumByPercent } from 'e'; import { Bank } from 'oldschooljs'; import { Emoji } from '../../../lib/constants'; import { + type Monkey, + TOTAL_MONKEYS, fightingMessages, getMonkeyPhrase, getRandomMonkey, - Monkey, monkeyEatables, monkeyHeadImage, monkeyTierOfUser, - monkeyTiers, - TOTAL_MONKEYS + monkeyTiers } from '../../../lib/monkeyRumble'; import { getMinigameEntity } from '../../../lib/settings/minigames'; -import { MonkeyRumbleOptions } from '../../../lib/types/minions'; +import type { MonkeyRumbleOptions } from '../../../lib/types/minions'; import { formatDuration } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; @@ -128,9 +128,7 @@ export async function monkeyRumbleCommand(user: MUser, channelID: string): Comma .map(m => `${m.special ? `${Emoji.Purple} ` : ''}${m.name}`) .join(', ')}). The trip will take ${formatDuration( duration - )}. Removed ${cost} from your bank. **1 in ${chanceOfSpecial} chance of a monkey being special, with ${quantity} monkeys in this trip, there was a 1 in ${( - chanceOfSpecial / quantity - ).toFixed(2)} chance that one of them would be special.**`; + )}. Removed ${cost} from your bank. **1 in ${chanceOfSpecial} chance of a monkey being special, with ${quantity} monkeys in this trip, there was a 1 in ${(chanceOfSpecial / quantity).toFixed(2)} chance that one of them would be special.**`; if (boosts.length > 0) { str += `\n\n**Boosts:** ${boosts.join(', ')}.`; } diff --git a/src/mahoji/lib/abstracted_commands/motherlodeMineCommand.ts b/src/mahoji/lib/abstracted_commands/motherlodeMineCommand.ts index fbaa95dc41a..d0f83b1e654 100644 --- a/src/mahoji/lib/abstracted_commands/motherlodeMineCommand.ts +++ b/src/mahoji/lib/abstracted_commands/motherlodeMineCommand.ts @@ -4,7 +4,7 @@ import { randomVariation } from 'oldschooljs/dist/util'; import { determineMiningTime } from '../../../lib/skilling/functions/determineMiningTime'; import { pickaxes } from '../../../lib/skilling/functions/miningBoosts'; import Mining from '../../../lib/skilling/skills/mining'; -import { MotherlodeMiningActivityTaskOptions } from '../../../lib/types/minions'; +import type { MotherlodeMiningActivityTaskOptions } from '../../../lib/types/minions'; import { formatDuration, itemNameFromID } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; @@ -62,7 +62,7 @@ export async function motherlodeMineCommand({ const powermine = false; // Calculate the time it takes to mine specific quantity or as many as possible - let [duration, newQuantity] = determineMiningTime({ + const [duration, newQuantity] = determineMiningTime({ quantity, ore: motherlode, ticksBetweenRolls: currentPickaxe.ticksBetweenRolls, diff --git a/src/mahoji/lib/abstracted_commands/naxxusCommand.ts b/src/mahoji/lib/abstracted_commands/naxxusCommand.ts index 04f04ad7b1d..fb5afcb0319 100644 --- a/src/mahoji/lib/abstracted_commands/naxxusCommand.ts +++ b/src/mahoji/lib/abstracted_commands/naxxusCommand.ts @@ -1,14 +1,14 @@ import { EmbedBuilder } from 'discord.js'; import { calcPercentOfNum, calcWhatPercent, increaseNumByPercent, reduceNumByPercent } from 'e'; import { Bank } from 'oldschooljs'; -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; -import { checkUserCanUseDegradeableItem, degradeablePvmBoostItems, degradeItem } from '../../../lib/degradeableItems'; -import { GearStats } from '../../../lib/gear'; +import { checkUserCanUseDegradeableItem, degradeItem, degradeablePvmBoostItems } from '../../../lib/degradeableItems'; +import type { GearStats } from '../../../lib/gear'; import { trackLoot } from '../../../lib/lootTrack'; -import { Naxxus, NAXXUS_HP } from '../../../lib/minions/data/killableMonsters/custom/bosses/Naxxus'; +import { NAXXUS_HP, Naxxus } from '../../../lib/minions/data/killableMonsters/custom/bosses/Naxxus'; import { Gear } from '../../../lib/structures/Gear'; -import { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; +import type { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; import { formatDuration, isWeekend } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; @@ -172,7 +172,7 @@ export async function naxxusCommand(user: MUser, channelID: string, quantity: nu )}% of max melee, ${Math.ceil(mage)}% of max mage]` ); - for (let itemBoost of itemBoosts) { + for (const itemBoost of itemBoosts) { if (user.gear[itemBoost.setup].hasEquipped(itemBoost.item.id)) { effectiveTime = reduceNumByPercent(effectiveTime, itemBoost.boost); boosts.push(`${itemBoost.boost}% ${itemBoost.item.name}`); @@ -258,15 +258,15 @@ export async function naxxusCommand(user: MUser, channelID: string, quantity: nu const embed = new EmbedBuilder() .setDescription( - `Your minion is now attempting to kill ${quantity}x Naxxus. The trip will take ${formatDuration(duration)}. - **Supplies**: ${foodBank.toString()}. - ${boosts.length > 0 ? `**Boosts:** ${boosts.join(', ')}` : ''}` + `**Supplies**: ${foodBank.toString()}. +${boosts.length > 0 ? `**Boosts:** ${boosts.join(', ')}` : ''}` ) .setImage( 'https://cdn.discordapp.com/attachments/920771763976167455/935659463434698783/179ad8548cf42d494bfb473171a1124b.jpg' ); return { + content: `Your minion is now attempting to kill ${quantity}x Naxxus, the trip will take ${formatDuration(duration)}.`, embeds: [embed.data] }; } diff --git a/src/mahoji/lib/abstracted_commands/nexCommand.ts b/src/mahoji/lib/abstracted_commands/nexCommand.ts index 14e44b082d4..a29a95fa20e 100644 --- a/src/mahoji/lib/abstracted_commands/nexCommand.ts +++ b/src/mahoji/lib/abstracted_commands/nexCommand.ts @@ -1,17 +1,17 @@ -import { ChatInputCommandInteraction } from 'discord.js'; -import { increaseNumByPercent, reduceNumByPercent, round, Time } from 'e'; import { Bank } from 'oldschooljs'; +import { channelIsSendable, formatDuration, isWeekend } from '@oldschoolgg/toolkit'; +import type { ChatInputCommandInteraction } from 'discord.js'; +import { Time, increaseNumByPercent, reduceNumByPercent, round } from 'e'; import { calcBossFood } from '../../../lib/bso/calcBossFood'; import { gorajanArcherOutfit, pernixOutfit } from '../../../lib/data/CollectionsExport'; import { trackLoot } from '../../../lib/lootTrack'; import { calculateMonsterFood } from '../../../lib/minions/functions'; -import { KillableMonster } from '../../../lib/minions/types'; +import type { KillableMonster } from '../../../lib/minions/types'; import { NexMonster } from '../../../lib/nex'; import { setupParty } from '../../../lib/party'; -import { MakePartyOptions } from '../../../lib/types'; -import { BossActivityTaskOptions } from '../../../lib/types/minions'; -import { channelIsSendable, formatDuration, isWeekend } from '../../../lib/util'; +import type { MakePartyOptions } from '../../../lib/types'; +import type { BossActivityTaskOptions } from '../../../lib/types/minions'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import calcDurQty from '../../../lib/util/calcMassDurationQuantity'; import { getNexGearStats } from '../../../lib/util/getNexGearStats'; @@ -140,7 +140,7 @@ export async function nexCommand( users.map(u => u.id) ); debugStr += `**${user.usernameOrMention}**: `; - let msgs = []; + const msgs = []; const rangeGear = user.gear.range; if (rangeGear.hasEquipped(pernixOutfit, true, true)) { @@ -241,7 +241,7 @@ export async function nexCommand( if (users.length === 5) minDuration = 1.2; if (users.length >= 6) minDuration = 1; - let durQtyRes = await calcDurQty( + const durQtyRes = await calcDurQty( users, { ...NexMonster, timeToFinish: effectiveTime }, inputQuantity, @@ -249,12 +249,12 @@ export async function nexCommand( Time.Minute * 30 ); if (typeof durQtyRes === 'string') return durQtyRes; - let [quantity, duration, perKillTime] = durQtyRes; + const [quantity, duration, perKillTime] = durQtyRes; const secondCheck = await checkReqs(users, NexMonster, quantity); if (secondCheck) return secondCheck; let foodString = 'Removed brews/restores from users: '; - let foodRemoved: string[] = []; + const foodRemoved: string[] = []; for (const user of users) { const food = await calcBossFood(user, NexMonster, users.length, quantity); if (!user.bank.has(food)) { @@ -303,16 +303,14 @@ export async function nexCommand( let str = type === 'solo' - ? `Your minion is now attempting to kill ${quantity}x Nex. ${foodString} The trip will take ${formatDuration( - duration - )}.` + ? `Your minion is now attempting to kill ${quantity}x Nex. ${foodString} The trip will take ${formatDuration(duration)}.` : `${partyOptions.leader.usernameOrMention}'s party (${users .map(u => u.usernameOrMention) .join(', ')}) is now off to kill ${quantity}x ${NexMonster.name}. Each kill takes ${formatDuration( perKillTime - )} instead of ${formatDuration(NexMonster.timeToFinish)} - the total trip will take ${formatDuration( + )} instead of ${formatDuration(NexMonster.timeToFinish)} - the total trip will take ${formatDuration( duration - )}. ${foodString}`; + )}. ${foodString}`; str += ` \n\n${debugStr}`; diff --git a/src/mahoji/lib/abstracted_commands/nightmareCommand.ts b/src/mahoji/lib/abstracted_commands/nightmareCommand.ts index b1c6a83efd2..22ffadca968 100644 --- a/src/mahoji/lib/abstracted_commands/nightmareCommand.ts +++ b/src/mahoji/lib/abstracted_commands/nightmareCommand.ts @@ -1,5 +1,5 @@ import { mentionCommand } from '@oldschoolgg/toolkit'; -import { reduceNumByPercent, Time } from 'e'; +import { Time, reduceNumByPercent } from 'e'; import { Bank } from 'oldschooljs'; import { BitField, PHOSANI_NIGHTMARE_ID, ZAM_HASTA_CRUSH } from '../../../lib/constants'; @@ -8,9 +8,9 @@ import { trackLoot } from '../../../lib/lootTrack'; import { NightmareMonster } from '../../../lib/minions/data/killableMonsters'; import { calculateMonsterFood } from '../../../lib/minions/functions'; import removeFoodFromUser from '../../../lib/minions/functions/removeFoodFromUser'; -import { KillableMonster } from '../../../lib/minions/types'; +import type { KillableMonster } from '../../../lib/minions/types'; import { Gear } from '../../../lib/structures/Gear'; -import { NightmareActivityTaskOptions } from '../../../lib/types/minions'; +import type { NightmareActivityTaskOptions } from '../../../lib/types/minions'; import { formatDuration, hasSkillReqs } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import calcDurQty from '../../../lib/util/calcMassDurationQuantity'; @@ -44,7 +44,7 @@ const inquisitorItems = resolveItems([ "Inquisitor's mace" ]); -export const phosaniBISGear = new Gear({ +const phosaniBISGear = new Gear({ head: "Inquisitor's great helm", neck: 'Amulet of torture', body: "Inquisitor's hauberk", @@ -89,7 +89,7 @@ async function checkReqs(user: MUser, monster: KillableMonster, isPhosani: boole return `${user.usernameOrMention} doesn't meet the requirements: ${requirements[1]}.`; } if ((await user.getKC(NightmareMonster.id)) < 50) { - return "You need to have killed The Nightmare atleast 50 times before you can face the Phosani's Nightmare."; + return "You need to have killed The Nightmare at least 50 times before you can face the Phosani's Nightmare."; } } } @@ -100,13 +100,13 @@ function perUserCost(user: MUser, quantity: number, isPhosani: boolean, hasShado const sangCharges = sangChargesPerKc * quantity; if (isPhosani) { if (hasShadow && user.user.tum_shadow_charges < tumCharges) { - return `You need atleast ${tumCharges} Tumeken's shadow charges to use it, otherwise it has to be unequipped: ${mentionCommand( + return `You need at least ${tumCharges} Tumeken's shadow charges to use it, otherwise it has to be unequipped: ${mentionCommand( globalClient, 'minion', 'charge' )}`; } else if (hasSang && user.user.sang_charges < sangCharges) { - return `You need atleast ${sangCharges} Sanguinesti staff charges to use it, otherwise it has to be unequipped: ${mentionCommand( + return `You need at least ${sangCharges} Sanguinesti staff charges to use it, otherwise it has to be unequipped: ${mentionCommand( globalClient, 'minion', 'charge' @@ -129,8 +129,8 @@ function perUserCost(user: MUser, quantity: number, isPhosani: boolean, hasShado } export async function nightmareCommand(user: MUser, channelID: string, name: string, qty: number | undefined) { - const hasShadow = user.gear.mage.hasEquipped("Tumeken's shadow") ? true : false; - const hasSang = user.gear.mage.hasEquipped('Sanguinesti staff') ? true : false; + const hasShadow = !!user.gear.mage.hasEquipped("Tumeken's shadow"); + const hasSang = !!user.gear.mage.hasEquipped('Sanguinesti staff'); name = name.toLowerCase(); let isPhosani = false; let type: 'solo' | 'mass' = 'solo'; @@ -232,7 +232,7 @@ export async function nightmareCommand(user: MUser, channelID: string, name: str if (hasCob && type === 'solo') { effectiveTime /= 2; } - let durQtyRes = await calcDurQty( + const durQtyRes = await calcDurQty( users, { ...NightmareMonster, timeToFinish: effectiveTime }, qty, @@ -240,7 +240,7 @@ export async function nightmareCommand(user: MUser, channelID: string, name: str Time.Minute * 30 ); if (typeof durQtyRes === 'string') return durQtyRes; - let [quantity, duration, perKillTime] = durQtyRes; + const [quantity, duration, perKillTime] = durQtyRes; const totalCost = new Bank(); let soloFoodUsage: Bank | null = null; @@ -248,7 +248,7 @@ export async function nightmareCommand(user: MUser, channelID: string, name: str const cost = perUserCost(user, quantity, isPhosani, hasShadow, hasSang); if (typeof cost === 'string') return cost; - let healingMod = isPhosani ? 1.5 : 1; + const healingMod = isPhosani ? 1.5 : 1; try { const { foodRemoved } = await removeFoodFromUser({ user, @@ -317,11 +317,11 @@ export async function nightmareCommand(user: MUser, channelID: string, name: str ${soloBoosts.length > 0 ? `**Boosts:** ${soloBoosts.join(', ')}` : ''}` : `${user.usernameOrMention}'s party of ${ users.length - } is now off to kill ${quantity}x Nightmare. Each kill takes ${formatDuration( + } is now off to kill ${quantity}x Nightmare. Each kill takes ${formatDuration( perKillTime - )} instead of ${formatDuration( + )} instead of ${formatDuration( NightmareMonster.timeToFinish - )} - the total trip will take ${formatDuration(duration)}.`; + )} - the total trip will take ${formatDuration(duration)}.`; if (hasCob && type === 'solo') { str += '\n2x Boost from Cob\n'; } @@ -330,8 +330,8 @@ ${soloBoosts.length > 0 ? `**Boosts:** ${soloBoosts.join(', ')}` : ''}` ? hasShadow ? ` Your minion is using ${shadowChargesPerKc * quantity} Tumeken's shadow charges. ` : hasSang - ? ` Your minion is using ${sangChargesPerKc * quantity} Sanguinesti staff charges. ` - : '' + ? ` Your minion is using ${sangChargesPerKc * quantity} Sanguinesti staff charges. ` + : '' : '' }`; diff --git a/src/mahoji/lib/abstracted_commands/nightmareZoneCommand.ts b/src/mahoji/lib/abstracted_commands/nightmareZoneCommand.ts index 4b472712a3c..f8861fbd0c4 100644 --- a/src/mahoji/lib/abstracted_commands/nightmareZoneCommand.ts +++ b/src/mahoji/lib/abstracted_commands/nightmareZoneCommand.ts @@ -1,21 +1,21 @@ -import { ChatInputCommandInteraction } from 'discord.js'; -import { calcWhatPercent, reduceNumByPercent, round, sumArr, Time } from 'e'; +import type { ChatInputCommandInteraction } from 'discord.js'; +import { Time, calcWhatPercent, reduceNumByPercent, round, sumArr } from 'e'; import { Bank } from 'oldschooljs'; -import { NMZStrategy } from '../../../lib/constants'; +import type { NMZStrategy } from '../../../lib/constants'; import { trackLoot } from '../../../lib/lootTrack'; +import { MAX_QP } from '../../../lib/minions/data/quests'; import { resolveAttackStyles } from '../../../lib/minions/functions'; import { getMinigameEntity } from '../../../lib/settings/minigames'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { Skills } from '../../../lib/types'; +import type { Skills } from '../../../lib/types'; import { formatDuration, hasSkillReqs, stringMatches } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; import getOSItem from '../../../lib/util/getOSItem'; import { handleMahojiConfirmation } from '../../../lib/util/handleMahojiConfirmation'; import { updateBankSetting } from '../../../lib/util/updateBankSetting'; -import { NightmareZoneActivityTaskOptions } from './../../../lib/types/minions'; -import { MAX_QP } from './questCommand'; +import type { NightmareZoneActivityTaskOptions } from './../../../lib/types/minions'; const itemBoosts = [ // Special weapons @@ -426,7 +426,7 @@ export async function nightmareZoneShopCommand( .join(', ')}`; } - let costPerItem = shopItem.cost; + const costPerItem = shopItem.cost; const cost = quantity * costPerItem; if (cost > currentUserPoints) { return `You don't have enough Nightmare Zone points to buy ${quantity.toLocaleString()}x ${ @@ -456,9 +456,7 @@ export async function nightmareZoneShopCommand( } }); - return `You successfully bought **${quantity.toLocaleString()}x ${shopItem.name}** for ${( - costPerItem * quantity - ).toLocaleString()} Nightmare Zone points.\nYou now have ${currentUserPoints - cost} Nightmare Zone points left.`; + return `You successfully bought **${quantity.toLocaleString()}x ${shopItem.name}** for ${(costPerItem * quantity).toLocaleString()} Nightmare Zone points.\nYou now have ${currentUserPoints - cost} Nightmare Zone points left.`; } export async function nightmareZoneImbueCommand(user: MUser, input = '') { diff --git a/src/mahoji/lib/abstracted_commands/odsCommand.ts b/src/mahoji/lib/abstracted_commands/odsCommand.ts index 30b8e495f33..8c830881a71 100644 --- a/src/mahoji/lib/abstracted_commands/odsCommand.ts +++ b/src/mahoji/lib/abstracted_commands/odsCommand.ts @@ -1,12 +1,12 @@ -import { randInt, reduceNumByPercent, Time } from 'e'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; +import type { CommandResponse } from '@oldschoolgg/toolkit'; +import { Time, randInt, reduceNumByPercent } from 'e'; import { Bank } from 'oldschooljs'; import { randomVariation } from 'oldschooljs/dist/util'; import { Emoji } from '../../../lib/constants'; import { trackLoot } from '../../../lib/lootTrack'; import { getMinigameEntity } from '../../../lib/settings/minigames'; -import { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; +import type { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; import { formatDuration, stringMatches } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; diff --git a/src/mahoji/lib/abstracted_commands/openCommand.ts b/src/mahoji/lib/abstracted_commands/openCommand.ts index 35a49d29e9e..036cb0ffefa 100644 --- a/src/mahoji/lib/abstracted_commands/openCommand.ts +++ b/src/mahoji/lib/abstracted_commands/openCommand.ts @@ -1,15 +1,13 @@ -import { stringMatches } from '@oldschoolgg/toolkit'; -import { ButtonBuilder, ChatInputCommandInteraction } from 'discord.js'; +import { type CommandResponse, PerkTier, stringMatches } from '@oldschoolgg/toolkit'; +import type { ButtonBuilder, ChatInputCommandInteraction } from 'discord.js'; import { noOp, notEmpty, percentChance, randArrItem, shuffleArr, uniqueArr } from 'e'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; import { Bank } from 'oldschooljs'; import { ClueTiers } from '../../../lib/clues/clueTiers'; import { buildClueButtons } from '../../../lib/clues/clueUtils'; -import { BitField, Emoji, PerkTier } from '../../../lib/constants'; -import { allOpenables, getOpenableLoot, UnifiedOpenable } from '../../../lib/openables'; +import { BitField, Emoji } from '../../../lib/constants'; +import { type UnifiedOpenable, allOpenables, getOpenableLoot } from '../../../lib/openables'; import { roboChimpUserFetch } from '../../../lib/roboChimp'; -import { prisma } from '../../../lib/settings/prisma'; import { assert, itemNameFromID, makeComponents } from '../../../lib/util'; import { checkElderClueRequirements } from '../../../lib/util/elderClueRequirements'; import getOSItem, { getItem } from '../../../lib/util/getOSItem'; @@ -59,7 +57,7 @@ export async function abstractedOpenUntilCommand(userID: string, name: string, o const cost = new Bank(); const loot = new Bank(); let amountOpened = 0; - let max = Math.min(100, amountOfThisOpenableOwned); + const max = Math.min(100, amountOfThisOpenableOwned); const totalLeaguesPoints = (await roboChimpUserFetch(user.id)).leagues_points_total; for (let i = 0; i < max; i++) { cost.add(openable.openedItem.id); @@ -106,7 +104,7 @@ const itemsThatDontAddToTempCL = resolveItems([ 'Monkey crate', 'Magic crate', 'Chimpling jar', - ...ClueTiers.map(t => [t.id, t.scrollID]).flat() + ...ClueTiers.flatMap(t => [t.id, t.scrollID]) ]); async function finalizeOpening({ @@ -132,7 +130,7 @@ async function finalizeOpening({ let smokeyMsg: string | null = null; if (hasSmokey || hasOcto) { - let bonuses = []; + const bonuses = []; const totalLeaguesPoints = (await roboChimpUserFetch(user.id)).leagues_points_total; for (const openable of openables) { if (!openable.smokeyApplies) continue; @@ -236,14 +234,15 @@ async function finalizeOpening({ const perkTier = user.perkTier(); const components: ButtonBuilder[] = buildClueButtons(loot, perkTier, user); - let response: Awaited = { + const response: Awaited = { files: [image.file], content: `You have now opened a total of ${openedStr} ${messages.join(', ')}`, components: components.length > 0 ? makeComponents(components) : undefined }; if (response.content!.length > 1900) { - response.files!.push({ name: 'response.txt', attachment: Buffer.from(response.content!) }); + response.files = [{ name: 'response.txt', attachment: Buffer.from(response.content!) }]; + response.content = 'Due to opening so many things at once, you will have to download the attached text file to read the response.'; } @@ -264,7 +263,7 @@ export async function abstractedOpenCommand( ? allOpenables.filter( ({ openedItem, excludeFromOpenAll }) => user.bank.has(openedItem.id) && !favorites.includes(openedItem.id) && excludeFromOpenAll !== true - ) + ) : names .map(name => allOpenables.find(o => o.aliases.some(alias => stringMatches(alias, name)))) .filter(notEmpty); diff --git a/src/mahoji/lib/abstracted_commands/otherActivitiesCommand.ts b/src/mahoji/lib/abstracted_commands/otherActivitiesCommand.ts index bdc394e9f9a..199fa14ed65 100644 --- a/src/mahoji/lib/abstracted_commands/otherActivitiesCommand.ts +++ b/src/mahoji/lib/abstracted_commands/otherActivitiesCommand.ts @@ -1,6 +1,7 @@ import { activity_type_enum } from '@prisma/client'; import { championsChallengeCommand } from './championsChallenge'; +import { combatRingCommand } from './combatRingCommand'; import { strongHoldOfSecurityCommand } from './strongHoldOfSecurityCommand'; export const otherActivities = [ @@ -13,6 +14,11 @@ export const otherActivities = [ name: 'Stronghold of Security', command: championsChallengeCommand, type: activity_type_enum.StrongholdOfSecurity + }, + { + name: 'Combat Ring (Shayzien)', + command: combatRingCommand, + type: activity_type_enum.CombatRing } ]; @@ -23,5 +29,8 @@ export function otherActivitiesCommand(type: string, user: MUser, channelID: str if (type === 'StrongholdOfSecurity') { return strongHoldOfSecurityCommand(user, channelID); } + if (type === 'CombatRing') { + return combatRingCommand(user, channelID); + } return 'Invalid activity type.'; } diff --git a/src/mahoji/lib/abstracted_commands/pestControlCommand.ts b/src/mahoji/lib/abstracted_commands/pestControlCommand.ts index bb59b5b5f66..6541f70d32f 100644 --- a/src/mahoji/lib/abstracted_commands/pestControlCommand.ts +++ b/src/mahoji/lib/abstracted_commands/pestControlCommand.ts @@ -1,11 +1,11 @@ import { toTitleCase } from '@oldschoolgg/toolkit'; -import { ChatInputCommandInteraction } from 'discord.js'; -import { reduceNumByPercent, Time } from 'e'; +import type { ChatInputCommandInteraction } from 'discord.js'; +import { Time, reduceNumByPercent } from 'e'; import { Bank } from 'oldschooljs'; -import { userhasDiaryTier, WesternProv } from '../../../lib/diaries'; +import { WesternProv, userhasDiaryTier } from '../../../lib/diaries'; import { getMinigameScore } from '../../../lib/settings/settings'; -import { SkillsEnum } from '../../../lib/skilling/types'; +import type { SkillsEnum } from '../../../lib/skilling/types'; import type { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; import { formatDuration, hasSkillReqs, stringMatches } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; @@ -15,7 +15,7 @@ import { handleMahojiConfirmation } from '../../../lib/util/handleMahojiConfirma import { minionIsBusy } from '../../../lib/util/minionIsBusy'; import { userStatsUpdate } from '../../mahojiSettings'; -let itemBoosts = [ +const itemBoosts = [ [['Abyssal whip', 'Abyssal tentacle'].map(getOSItem), 12], [['Barrows gloves', 'Ferocious gloves'].map(getOSItem), 4], [['Amulet of fury', 'Amulet of torture', 'Amulet of fury (or)', 'Amulet of torture (or)'].map(getOSItem), 5], @@ -25,7 +25,7 @@ let itemBoosts = [ export function getBoatType(user: MUser, cbLevel: number) { let type: 'veteran' | 'intermediate' | 'novice' = 'intermediate'; - let pointsPerGame: number = 1; + let pointsPerGame = 1; if (cbLevel >= 100) { type = 'veteran'; @@ -58,7 +58,7 @@ export function getBoatType(user: MUser, cbLevel: number) { }; } -let baseStats = { +const baseStats = { attack: 42, strength: 42, defence: 42, @@ -123,7 +123,7 @@ export const pestControlBuyables = [ } ]; -let xpMultiplier = { +const xpMultiplier = { prayer: 18, magic: 32, ranged: 32, @@ -148,7 +148,7 @@ export async function pestControlBuyCommand(user: MUser, input: string) { return `You don't have enough Void knight commendation points to buy the ${item.name}. You need ${cost}, but you have only ${balance}.`; } - let [hasReqs, str] = hasSkillReqs(user, buyable.requiredStats); + const [hasReqs, str] = hasSkillReqs(user, buyable.requiredStats); if (!hasReqs) { return `You need ${str} to buy this item.`; } @@ -189,7 +189,7 @@ export async function pestControlStartCommand(user: MUser, channelID: string) { const maxLength = calcMaxTripLength(user, 'PestControl'); const gear = user.gear.melee; - let boosts = []; + const boosts = []; for (const [items, percent] of itemBoosts) { for (const item of items) { if (gear.hasEquipped(item.name)) { @@ -202,7 +202,7 @@ export async function pestControlStartCommand(user: MUser, channelID: string) { const quantity = Math.floor(maxLength / gameLength); - let duration = quantity * gameLength; + const duration = quantity * gameLength; await addSubTaskToActivityTask({ userID: user.id, @@ -213,7 +213,7 @@ export async function pestControlStartCommand(user: MUser, channelID: string) { minigameID: 'pest_control' }); - let { boatType } = getBoatType(user, user.combatLevel); + const { boatType } = getBoatType(user, user.combatLevel); let str = `${ user.minionName diff --git a/src/mahoji/lib/abstracted_commands/pohCommand.ts b/src/mahoji/lib/abstracted_commands/pohCommand.ts index 24b479eaf0b..069f60b8f4e 100644 --- a/src/mahoji/lib/abstracted_commands/pohCommand.ts +++ b/src/mahoji/lib/abstracted_commands/pohCommand.ts @@ -1,11 +1,11 @@ import { stringMatches } from '@oldschoolgg/toolkit'; -import { ChatInputCommandInteraction } from 'discord.js'; +import type { ChatInputCommandInteraction } from 'discord.js'; import { Bank } from 'oldschooljs'; import { BitField } from '../../../lib/constants'; -import { getPOHObject, GroupedPohObjects, itemsNotRefundable, PoHObjects } from '../../../lib/poh'; +import { GroupedPohObjects, PoHObjects, getPOHObject, itemsNotRefundable } from '../../../lib/poh'; import { pohImageGenerator } from '../../../lib/pohImage'; -import { prisma } from '../../../lib/settings/prisma'; + import { SkillsEnum } from '../../../lib/skilling/types'; import { formatSkillRequirements, itemNameFromID } from '../../../lib/util'; import getOSItem from '../../../lib/util/getOSItem'; diff --git a/src/mahoji/lib/abstracted_commands/puroPuroCommand.ts b/src/mahoji/lib/abstracted_commands/puroPuroCommand.ts index 4c5b8928893..c4142eb55d7 100644 --- a/src/mahoji/lib/abstracted_commands/puroPuroCommand.ts +++ b/src/mahoji/lib/abstracted_commands/puroPuroCommand.ts @@ -1,8 +1,8 @@ import { Time } from 'e'; -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; -import { Skills } from '../../../lib/types'; -import { PuroPuroActivityTaskOptions } from '../../../lib/types/minions'; +import type { Skills } from '../../../lib/types'; +import type { PuroPuroActivityTaskOptions } from '../../../lib/types/minions'; import { formatDuration, hasSkillReqs, itemID, stringMatches } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; @@ -30,7 +30,7 @@ const darkLureSkillRequirements: Skills = { strength: 16 }; -export const puroOptions: PuroImpling[] = [ +const puroOptions: PuroImpling[] = [ { name: 'All Implings', hunterLevel: 17, spell: true, item: null, tier: 1 }, { name: 'High-tier Implings', hunterLevel: 58, spell: true, item: null, tier: 2 }, { name: 'Eclectic Implings', hunterLevel: 50, spell: false, item: getOSItem('Eclectic impling jar'), tier: 3 }, @@ -68,7 +68,7 @@ export async function puroPuroStartCommand( } if (!impToHunt) return 'Error selecting impling, please try again.'; if (hunterLevel < impToHunt.hunterLevel) - return `${user.minionName} needs atleast level ${impToHunt.hunterLevel} hunter to hunt ${impToHunt.name} in Puro-Puro.`; + return `${user.minionName} needs at least level ${impToHunt.hunterLevel} hunter to hunt ${impToHunt.name} in Puro-Puro.`; if (!darkLure || (darkLure && !impToHunt.spell)) darkLure = false; if (darkLure) { if (user.QP < 9) return 'To use Dark Lure, you need 9 QP.'; diff --git a/src/mahoji/lib/abstracted_commands/pyramidPlunderCommand.ts b/src/mahoji/lib/abstracted_commands/pyramidPlunderCommand.ts index 832ca8a5712..92962582460 100644 --- a/src/mahoji/lib/abstracted_commands/pyramidPlunderCommand.ts +++ b/src/mahoji/lib/abstracted_commands/pyramidPlunderCommand.ts @@ -1,8 +1,8 @@ -import { reduceNumByPercent, Time } from 'e'; +import { Time, reduceNumByPercent } from 'e'; import { plunderBoosts, plunderRooms } from '../../../lib/minions/data/plunder'; import { getMinigameScore } from '../../../lib/settings/minigames'; -import { PlunderActivityTaskOptions } from '../../../lib/types/minions'; +import type { PlunderActivityTaskOptions } from '../../../lib/types/minions'; import { formatDuration } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; @@ -15,7 +15,7 @@ export async function pyramidPlunderCommand(user: MUser, channelID: string) { const thievingLevel = skills.thieving; const minLevel = plunderRooms[0].thievingLevel; if (thievingLevel < minLevel) { - return `You need atleast level ${minLevel} Thieving to do the Pyramid Plunder.`; + return `You need at least level ${minLevel} Thieving to do the Pyramid Plunder.`; } const completableRooms = plunderRooms.filter(room => thievingLevel >= room.thievingLevel); diff --git a/src/mahoji/lib/abstracted_commands/questCommand.ts b/src/mahoji/lib/abstracted_commands/questCommand.ts index cbe8fabcac9..939eb683e7a 100644 --- a/src/mahoji/lib/abstracted_commands/questCommand.ts +++ b/src/mahoji/lib/abstracted_commands/questCommand.ts @@ -1,227 +1,12 @@ -import { sumArr, Time } from 'e'; -import { Bank } from 'oldschooljs'; +import { Time, sumArr } from 'e'; -import { Skills } from '../../../lib/types'; -import { ActivityTaskOptionsWithNoChanges, SpecificQuestOptions } from '../../../lib/types/minions'; +import { MAX_GLOBAL_QP, MAX_QP, quests } from '../../../lib/minions/data/quests'; +import type { ActivityTaskOptionsWithNoChanges, SpecificQuestOptions } from '../../../lib/types/minions'; import { formatDuration, hasSkillReqs } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { minionIsBusy } from '../../../lib/util/minionIsBusy'; import { userHasGracefulEquipped } from '../../mahojiSettings'; -export const MAX_GLOBAL_QP = 5000; - -interface Quest { - id: QuestID; - qp: number; - name: string; - skillReqs?: Skills; - ironmanSkillReqs?: Skills; - qpReq?: number; - rewards?: Bank; - skillsRewards?: { - [skill: string]: number; - }; - combatLevelReq?: number; - prerequisitesQuests?: QuestID[]; - calcTime: (user: MUser) => number; -} - -export enum QuestID { - DesertTreasureII = 1, - ThePathOfGlouphrie = 2, - ChildrenOfTheSun = 3, - DefenderOfVarrock = 4, - TheRibbitingTaleOfALillyPadLabourDispute = 5, - PerilousMoons = 6, - AtFirstLight = 7, - TwilightsPromise = 8 -} - -export const quests: Quest[] = [ - { - id: QuestID.DesertTreasureII, - qp: 5, - name: 'Desert Treasure II - The Fallen Empire', - skillReqs: { - firemaking: 75, - magic: 75, - thieving: 70, - herblore: 62, - runecraft: 60, - construction: 60 - }, - combatLevelReq: 110, - qpReq: 150, - rewards: new Bank().add(28_409, 3).add('Ring of shadows').freeze(), - calcTime: (user: MUser) => { - let duration = Time.Hour * 3; - if (user.combatLevel < 100) { - duration += Time.Minute * 30; - } - if (user.combatLevel < 90) { - duration += Time.Minute * 40; - } - const percentOfBossCL = user.percentOfBossCLFinished(); - if (percentOfBossCL < 10) { - duration += Time.Minute * 20; - } else if (percentOfBossCL < 30) { - duration += Time.Minute * 10; - } else if (percentOfBossCL > 80) { - duration -= Time.Minute * 60; - } else if (percentOfBossCL > 50) { - duration -= Time.Minute * 30; - } - return duration; - } - }, - { - id: QuestID.ThePathOfGlouphrie, - qp: 2, - name: 'The Path of Glouphrie', - skillReqs: { - strength: 60, - slayer: 56, - thieving: 56, - ranged: 47, - agility: 45 - }, - ironmanSkillReqs: { - fletching: 59, - smithing: 59 - }, - combatLevelReq: 50, - qpReq: 10, - rewards: new Bank().add(28_587).add(28_587).add(28_588).add(28_589).add(28_590).freeze(), - calcTime: (user: MUser) => { - let duration = Time.Minute * 10; - if (user.combatLevel < 90) { - duration += Time.Minute * 5; - } - return duration; - } - }, - { - id: QuestID.ChildrenOfTheSun, - qp: 1, - name: 'Children of the Sun', - calcTime: () => { - const duration = Time.Minute * 3; - return duration; - } - }, - { - id: QuestID.DefenderOfVarrock, - qp: 2, - name: 'Defender of Varrock', - skillReqs: { - smithing: 55, - hunter: 52 - }, - combatLevelReq: 65, - qpReq: 20, - // Awaiting item update for the lamp to be added - // rewards: new Bank().add(28_820).freeze(), - skillsRewards: { - smithing: 15_000, - hunter: 15_000 - }, - calcTime: (user: MUser) => { - let duration = Time.Minute * 12; - if (user.combatLevel < 100) { - duration += Time.Minute * 8; - } - return duration; - } - }, - { - id: QuestID.TheRibbitingTaleOfALillyPadLabourDispute, - qp: 1, - name: 'The Ribbiting Tale of a Lily Pad Labour Dispute', - skillReqs: { - woodcutting: 15 - }, - prerequisitesQuests: [QuestID.ChildrenOfTheSun], - skillsRewards: { - woodcutting: 2000 - }, - calcTime: () => { - const duration = Time.Minute * 3; - return duration; - } - }, - { - id: QuestID.PerilousMoons, - qp: 2, - name: 'Perilous Moons', - skillReqs: { - slayer: 48, - hunter: 20, - fishing: 20, - runecraft: 20, - construction: 10 - }, - combatLevelReq: 75, - prerequisitesQuests: [QuestID.ChildrenOfTheSun, QuestID.TwilightsPromise], - skillsRewards: { - slayer: 40_000, - runecraft: 5000, - hunter: 5000, - fishing: 5000 - }, - calcTime: (user: MUser) => { - let duration = Time.Minute * 20; - if (user.combatLevel < 120) { - duration += Time.Minute * 5; - } - if (user.combatLevel < 100) { - duration += Time.Minute * 10; - } - return duration; - } - }, - { - id: QuestID.AtFirstLight, - qp: 1, - name: 'At First Light', - skillReqs: { - hunter: 46, - herblore: 30, - construction: 27 - }, - combatLevelReq: 75, - qpReq: 2, - prerequisitesQuests: [QuestID.ChildrenOfTheSun], - skillsRewards: { - hunter: 4500, - construction: 800, - herblore: 500 - }, - calcTime: () => { - let duration = Time.Minute * 6; - return duration; - } - }, - { - id: QuestID.TwilightsPromise, - qp: 1, - name: "Twilight's Promise", - skillsRewards: { - thieving: 3000 - }, - combatLevelReq: 40, - prerequisitesQuests: [QuestID.ChildrenOfTheSun], - calcTime: (user: MUser) => { - let duration = Time.Minute * 9; - if (user.combatLevel < 75) { - duration += Time.Minute * 5; - } - return duration; - } - } -]; - -export const MAX_QP = MAX_GLOBAL_QP + sumArr(quests.map(i => i.qp)); - export async function questCommand(user: MUser, channelID: string, name?: string) { if (!user.user.minion_hasBought) { return 'You need a minion to do a questing trip'; diff --git a/src/mahoji/lib/abstracted_commands/roguesDenCommand.ts b/src/mahoji/lib/abstracted_commands/roguesDenCommand.ts index 94a691fe7f9..32656a6844c 100644 --- a/src/mahoji/lib/abstracted_commands/roguesDenCommand.ts +++ b/src/mahoji/lib/abstracted_commands/roguesDenCommand.ts @@ -1,9 +1,9 @@ -import { reduceNumByPercent, Time } from 'e'; +import { Time, reduceNumByPercent } from 'e'; import { Bank } from 'oldschooljs'; +import { formatDuration } from '@oldschoolgg/toolkit'; import { SkillsEnum } from '../../../lib/skilling/types'; import type { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; -import { formatDuration } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; import { updateBankSetting } from '../../../lib/util/updateBankSetting'; diff --git a/src/mahoji/lib/abstracted_commands/sawmillCommand.ts b/src/mahoji/lib/abstracted_commands/sawmillCommand.ts index 9fcf2b7d9ea..8a70b8b8740 100644 --- a/src/mahoji/lib/abstracted_commands/sawmillCommand.ts +++ b/src/mahoji/lib/abstracted_commands/sawmillCommand.ts @@ -1,8 +1,8 @@ -import { clamp, Time } from 'e'; +import { Time, clamp } from 'e'; import { Bank } from 'oldschooljs'; import { Planks } from '../../../lib/minions/data/planks'; -import { SawmillActivityTaskOptions } from '../../../lib/types/minions'; +import type { SawmillActivityTaskOptions } from '../../../lib/types/minions'; import { formatDuration, itemNameFromID, stringMatches, toKMB } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; @@ -86,10 +86,10 @@ export async function sawmillCommand( await addSubTaskToActivityTask({ type: 'Sawmill', duration, - plankID: plank!.outputItem, + plankID: plank?.outputItem, plankQuantity: quantity, userID: user.id, - channelID: channelID.toString() + channelID }); let response = `${user.minionName} is now creating ${quantity} ${itemNameFromID(plank.outputItem)}${ diff --git a/src/mahoji/lib/abstracted_commands/scatterCommand.ts b/src/mahoji/lib/abstracted_commands/scatterCommand.ts index 839668468fa..ea78cf49cf8 100644 --- a/src/mahoji/lib/abstracted_commands/scatterCommand.ts +++ b/src/mahoji/lib/abstracted_commands/scatterCommand.ts @@ -3,7 +3,7 @@ import { Bank } from 'oldschooljs'; import Prayer from '../../../lib/skilling/skills/prayer'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { ScatteringActivityTaskOptions } from '../../../lib/types/minions'; +import type { ScatteringActivityTaskOptions } from '../../../lib/types/minions'; import { formatDuration, stringMatches } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; diff --git a/src/mahoji/lib/abstracted_commands/sepulchreCommand.ts b/src/mahoji/lib/abstracted_commands/sepulchreCommand.ts index c2a87c8175a..f4936eeca49 100644 --- a/src/mahoji/lib/abstracted_commands/sepulchreCommand.ts +++ b/src/mahoji/lib/abstracted_commands/sepulchreCommand.ts @@ -1,8 +1,8 @@ -import { reduceNumByPercent, sumArr, Time } from 'e'; +import { Time, reduceNumByPercent, sumArr } from 'e'; import { sepulchreBoosts, sepulchreFloors } from '../../../lib/minions/data/sepulchre'; import { getMinigameScore } from '../../../lib/settings/minigames'; -import { SepulchreActivityTaskOptions } from '../../../lib/types/minions'; +import type { SepulchreActivityTaskOptions } from '../../../lib/types/minions'; import { formatDuration } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; @@ -14,11 +14,11 @@ export async function sepulchreCommand(user: MUser, channelID: string) { const thievingLevel = skills.thieving; const minLevel = sepulchreFloors[0].agilityLevel; if (agilityLevel < minLevel) { - return `You need atleast level ${minLevel} Agility to do the Hallowed Sepulchre.`; + return `You need at least level ${minLevel} Agility to do the Hallowed Sepulchre.`; } if (thievingLevel < 66) { - return 'You need atleast level 66 Thieving to do the Hallowed Sepulchre.'; + return 'You need at least level 66 Thieving to do the Hallowed Sepulchre.'; } if (!userHasGracefulEquipped(user)) { diff --git a/src/mahoji/lib/abstracted_commands/shadesOfMortonCommand.ts b/src/mahoji/lib/abstracted_commands/shadesOfMortonCommand.ts index 553b5b2e992..90de041a444 100644 --- a/src/mahoji/lib/abstracted_commands/shadesOfMortonCommand.ts +++ b/src/mahoji/lib/abstracted_commands/shadesOfMortonCommand.ts @@ -1,8 +1,8 @@ import { Time } from 'e'; import { Bank } from 'oldschooljs'; -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; -import { ShadesOfMortonOptions } from '../../../lib/types/minions'; +import type { ShadesOfMortonOptions } from '../../../lib/types/minions'; import { formatDuration, itemNameFromID } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; @@ -255,12 +255,12 @@ export const shadesLogs: ShadesLog[] = [ const coffins = ['Bronze coffin', 'Steel coffin', 'Black coffin', 'Silver coffin', 'Gold coffin']; export async function shadesOfMortonStartCommand(user: MUser, channelID: string, logStr: string, shadeStr: string) { - let messages: string[] = []; + const messages: string[] = []; let totalTime = calcMaxTripLength(user, 'ShadesOfMorton'); for (let i = coffins.length - 1; i >= 0; i--) { const coffin = coffins[i]; if (user.hasEquipped(coffin)) { - let bonusTime = i * Time.Minute; + const bonusTime = i * Time.Minute; if (bonusTime) { totalTime += bonusTime; messages.push(`${formatDuration(bonusTime)} bonus max trip length for ${itemNameFromID(coffin)}`); @@ -287,7 +287,7 @@ export async function shadesOfMortonStartCommand(user: MUser, channelID: string, const quantity = Math.min(logsOwned, shadesOwned, Math.floor(totalTime / timePerLog)); const duration = quantity * timePerLog; - let prayerXP = log.prayerXP[shade.shadeName]; + const prayerXP = log.prayerXP[shade.shadeName]; if (!prayerXP) { return `You can't use ${log.normalLog.name} with ${shade.item.name}.`; } @@ -298,7 +298,7 @@ export async function shadesOfMortonStartCommand(user: MUser, channelID: string, if (!user.owns(cost)) return `You don't own: ${cost}.`; await user.removeItemsFromBank(cost); - await userStatsBankUpdate(user.id, 'shades_of_morton_cost_bank', cost); + await userStatsBankUpdate(user, 'shades_of_morton_cost_bank', cost); await addSubTaskToActivityTask({ userID: user.id, diff --git a/src/mahoji/lib/abstracted_commands/shootingStarsCommand.ts b/src/mahoji/lib/abstracted_commands/shootingStarsCommand.ts index ff34b104764..49d2c1832ac 100644 --- a/src/mahoji/lib/abstracted_commands/shootingStarsCommand.ts +++ b/src/mahoji/lib/abstracted_commands/shootingStarsCommand.ts @@ -1,19 +1,20 @@ import { SimpleTable } from '@oldschoolgg/toolkit'; -import { activity_type_enum } from '@prisma/client'; +import type { activity_type_enum } from '@prisma/client'; import { ButtonBuilder, ButtonStyle } from 'discord.js'; -import { percentChance, randInt, roll, Time } from 'e'; +import { Time, percentChance, randInt, roll } from 'e'; import { Bank } from 'oldschooljs'; import addSkillingClueToLoot from '../../../lib/minions/functions/addSkillingClueToLoot'; import { determineMiningTime } from '../../../lib/skilling/functions/determineMiningTime'; import { pickaxes } from '../../../lib/skilling/functions/miningBoosts'; -import { Ore, SkillsEnum } from '../../../lib/skilling/types'; -import { ActivityTaskData, ShootingStarsOptions } from '../../../lib/types/minions'; +import type { Ore } from '../../../lib/skilling/types'; +import { SkillsEnum } from '../../../lib/skilling/types'; +import type { ActivityTaskData, ShootingStarsOptions } from '../../../lib/types/minions'; import { formatDuration, itemNameFromID } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength, patronMaxTripBonus } from '../../../lib/util/calcMaxTripLength'; import { minionName } from '../../../lib/util/minionUtils'; -import { MUserClass } from './../../../lib/MUser'; +import type { MUserClass } from './../../../lib/MUser'; interface Star extends Ore { size: number; @@ -202,8 +203,8 @@ export async function shootingStarsCommand(channelID: string, user: MUserClass, let duration = 0; let dustReceived = 0; let totalXp = 0; - for (let star of stars) { - let [timeToMine, newQuantity] = determineMiningTime({ + for (const star of stars) { + const [timeToMine, newQuantity] = determineMiningTime({ quantity: Math.round(star.dustAvailable / usersWith), ore: star, ticksBetweenRolls: currentPickaxe.ticksBetweenRolls, diff --git a/src/mahoji/lib/abstracted_commands/slayerShopCommand.ts b/src/mahoji/lib/abstracted_commands/slayerShopCommand.ts index b65cbecf976..c97b03ce63c 100644 --- a/src/mahoji/lib/abstracted_commands/slayerShopCommand.ts +++ b/src/mahoji/lib/abstracted_commands/slayerShopCommand.ts @@ -1,4 +1,4 @@ -import { ChatInputCommandInteraction } from 'discord.js'; +import type { ChatInputCommandInteraction } from 'discord.js'; import { removeFromArr } from 'e'; import { Bank } from 'oldschooljs'; @@ -109,11 +109,7 @@ export function slayerShopListMyUnlocks(mahojiUser: MUser) { const myUnlocks = SlayerRewardsShop.filter(srs => mahojiUser.user.slayer_unlocks.includes(srs.id)); const unlocksStr = myUnlocks.map(unlock => unlock.name).join('\n'); - const content = - `Current points: ${mahojiUser.user.slayer_points}\n**You currently have the following ` + - `rewards unlocked:**\n${unlocksStr}\n\n` + - 'Usage:\n`/slayer rewards [unlock|buy|disable] Reward`\nExample:' + - '\n`/slayer rewards unlock unlockable:Malevolent Masquerade`'; + const content = `Current points: ${mahojiUser.user.slayer_points}\n**You currently have the following rewards unlocked:**\n${unlocksStr}\n\nUsage:\n\`/slayer rewards [unlock|buy|disable] Reward\`\nExample:\n\`/slayer rewards unlock unlockable:Malevolent Masquerade\``; if (content.length > 2000) { return { content: 'Your currently unlocked Slayer rewards', diff --git a/src/mahoji/lib/abstracted_commands/slayerTaskCommand.ts b/src/mahoji/lib/abstracted_commands/slayerTaskCommand.ts index 4cca01aea2c..bfe56def4f6 100644 --- a/src/mahoji/lib/abstracted_commands/slayerTaskCommand.ts +++ b/src/mahoji/lib/abstracted_commands/slayerTaskCommand.ts @@ -1,10 +1,11 @@ import { stringMatches } from '@oldschoolgg/toolkit'; -import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ChatInputCommandInteraction } from 'discord.js'; -import { notEmpty, removeFromArr, Time } from 'e'; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, type ChatInputCommandInteraction } from 'discord.js'; +import { Time, notEmpty, removeFromArr } from 'e'; import { Monsters } from 'oldschooljs'; import killableMonsters from '../../../lib/minions/data/killableMonsters'; -import { prisma } from '../../../lib/settings/prisma'; + +import { InteractionID } from '../../../lib/InteractionID'; import { runCommand } from '../../../lib/settings/settings'; import { slayerMasters } from '../../../lib/slayer/slayerMasters'; import { @@ -14,7 +15,7 @@ import { getUsersCurrentSlayerInfo, userCanUseMaster } from '../../../lib/slayer/slayerUtil'; -import { AssignableSlayerTask } from '../../../lib/slayer/types'; +import type { AssignableSlayerTask } from '../../../lib/slayer/types'; import { awaitMessageComponentInteraction, channelIsSendable } from '../../../lib/util'; import { handleMahojiConfirmation } from '../../../lib/util/handleMahojiConfirmation'; import { interactionReply } from '../../../lib/util/interactionReply'; @@ -27,39 +28,34 @@ const returnSuccessButtons = [ new ButtonBuilder({ label: 'Autoslay (Saved)', style: ButtonStyle.Secondary, - customId: 'assaved' + customId: InteractionID.Slayer.AutoSlaySaved }), new ButtonBuilder({ label: 'Autoslay (Default)', style: ButtonStyle.Secondary, - customId: 'asdef' + customId: InteractionID.Slayer.AutoSlayDefault }), new ButtonBuilder({ label: 'Autoslay (EHP)', style: ButtonStyle.Secondary, - customId: 'asehp' + customId: InteractionID.Slayer.AutoSlayEHP }), new ButtonBuilder({ label: 'Autoslay (Boss)', style: ButtonStyle.Secondary, - customId: 'asboss' + customId: InteractionID.Slayer.AutoSlayBoss }) ]), new ActionRowBuilder().addComponents([ new ButtonBuilder({ label: 'Cancel Task + New (30 points)', style: ButtonStyle.Danger, - customId: 'skip' + customId: InteractionID.Slayer.SkipTask }), new ButtonBuilder({ label: 'Block Task + New (100 points)', style: ButtonStyle.Danger, - customId: 'block' - }), - new ButtonBuilder({ - label: 'Do Nothing', - style: ButtonStyle.Secondary, - customId: 'doNothing' + customId: InteractionID.Slayer.BlockTask }) ]) ]; @@ -69,14 +65,14 @@ function getAlternateMonsterList(assignedTask: AssignableSlayerTask | null) { const altMobs = assignedTask.monsters; const alternateMonsters = killableMonsters .filter(m => { - return altMobs.includes(m.id) && m!.id !== assignedTask.monster.id; + return altMobs.includes(m.id) && m.id !== assignedTask.monster.id; }) .map(m => { - return m!.name; + return m?.name; }); - const cname = getCommonTaskName(assignedTask!.monster); - if (cname !== assignedTask!.monster.name && cname.substr(0, cname.length - 1) !== assignedTask!.monster.name) { - alternateMonsters.unshift(assignedTask!.monster.name); + const cname = getCommonTaskName(assignedTask?.monster); + if (cname !== assignedTask?.monster.name && cname.substr(0, cname.length - 1) !== assignedTask?.monster.name) { + alternateMonsters.unshift(assignedTask?.monster.name); } return alternateMonsters.length > 0 ? ` (**Alternate Monsters**: ${alternateMonsters.join(', ')})` : ''; @@ -99,18 +95,21 @@ export async function slayerListBlocksCommand(mahojiUser: MUser) { export async function slayerStatusCommand(mahojiUser: MUser) { const { currentTask, assignedTask, slayerMaster } = await getUsersCurrentSlayerInfo(mahojiUser.id); const { slayer_points: slayerPoints } = mahojiUser.user; - const { slayer_task_streak: slayerStreak } = await mahojiUser.fetchStats({ slayer_task_streak: true }); + const slayer_streaks = await mahojiUser.fetchStats({ slayer_task_streak: true, slayer_wildy_task_streak: true }); + return ( `${ currentTask - ? `\nYour current task from ${slayerMaster!.name} is to kill **${getCommonTaskName( - assignedTask!.monster - )}**${getAlternateMonsterList( + ? `\nYour current task from ${slayerMaster.name} is to kill **${getCommonTaskName( + assignedTask.monster + )}**${getAlternateMonsterList( assignedTask - )}. You have ${currentTask.quantity_remaining.toLocaleString()} kills remaining.` + )}. You have ${currentTask.quantity_remaining.toLocaleString()} kills remaining.` : '' }` + - `\nYou have ${slayerPoints.toLocaleString()} slayer points, and have completed ${slayerStreak} tasks in a row.` + `\nYou have ${slayerPoints.toLocaleString()} slayer points, and have completed ${ + slayer_streaks.slayer_task_streak + } tasks in a row and ${slayer_streaks.slayer_wildy_task_streak} wilderness tasks in a row.` ); } @@ -143,7 +142,7 @@ async function returnSuccess(channelID: string, user: MUser, content: string) { }); if (!selection.isButton()) return; switch (selection.customId) { - case 'assaved': { + case InteractionID.Slayer.AutoSlaySaved: { await runCommand({ commandName: 'slayer', args: { autoslay: {} }, @@ -153,7 +152,7 @@ async function returnSuccess(channelID: string, user: MUser, content: string) { }); return; } - case 'asdef': { + case InteractionID.Slayer.AutoSlayDefault: { await runCommand({ commandName: 'slayer', args: { autoslay: { mode: 'default' } }, @@ -163,7 +162,7 @@ async function returnSuccess(channelID: string, user: MUser, content: string) { }); return; } - case 'asehp': { + case InteractionID.Slayer.AutoSlayEHP: { await runCommand({ commandName: 'slayer', args: { autoslay: { mode: 'ehp' } }, @@ -173,7 +172,7 @@ async function returnSuccess(channelID: string, user: MUser, content: string) { }); return; } - case 'asboss': { + case InteractionID.Slayer.AutoSlayBoss: { await runCommand({ commandName: 'slayer', args: { autoslay: { mode: 'boss' } }, @@ -183,7 +182,7 @@ async function returnSuccess(channelID: string, user: MUser, content: string) { }); return; } - case 'skip': { + case InteractionID.Slayer.SkipTask: { await runCommand({ commandName: 'slayer', args: { manage: { command: 'skip', new: true } }, @@ -193,7 +192,7 @@ async function returnSuccess(channelID: string, user: MUser, content: string) { }); return; } - case 'block': { + case InteractionID.Slayer.BlockTask: { await runCommand({ commandName: 'slayer', args: { manage: { command: 'block', new: true } }, @@ -204,6 +203,11 @@ async function returnSuccess(channelID: string, user: MUser, content: string) { } } } catch (err: unknown) { + if ((err as any).message === 'time') return; + logError(err, { + user_id: user.id.toString(), + channel_id: channelID + }); } finally { await sentMessage.edit({ components: [] }); } @@ -241,8 +245,9 @@ export async function slayerNewTaskCommand({ const has99SlayerCape = user.skillLevel('slayer') >= 99 && user.hasEquippedOrInBank('Slayer cape'); - // Chooses a default slayer master: + // Chooses a default slayer master (excluding Krystilia): const proposedDefaultMaster = slayerMasters + .filter(sm => sm.id !== 8) // Exclude Krystilia .sort((a, b) => b.basePoints - a.basePoints) .find(sm => userCanUseMaster(user, sm)); @@ -251,15 +256,15 @@ export async function slayerNewTaskCommand({ slayerMasterOverride && has99SlayerCape ? slayerMasters.find(m => m.aliases.some(alias => stringMatches(alias, slayerMasterOverride))) ?? null : slayerMasterOverride - ? slayerMasters - .filter(m => userCanUseMaster(user, m)) - .find(m => m.aliases.some(alias => stringMatches(alias, slayerMasterOverride))) ?? null - : rememberedSlayerMaster - ? slayerMasters - .filter(m => userCanUseMaster(user, m)) - .find(m => m.aliases.some(alias => stringMatches(alias, rememberedSlayerMaster))) ?? - proposedDefaultMaster - : proposedDefaultMaster; + ? slayerMasters + .filter(m => userCanUseMaster(user, m)) + .find(m => m.aliases.some(alias => stringMatches(alias, slayerMasterOverride))) ?? null + : rememberedSlayerMaster + ? slayerMasters + .filter(m => userCanUseMaster(user, m)) + .find(m => m.aliases.some(alias => stringMatches(alias, rememberedSlayerMaster))) ?? + proposedDefaultMaster + : proposedDefaultMaster; // Contains (if matched) the requested Slayer Master regardless of requirements. const matchedSlayerMaster = slayerMasterOverride @@ -267,7 +272,7 @@ export async function slayerNewTaskCommand({ m => stringMatches(m.name, slayerMasterOverride) || m.aliases.some(alias => stringMatches(alias, slayerMasterOverride)) - ) ?? null + ) ?? null : null; // Special handling for Turael skip @@ -276,11 +281,13 @@ export async function slayerNewTaskCommand({ interactionReply(interaction, 'You cannot skip this task because Turael assigns it.'); return; } + const isUsingKrystilia = Boolean(currentTask?.slayer_master_id === 8); + const taskStreakKey = isUsingKrystilia ? 'slayer_wildy_task_streak' : 'slayer_task_streak'; + const warning = `Really cancel task? This will reset your${ + isUsingKrystilia ? ' wilderness' : '' + } streak to 0 and give you a new ${slayerMaster.name} task.`; - await handleMahojiConfirmation( - interaction, - `Really cancel task? This will reset your streak to 0 and give you a new ${slayerMaster.name} task.` - ); + await handleMahojiConfirmation(interaction, warning); await prisma.slayerTask.update({ where: { id: currentTask.id @@ -290,9 +297,10 @@ export async function slayerNewTaskCommand({ quantity_remaining: 0 } }); - await userStatsUpdate(user.id, { slayer_task_streak: 0 }, {}); + await userStatsUpdate(user.id, { [taskStreakKey]: 0 }, {}); + const newSlayerTask = await assignNewSlayerTask(user, slayerMaster); - let commonName = getCommonTaskName(newSlayerTask.assignedTask!.monster); + const commonName = getCommonTaskName(newSlayerTask.assignedTask.monster); const returnMessage = `Your task has been skipped.\n\n ${slayerMaster.name}` + ` has assigned you to kill ${newSlayerTask.currentTask.quantity}x ${commonName}${getAlternateMonsterList( @@ -311,13 +319,13 @@ export async function slayerNewTaskCommand({ // Store favorite slayer master if requested: if (saveDefaultSlayerMaster && slayerMaster) { await user.update({ slayer_remember_master: slayerMaster.name }); - resultMessage = `**Saved ${slayerMaster!.name} as default slayer master.**\n\n`; + resultMessage = `**Saved ${slayerMaster?.name} as default slayer master.**\n\n`; } if (currentTask || !slayerMaster) { let warningInfo = ''; if (slayerMasterOverride && !slayerMaster && matchedSlayerMaster) { - let aRequirements: string[] = []; + const aRequirements: string[] = []; if (matchedSlayerMaster.slayerLvl) aRequirements.push(`Slayer Level: ${matchedSlayerMaster.slayerLvl}`); if (matchedSlayerMaster.combatLvl) aRequirements.push(`Combat Level: ${matchedSlayerMaster.combatLvl}`); if (matchedSlayerMaster.questPoints) aRequirements.push(`Quest points: ${matchedSlayerMaster.questPoints}`); @@ -325,10 +333,9 @@ export async function slayerNewTaskCommand({ if (aRequirements.length > 0) warningInfo += `**Requires**:\n${aRequirements.join('\n')}\n\n`; } - let baseInfo = currentTask + const baseInfo = currentTask ? await slayerStatusCommand(user) - : 'You have no task at the moment, you can get a task using `/slayer task master:Turael`' + - `All slayer Masters: ${slayerMasters.map(i => i.name).join(', ')}`; + : `You have no task at the moment, you can get a task using \`/slayer task master:Turael\`All slayer Masters: ${slayerMasters.map(i => i.name).join(', ')}`; resultMessage += `${warningInfo}${baseInfo}`; if (currentTask && !warningInfo) { @@ -344,7 +351,7 @@ export async function slayerNewTaskCommand({ const newSlayerTask = await assignNewSlayerTask(user, slayerMaster); - let commonName = getCommonTaskName(newSlayerTask.assignedTask!.monster); + let commonName = getCommonTaskName(newSlayerTask.assignedTask.monster); if (commonName === 'TzHaar') { resultMessage += 'Ah... Tzhaar... '; commonName += @@ -402,9 +409,7 @@ export async function slayerSkipTaskCommand({ if (block && myBlockList.length >= maxBlocks) { interactionReply( interaction, - `You cannot have more than ${maxBlocks} slayer blocks!\n\nUse:\n` + - '`/slayer rewards unblock assignment:kalphite`\n to remove a blocked monster.\n' + - '`/slayer manage command:list_blocks` for your list of blocked monsters.' + `You cannot have more than ${maxBlocks} slayer blocks!\n\nUse:\n\`/slayer rewards unblock assignment:kalphite\`\n to remove a blocked monster.\n\`/slayer manage command:list_blocks\` for your list of blocked monsters.` ); return; } diff --git a/src/mahoji/lib/abstracted_commands/slotsCommand.ts b/src/mahoji/lib/abstracted_commands/slotsCommand.ts index 9cc4f5f0372..6ebe0f0d648 100644 --- a/src/mahoji/lib/abstracted_commands/slotsCommand.ts +++ b/src/mahoji/lib/abstracted_commands/slotsCommand.ts @@ -1,13 +1,8 @@ import { SimpleTable } from '@oldschoolgg/toolkit'; -import { - ActionRowBuilder, - BaseMessageOptions, - ButtonBuilder, - ButtonStyle, - ChatInputCommandInteraction -} from 'discord.js'; +import type { CommandResponse } from '@oldschoolgg/toolkit'; +import type { BaseMessageOptions, ChatInputCommandInteraction } from 'discord.js'; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js'; import { chunk, noOp, randInt, shuffleArr, sleep } from 'e'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; import { Bank } from 'oldschooljs'; import { toKMB } from 'oldschooljs/dist/util'; @@ -81,7 +76,7 @@ function determineWinnings(bet: number, buttons: ButtonInstance[]) { const winningRow = chunk(buttons, 3) .filter(row => row.every(b => b.name === row[0].name)) .sort((a, b) => b[0].mod(bet) - a[0].mod(bet))[0]; - let amountReceived = winningRow ? winningRow[0].mod(bet) : 0; + const amountReceived = winningRow ? winningRow[0].mod(bet) : 0; return { amountReceived, winningRow @@ -125,7 +120,7 @@ ${buttonsData.map(b => `${b.name}: ${b.mod(1)}x`).join('\n')}`; await user.removeItemsFromBank(new Bank().add('Coins', amount)); const buttonsToShow = getButtons(); - let chunkedButtons = chunk(buttonsToShow, 3); + const chunkedButtons = chunk(buttonsToShow, 3); const { winningRow, amountReceived } = determineWinnings(amount, buttonsToShow); @@ -141,8 +136,8 @@ ${buttonsData.map(b => `${b.name}: ${b.mod(1)}x`).join('\n')}`; !shouldShowThisButton ? ButtonStyle.Secondary : isWinning - ? ButtonStyle.Success - : ButtonStyle.Secondary + ? ButtonStyle.Success + : ButtonStyle.Secondary ) .setEmoji(shouldShowThisButton ? b.emoji : '❓'); }) @@ -162,13 +157,11 @@ ${buttonsData.map(b => `${b.name}: ${b.mod(1)}x`).join('\n')}`; amountReceived === 0 ? "Unlucky, you didn't win anything, and lost your bet!" : `You won ${toKMB(amountReceived)}!`; - sentMessage?.edit({ content: finishContent, components: getCurrentButtons({ columnsToHide: [] }) }).catch(noOp); + sentMessage?.delete().catch(noOp); await user.addItemsToBank({ items: new Bank().add('Coins', amountReceived), collectionLog: false }); await updateClientGPTrackSetting('gp_slots', amountReceived - amount); await updateGPTrackSetting('gp_slots', amountReceived - amount, user); - return { - content: finishContent - }; + return { content: finishContent, components: getCurrentButtons({ columnsToHide: [] }) }; } diff --git a/src/mahoji/lib/abstracted_commands/soulWarsCommand.ts b/src/mahoji/lib/abstracted_commands/soulWarsCommand.ts index a6326c5a8dd..b231202ea34 100644 --- a/src/mahoji/lib/abstracted_commands/soulWarsCommand.ts +++ b/src/mahoji/lib/abstracted_commands/soulWarsCommand.ts @@ -1,4 +1,4 @@ -import { User } from '@prisma/client'; +import type { User } from '@prisma/client'; import { Time } from 'e'; import { Bank } from 'oldschooljs'; @@ -164,7 +164,7 @@ export async function soulWarsBuyCommand(user: MUser, input = '', quantity?: num if (!quantity) { quantity = 1; } - if (!Number.isNaN(parseInt(possibleItemName[0]))) { + if (!Number.isNaN(Number.parseInt(possibleItemName[0]))) { quantity = Number(possibleItemName.shift()); } diff --git a/src/mahoji/lib/abstracted_commands/stashUnitsCommand.ts b/src/mahoji/lib/abstracted_commands/stashUnitsCommand.ts index 4f6e6274cb3..b326f0c2f0b 100644 --- a/src/mahoji/lib/abstracted_commands/stashUnitsCommand.ts +++ b/src/mahoji/lib/abstracted_commands/stashUnitsCommand.ts @@ -1,11 +1,12 @@ import { stringMatches } from '@oldschoolgg/toolkit'; -import { StashUnit, User } from '@prisma/client'; +import type { CommandResponse } from '@oldschoolgg/toolkit'; +import type { StashUnit, User } from '@prisma/client'; import { partition } from 'e'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; import Bank from 'oldschooljs/dist/structures/Bank'; -import { allStashUnitsFlat, allStashUnitTiers, IStashUnit, StashUnitTier } from '../../../lib/clues/stashUnits'; -import { prisma } from '../../../lib/settings/prisma'; +import type { IStashUnit, StashUnitTier } from '../../../lib/clues/stashUnits'; +import { allStashUnitTiers, allStashUnitsFlat } from '../../../lib/clues/stashUnits'; + import { assert } from '../../../lib/util'; import { makeBankImage } from '../../../lib/util/makeBankImage'; import { itemNameFromID } from '../../../lib/util/smallUtils'; @@ -30,7 +31,7 @@ export async function getParsedStashUnits(userID: string): Promise return slot.some(i => builtUnit.items_contained.includes(i)); } return builtUnit.items_contained.includes(slot); - }) + }) : false, builtUnit, tier diff --git a/src/mahoji/lib/abstracted_commands/statCommand.ts b/src/mahoji/lib/abstracted_commands/statCommand.ts index 229cfc8acf1..1a6f0808cb1 100644 --- a/src/mahoji/lib/abstracted_commands/statCommand.ts +++ b/src/mahoji/lib/abstracted_commands/statCommand.ts @@ -1,17 +1,16 @@ -import { bold } from '@discordjs/builders'; -import { toTitleCase } from '@oldschoolgg/toolkit'; -import { activity_type_enum, UserStats } from '@prisma/client'; -import { sumArr, Time } from 'e'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; import { Bank, Monsters } from 'oldschooljs'; import { SkillsEnum } from 'oldschooljs/dist/constants'; -import { ItemBank, SkillsScore } from 'oldschooljs/dist/meta/types'; +import type { ItemBank, SkillsScore } from 'oldschooljs/dist/meta/types'; import { TOBRooms } from 'oldschooljs/dist/simulation/misc/TheatreOfBlood'; import { toKMB } from 'oldschooljs/dist/util'; +import { type CommandResponse, PerkTier, toTitleCase } from '@oldschoolgg/toolkit'; +import type { UserStats, activity_type_enum } from '@prisma/client'; +import { bold } from 'discord.js'; +import { Time, sumArr } from 'e'; import { ClueTiers } from '../../../lib/clues/clueTiers'; import { getClueScoresFromOpenables } from '../../../lib/clues/clueUtils'; -import { Emoji, PerkTier } from '../../../lib/constants'; +import { Emoji } from '../../../lib/constants'; import { calcCLDetails, isCLItem } from '../../../lib/data/Collections'; import { skillEmoji } from '../../../lib/data/emojis'; import { slayerMaskHelms } from '../../../lib/data/slayerMaskHelms'; @@ -26,20 +25,20 @@ import { getBankBgById } from '../../../lib/minions/data/bankBackgrounds'; import killableMonsters from '../../../lib/minions/data/killableMonsters'; import { RandomEvents } from '../../../lib/randomEvents'; import { getMinigameScore } from '../../../lib/settings/minigames'; -import { prisma } from '../../../lib/settings/prisma'; + import Agility from '../../../lib/skilling/skills/agility'; import { Castables } from '../../../lib/skilling/skills/magic/castables'; import { ForestryEvents } from '../../../lib/skilling/skills/woodcutting/forestry'; import { getAllAlternateMonsters, getCommonTaskName, getSlayerTaskStats } from '../../../lib/slayer/slayerUtil'; import { sorts } from '../../../lib/sorts'; -import { InfernoOptions } from '../../../lib/types/minions'; -import { formatDuration, getUsername, sanitizeBank, SQL_sumOfAllCLItems, stringMatches } from '../../../lib/util'; -import { barChart, lineChart, pieChart } from '../../../lib/util/chart'; +import type { InfernoOptions } from '../../../lib/types/minions'; +import { SQL_sumOfAllCLItems, formatDuration, getUsername, sanitizeBank, stringMatches } from '../../../lib/util'; +import { createChart } from '../../../lib/util/chart'; import { getItem } from '../../../lib/util/getOSItem'; import { makeBankImage } from '../../../lib/util/makeBankImage'; import resolveItems from '../../../lib/util/resolveItems'; import { Cooldowns } from '../Cooldowns'; -import { collectables } from './collectCommand'; +import { collectables } from '../collectables'; interface DataPiece { name: string; @@ -128,8 +127,7 @@ WHERE } export async function personalConstructionStats(user: MUser) { - const result: { id: number; qty: number }[] = - await prisma.$queryRawUnsafe(`SELECT (data->>'objectID')::int AS id, SUM((data->>'quantity')::int)::int AS qty + const result: { id: number; qty: number }[] = await prisma.$queryRawUnsafe(`SELECT (data->>'objectID')::int AS id, SUM((data->>'quantity')::int)::int AS qty FROM activity WHERE type = 'Construction' AND user_id = '${user.id}'::bigint @@ -146,8 +144,7 @@ GROUP BY data->>'objectID';`); } export async function personalFiremakingStats(user: MUser) { - const result: { id: number; qty: number }[] = - await prisma.$queryRawUnsafe(`SELECT (data->>'burnableID')::int AS id, SUM((data->>'quantity')::int)::int AS qty + const result: { id: number; qty: number }[] = await prisma.$queryRawUnsafe(`SELECT (data->>'burnableID')::int AS id, SUM((data->>'quantity')::int)::int AS qty FROM activity WHERE type = 'Firemaking' AND user_id = '${user.id}'::bigint @@ -164,8 +161,7 @@ GROUP BY data->>'burnableID';`); } export async function personalWoodcuttingStats(user: MUser) { - const result: { id: number; qty: number }[] = - await prisma.$queryRawUnsafe(`SELECT (data->>'logID')::int AS id, SUM((data->>'quantity')::int)::int AS qty + const result: { id: number; qty: number }[] = await prisma.$queryRawUnsafe(`SELECT (data->>'logID')::int AS id, SUM((data->>'quantity')::int)::int AS qty FROM activity WHERE type = 'Woodcutting' AND user_id = '${user.id}'::bigint @@ -182,8 +178,7 @@ GROUP BY data->>'logID';`); } export async function personalMiningStats(user: MUser) { - const result: { id: number; qty: number }[] = - await prisma.$queryRawUnsafe(`SELECT (data->>'oreID')::int AS id, SUM((data->>'quantity')::int)::int AS qty + const result: { id: number; qty: number }[] = await prisma.$queryRawUnsafe(`SELECT (data->>'oreID')::int AS id, SUM((data->>'quantity')::int)::int AS qty FROM activity WHERE type = 'Mining' AND user_id = '${user.id}'::bigint @@ -200,8 +195,7 @@ GROUP BY data->>'oreID';`); } export async function personalHerbloreStats(user: MUser, stats: UserStats) { - const result: { id: number; qty: number }[] = - await prisma.$queryRawUnsafe(`SELECT (data->>'mixableID')::int AS id, SUM((data->>'quantity')::int)::int AS qty + const result: { id: number; qty: number }[] = await prisma.$queryRawUnsafe(`SELECT (data->>'mixableID')::int AS id, SUM((data->>'quantity')::int)::int AS qty FROM activity WHERE type = 'Herblore' AND user_id = '${user.id}'::bigint @@ -218,16 +212,14 @@ GROUP BY data->>'mixableID';`); return items; } export async function personalAlchingStats(user: MUser, includeAgilityAlching = true) { - const result: { id: number; qty: number }[] = - await prisma.$queryRawUnsafe(`SELECT (data->>'itemID')::int AS id, SUM((data->>'quantity')::int)::int AS qty + const result: { id: number; qty: number }[] = await prisma.$queryRawUnsafe(`SELECT (data->>'itemID')::int AS id, SUM((data->>'quantity')::int)::int AS qty FROM activity WHERE type = 'Alching' AND user_id = '${user.id}'::bigint AND data->>'itemID' IS NOT NULL AND completed = true GROUP BY data->>'itemID';`); - const agilityAlchRes: { id: number; qty: number }[] = - await prisma.$queryRawUnsafe(`SELECT (((data->>'alch')::json)->>'itemID')::int AS id, SUM((((data->>'alch')::json)->>'quantity')::int)::int AS qty + const agilityAlchRes: { id: number; qty: number }[] = await prisma.$queryRawUnsafe(`SELECT (((data->>'alch')::json)->>'itemID')::int AS id, SUM((((data->>'alch')::json)->>'quantity')::int)::int AS qty FROM activity WHERE type = 'Agility' AND user_id = '${user.id}'::bigint @@ -244,8 +236,7 @@ GROUP BY ((data->>'alch')::json)->>'itemID';`); return items; } export async function personalSmithingStats(user: MUser) { - const result: { id: number; qty: number }[] = - await prisma.$queryRawUnsafe(`SELECT (data->>'smithedBarID')::int AS id, SUM((data->>'quantity')::int)::int AS qty + const result: { id: number; qty: number }[] = await prisma.$queryRawUnsafe(`SELECT (data->>'smithedBarID')::int AS id, SUM((data->>'quantity')::int)::int AS qty FROM activity WHERE type = 'Smithing' AND user_id = '${user.id}'::bigint @@ -261,8 +252,7 @@ GROUP BY data->>'smithedBarID';`); return items; } export async function personalSmeltingStats(user: MUser) { - const result: { id: number; qty: number }[] = - await prisma.$queryRawUnsafe(`SELECT (data->>'barID')::int AS id, SUM((data->>'quantity')::int)::int AS qty + const result: { id: number; qty: number }[] = await prisma.$queryRawUnsafe(`SELECT (data->>'barID')::int AS id, SUM((data->>'quantity')::int)::int AS qty FROM activity WHERE type = 'Smelting' AND user_id = '${user.id}'::bigint @@ -278,8 +268,7 @@ GROUP BY data->>'barID';`); return items; } export async function personalSpellCastStats(user: MUser) { - const result: { id: number; qty: number }[] = - await prisma.$queryRawUnsafe(`SELECT (data->>'spellID')::int AS id, SUM((data->>'quantity')::int)::int AS qty + const result: { id: number; qty: number }[] = await prisma.$queryRawUnsafe(`SELECT (data->>'spellID')::int AS id, SUM((data->>'quantity')::int)::int AS qty FROM activity WHERE type = 'Casting' AND user_id = '${user.id}'::bigint @@ -289,15 +278,14 @@ GROUP BY data->>'spellID';`); return result.map(i => ({ castable: Castables.find(t => t.id === i.id)!, id: i.id, qty: i.qty })); } export async function personalCollectingStats(user: MUser) { - const result: { id: number; qty: number }[] = - await prisma.$queryRawUnsafe(`SELECT (data->>'collectableID')::int AS id, SUM((data->>'quantity')::int)::int AS qty + const result: { id: number; qty: number }[] = await prisma.$queryRawUnsafe(`SELECT (data->>'collectableID')::int AS id, SUM((data->>'quantity')::int)::int AS qty FROM activity WHERE type = 'Collecting' AND user_id = '${user.id}'::bigint AND data->>'collectableID' IS NOT NULL AND completed = true GROUP BY data->>'collectableID';`); - let bank = new Bank(); + const bank = new Bank(); for (const { id, qty } of result) { const col = collectables.find(t => t.item.id === id); if (!col) continue; @@ -308,6 +296,9 @@ GROUP BY data->>'collectableID';`); async function makeResponseForBank(bank: Bank, title: string, content?: string) { sanitizeBank(bank); + if (bank.length === 0) { + return { content: 'No results.' }; + } const image = await makeBankImage({ title, bank @@ -489,15 +480,21 @@ export const dataPoints: readonly DataPiece[] = [ name: 'Personal Activity Types', perkTierNeeded: PerkTier.Four, run: async (user: MUser) => { - const result: { type: activity_type_enum; qty: number }[] = - await prisma.$queryRawUnsafe(`SELECT type, count(type)::int AS qty + const result: { type: activity_type_enum; qty: number }[] = await prisma.$queryRawUnsafe(`SELECT type, count(type)::int AS qty FROM activity WHERE completed = true AND user_id = ${BigInt(user.id)} OR (data->>'users')::jsonb @> ${wrap(user.id)}::jsonb GROUP BY type;`); const dataPoints: [string, number][] = result.filter(i => i.qty >= 5).map(i => [i.type, i.qty]); - return makeResponseForBuffer(await barChart('Your Activity Types', val => `${val} Trips`, dataPoints)); + return makeResponseForBuffer( + await createChart({ + title: 'Your Activity Types', + format: 'kmb', + values: dataPoints, + type: 'bar' + }) + ); } }, { @@ -515,29 +512,40 @@ GROUP BY type;`); .filter(i => i.hours >= 1) .sort((a, b) => Number(b.hours - a.hours)) .map(i => [i.type, Number(i.hours)]); - const buffer = await barChart('Your Activity Durations', val => `${val} Hrs`, dataPoints); - return makeResponseForBuffer(buffer); + return makeResponseForBuffer( + await createChart({ + title: 'Your Activity Durations', + format: 'kmb', + values: dataPoints, + type: 'bar' + }) + ); } }, { name: 'Personal Monster KC', perkTierNeeded: PerkTier.Four, run: async (user: MUser) => { - const result: { id: number; kc: number }[] = - await prisma.$queryRawUnsafe(`SELECT (data->>'monsterID')::int as id, SUM((data->>'quantity')::int)::int AS kc + const result: { id: number; kc: number }[] = await prisma.$queryRawUnsafe(`SELECT (data->>'mi')::int as id, SUM((data->>'q')::int)::int AS kc FROM activity WHERE completed = true AND user_id = ${BigInt(user.id)} AND type = 'MonsterKilling' AND data IS NOT NULL AND data::text != '{}' -GROUP BY data->>'monsterID';`); +GROUP BY data->>'mi';`); const dataPoints: [string, number][] = result .sort((a, b) => b.kc - a.kc) .slice(0, 30) .map(i => [killableMonsters.find(mon => mon.id === i.id)?.name ?? i.id.toString(), i.kc]); - const buffer = await barChart("Your Monster KC's", val => `${val} KC`, dataPoints); - return makeResponseForBuffer(buffer); + return makeResponseForBuffer( + await createChart({ + title: "Your Monster KC's", + format: 'kmb', + values: dataPoints, + type: 'bar' + }) + ); } }, { @@ -550,18 +558,17 @@ GROUP BY data->>'monsterID';`); .slice(0, 15) .map(i => [i[0].name, i[0].price * i[1]]); const everythingElse = items.slice(20, items.length); - let everythingElseBank = new Bank(); + const everythingElseBank = new Bank(); for (const i of everythingElse) everythingElseBank.add(i[0].name, i[1]); dataPoints.push(['Everything else', everythingElseBank.value()]); - const buffer = await barChart( - 'Your Top Bank Value Items', - val => { - if (typeof val === 'string') return val; - return `${toKMB(val)} GP`; - }, - dataPoints + return makeResponseForBuffer( + await createChart({ + title: 'Your Top Bank Value Items', + format: 'kmb', + values: dataPoints, + type: 'bar' + }) ); - return makeResponseForBuffer(buffer); } }, { @@ -569,43 +576,46 @@ GROUP BY data->>'monsterID';`); perkTierNeeded: PerkTier.Four, run: async (user: MUser): CommandResponse => { const { percent } = calcCLDetails(user); - const attachment: Buffer = await pieChart( - 'Your Personal Collection Log Progress', - val => `${val.toFixed(2)}%`, - [ - ['Complete Collection Log Items', percent, '#9fdfb2'], - ['Incomplete Collection Log Items', 100 - percent, '#df9f9f'] - ] + return makeResponseForBuffer( + await createChart({ + title: 'Your Personal Collection Log Progress', + format: 'percent', + values: [ + ['Complete Collection Log Items', percent, '#9fdfb2'], + ['Incomplete Collection Log Items', 100 - percent, '#df9f9f'] + ], + type: 'pie' + }) ); - return makeResponseForBuffer(attachment); } }, { name: 'Global Inferno Death Times', perkTierNeeded: PerkTier.Four, run: async () => { - const result: { mins: number; count: number }[] = - await prisma.$queryRaw`SELECT mins, COUNT(mins)::int FROM (SELECT ((data->>'deathTime')::int / 1000 / 60) as mins + const result: { mins: number; count: number }[] = await prisma.$queryRaw`SELECT mins, COUNT(mins)::int FROM (SELECT ((data->>'deathTime')::int / 1000 / 60) as mins FROM activity WHERE type = 'Inferno' AND completed = true AND data->>'deathTime' IS NOT NULL) death_mins GROUP BY mins;`; if (result.length === 0) return 'No results.'; - const buffer = await lineChart( - 'Global Inferno Death Times', - result.map(i => [i.mins.toString(), i.count]), - val => `${val} Mins` + + return makeResponseForBuffer( + await createChart({ + title: 'Global Inferno Death Times', + format: 'kmb', + values: result.map(i => [i.mins.toString(), i.count]), + type: 'line' + }) ); - return makeResponseForBuffer(buffer); } }, { name: 'Personal Inferno Death Times', perkTierNeeded: PerkTier.Four, run: async (user: MUser) => { - const result: { mins: number; count: number }[] = - await prisma.$queryRawUnsafe(`SELECT mins, COUNT(mins)::int FROM (SELECT ((data->>'deathTime')::int / 1000 / 60) as mins + const result: { mins: number; count: number }[] = await prisma.$queryRawUnsafe(`SELECT mins, COUNT(mins)::int FROM (SELECT ((data->>'deathTime')::int / 1000 / 60) as mins FROM activity WHERE type = 'Inferno' AND user_id = ${BigInt(user.id)} @@ -613,12 +623,15 @@ AND completed = true AND data->>'deathTime' IS NOT NULL) death_mins GROUP BY mins;`); if (result.length === 0) return 'No results.'; - const buffer = await lineChart( - 'Personal Inferno Death Times', - result.map(i => [i.mins.toString(), i.count]), - val => `${val} Mins` + + return makeResponseForBuffer( + await createChart({ + title: 'Personal Inferno Death Times', + format: 'kmb', + values: result.map(i => [i.mins.toString(), i.count]), + type: 'line' + }) ); - return makeResponseForBuffer(buffer); } }, { @@ -636,7 +649,7 @@ GROUP BY mins;`); }); let completedAt = null; let postFirstCapeCompletions = 0; - let totalCost = new Bank(); + const totalCost = new Bank(); for (let i = 0; i < activities.length; i++) { const data = activities[i].data as unknown as InfernoOptions; if (completedAt === null && !data.deathTime) { @@ -660,8 +673,7 @@ GROUP BY mins;`); name: 'Personal TOB Wipes', perkTierNeeded: PerkTier.Four, run: async (user: MUser) => { - const result: { wiped_room: number; count: number }[] = - await prisma.$queryRawUnsafe(`SELECT (data->>'wipedRoom')::int AS wiped_room, COUNT(data->>'wipedRoom')::int + const result: { wiped_room: number; count: number }[] = await prisma.$queryRawUnsafe(`SELECT (data->>'wipedRoom')::int AS wiped_room, COUNT(data->>'wipedRoom')::int FROM activity WHERE type = 'TheatreOfBlood' AND completed = true @@ -672,31 +684,36 @@ GROUP BY 1;`); if (result.length === 0) { return { content: "You haven't wiped in any Theatre of Blood raids yet." }; } - const buffer = await barChart( - 'Personal TOB Deaths', - val => `${val} Deaths`, - result.map(i => [TOBRooms[i.wiped_room].name, i.count]) + + return makeResponseForBuffer( + await createChart({ + title: 'Personal TOB Deaths', + format: 'kmb', + values: result.map(i => [TOBRooms[i.wiped_room].name, i.count]), + type: 'bar' + }) ); - return makeResponseForBuffer(buffer); } }, { name: 'Global TOB Wipes', perkTierNeeded: PerkTier.Four, run: async () => { - const result: { wiped_room: number; count: number }[] = - await prisma.$queryRaw`SELECT (data->>'wipedRoom')::int AS wiped_room, COUNT(data->>'wipedRoom')::int + const result: { wiped_room: number; count: number }[] = await prisma.$queryRaw`SELECT (data->>'wipedRoom')::int AS wiped_room, COUNT(data->>'wipedRoom')::int FROM activity WHERE type = 'TheatreOfBlood' AND completed = true AND data->>'wipedRoom' IS NOT NULL GROUP BY 1;`; - const buffer = await barChart( - 'Global TOB Deaths', - val => `${val} Deaths`, - result.map(i => [TOBRooms[i.wiped_room].name, i.count]) + + return makeResponseForBuffer( + await createChart({ + title: 'Global TOB Deaths', + format: 'kmb', + values: result.map(i => [TOBRooms[i.wiped_room].name, i.count]), + type: 'bar' + }) ); - return makeResponseForBuffer(buffer); } }, { @@ -715,51 +732,58 @@ WHERE "skills.${skillName}" = 200000000::int;`) as Promise<{ qty: number; skill_ ) .map(i => i[0]) .sort((a, b) => b.qty - a.qty); - const buffer = await barChart( - 'Global 200ms', - val => `${val} 200ms`, - result.map(i => [i.skill_name, i.qty]) + + return makeResponseForBuffer( + await createChart({ + title: 'Global 200ms', + format: 'kmb', + values: result.map(i => [i.skill_name, i.qty]), + type: 'bar' + }) ); - return makeResponseForBuffer(buffer); } }, { name: 'Personal Farmed Crops', perkTierNeeded: PerkTier.Four, run: async (user: MUser) => { - const result: { plant: string; qty: number }[] = - await prisma.$queryRawUnsafe(`SELECT data->>'plantsName' as plant, COUNT(data->>'plantsName')::int AS qty + const result: { plant: string; qty: number }[] = await prisma.$queryRawUnsafe(`SELECT data->>'plantsName' as plant, COUNT(data->>'plantsName')::int AS qty FROM activity WHERE type = 'Farming' AND data->>'plantsName' IS NOT NULL AND user_id = ${BigInt(user.id)} GROUP BY data->>'plantsName'`); result.sort((a, b) => b.qty - a.qty); - const buffer = await barChart( - 'Personal Farmed Crops', - val => `${val} Crops`, - result.map(i => [i.plant, i.qty]) + + return makeResponseForBuffer( + await createChart({ + title: 'Personal Farmed Crops', + format: 'kmb', + values: result.map(i => [i.plant, i.qty]), + type: 'bar' + }) ); - return makeResponseForBuffer(buffer); } }, { name: 'Global Farmed Crops', perkTierNeeded: PerkTier.Four, run: async () => { - const result: { plant: string; qty: number }[] = - await prisma.$queryRaw`SELECT data->>'plantsName' as plant, COUNT(data->>'plantsName')::int AS qty + const result: { plant: string; qty: number }[] = await prisma.$queryRaw`SELECT data->>'plantsName' as plant, COUNT(data->>'plantsName')::int AS qty FROM activity WHERE type = 'Farming' AND data->>'plantsName' IS NOT NULL GROUP BY data->>'plantsName'`; result.sort((a, b) => b.qty - a.qty); - const buffer = await barChart( - 'Global Farmed Crops', - val => `${val} Crops`, - result.map(i => [i.plant, i.qty]) + + return makeResponseForBuffer( + await createChart({ + title: 'Global Farmed Crops', + format: 'kmb', + values: result.map(i => [i.plant, i.qty]), + type: 'bar' + }) ); - return makeResponseForBuffer(buffer); } }, { @@ -977,7 +1001,7 @@ ${result const result = await prisma.$queryRawUnsafe( 'SELECT COUNT(*)::int FROM users WHERE "minion.ironman" = true;' ); - return `There are ${parseInt(result[0].count).toLocaleString()} ironman minions!`; + return `There are ${Number.parseInt(result[0].count).toLocaleString()} ironman minions!`; } }, { @@ -1004,7 +1028,7 @@ GROUP BY "bankBackground";`); return result .map( (res: any) => - `**${getBankBgById(res.bankBackground).name}:** ${parseInt(res.count).toLocaleString()}` + `**${getBankBgById(res.bankBackground).name}:** ${Number.parseInt(res.count).toLocaleString()}` ) .join('\n'); } @@ -1014,7 +1038,7 @@ GROUP BY "bankBackground";`); perkTierNeeded: PerkTier.Four, run: async () => { const result = await prisma.$queryRawUnsafe('SELECT SUM ("sacrificedValue") AS total FROM users;'); - return `There has been ${parseInt(result[0].total).toLocaleString()} GP worth of items sacrificed!`; + return `There has been ${Number.parseInt(result[0].total).toLocaleString()} GP worth of items sacrificed!`; } }, { @@ -1040,7 +1064,7 @@ GROUP BY "bankBackground";`); str += Object.entries(totalBank) .sort(([, qty1], [, qty2]) => qty2 - qty1) .map(([monID, qty]) => { - return `${Monsters.get(parseInt(monID))?.name}: ${qty.toLocaleString()}`; + return `${Monsters.get(Number.parseInt(monID))?.name}: ${qty.toLocaleString()}`; }) .join('\n'); @@ -1070,7 +1094,7 @@ GROUP BY "bankBackground";`); return Object.entries(totalBank) .map( ([clueID, qty]) => - `**${ClueTiers.find(t => t.id === parseInt(clueID))?.name}:** ${qty.toLocaleString()}` + `**${ClueTiers.find(t => t.id === Number.parseInt(clueID))?.name}:** ${qty.toLocaleString()}` ) .join('\n'); } @@ -1084,8 +1108,8 @@ GROUP BY "bankBackground";`); let res = `${Emoji.Casket} **${user.minionName}'s Clue Scores:**\n\n`; for (const [clueID, clueScore] of Object.entries(clueScores.bank)) { - const clue = ClueTiers.find(c => c.id === parseInt(clueID)); - res += `**${clue!.name}**: ${clueScore.toLocaleString()}\n`; + const clue = ClueTiers.find(c => c.id === Number.parseInt(clueID)); + res += `**${clue?.name}**: ${clueScore.toLocaleString()}\n`; } return res; } @@ -1101,13 +1125,13 @@ GROUP BY "bankBackground";`); name: 'Personal Agility Stats', perkTierNeeded: null, run: async (user, stats) => { - const entries = Object.entries(stats.laps_scores as ItemBank).map(arr => [parseInt(arr[0]), arr[1]]); + const entries = Object.entries(stats.laps_scores as ItemBank).map(arr => [Number.parseInt(arr[0]), arr[1]]); const sepulchreCount = await getMinigameScore(user.id, 'sepulchre'); if (sepulchreCount === 0 && entries.length === 0) { return "You haven't done any laps yet! Sad."; } const data = `${entries - .map(([id, qty]) => `**${Agility.Courses.find(c => c.id === id)!.name}:** ${qty}`) + .map(([id, qty]) => `**${Agility.Courses.find(c => c.id === id)?.name}:** ${qty}`) .join('\n')}\n**Hallowed Sepulchre:** ${sepulchreCount}`; return data; } @@ -1238,7 +1262,7 @@ GROUP BY "bankBackground";`); items_sent: true } }); - let items = new Bank(); + const items = new Bank(); for (const g of giveaways) { items.add(g.items_sent as ItemBank); } @@ -1259,7 +1283,7 @@ GROUP BY "bankBackground";`); items_sent: true } }); - let items = new Bank(); + const items = new Bank(); for (const g of giveaways) { items.add(g.items_sent as ItemBank); } @@ -1436,24 +1460,24 @@ LIMIT 5;` }[][]; const response = `**Luckiest CoX Raiders** -${luckiest - .map( - i => - `${getUsername(i.id)}: ${i.points_per_item.toLocaleString()} points per item / 1 in ${( - i.raids_total_kc / i.total_cox_items - ).toFixed(1)} raids` +${( + await Promise.all( + luckiest.map( + async i => + `${await getUsername(i.id)}: ${i.points_per_item.toLocaleString()} points per item / 1 in ${(i.raids_total_kc / i.total_cox_items).toFixed(1)} raids` + ) ) - .join('\n')} +).join('\n')} **Unluckiest CoX Raiders** -${unluckiest - .map( - i => - `${getUsername(i.id)}: ${i.points_per_item.toLocaleString()} points per item / 1 in ${( - i.raids_total_kc / i.total_cox_items - ).toFixed(1)} raids` +${( + await Promise.all( + unluckiest.map( + async i => + `${await getUsername(i.id)}: ${i.points_per_item.toLocaleString()} points per item / 1 in ${(i.raids_total_kc / i.total_cox_items).toFixed(1)} raids` + ) ) - .join('\n')}`; +).join('\n')}`; return { content: response }; @@ -1560,7 +1584,12 @@ ${Object.entries(result) const result = await fetchHistoricalDataDifferences(user); const dataPoints: [string, number][] = result.map(i => [i.week_start, i.diff_total_xp]); return makeResponseForBuffer( - await barChart('Your Weekly XP Gains', val => `${toKMB(val)} XP`, dataPoints, true) + await createChart({ + title: 'Your Weekly XP Gains', + format: 'kmb', + values: dataPoints, + type: 'bar' + }) ); } }, @@ -1571,7 +1600,12 @@ ${Object.entries(result) const result = await fetchHistoricalDataDifferences(user); const dataPoints: [string, number][] = result.map(i => [i.week_start, i.diff_cl_completion_count]); return makeResponseForBuffer( - await barChart('Your Weekly CL slot Gains', val => `${toKMB(val)} Slots`, dataPoints, true) + await createChart({ + title: 'Your Weekly CL slot Gains', + format: 'kmb', + values: dataPoints, + type: 'bar' + }) ); } }, @@ -1582,12 +1616,12 @@ ${Object.entries(result) const result = await fetchHistoricalDataDifferences(user); const dataPoints: [string, number][] = result.map(i => [i.week_start, i.diff_cl_global_rank]); return makeResponseForBuffer( - await barChart( - 'Your Weekly CL leaderboard rank gains', - val => `${val > 0 ? `+${val}` : val}`, - dataPoints, - true - ) + await createChart({ + title: 'Your Weekly CL leaderboard rank gains', + format: 'delta', + values: dataPoints, + type: 'bar' + }) ); } }, @@ -1598,7 +1632,12 @@ ${Object.entries(result) const result = await fetchHistoricalDataDifferences(user); const dataPoints: [string, number][] = result.map(i => [i.week_start, i.diff_GP]); return makeResponseForBuffer( - await barChart('Your Weekly GP gains', val => `${toKMB(val)} GP`, dataPoints, true) + await createChart({ + title: 'Your Weekly GP gains', + format: 'delta', + values: dataPoints, + type: 'bar' + }) ); } }, diff --git a/src/mahoji/lib/abstracted_commands/stealingCreation.ts b/src/mahoji/lib/abstracted_commands/stealingCreation.ts index 440070461e8..8046c31ddf5 100644 --- a/src/mahoji/lib/abstracted_commands/stealingCreation.ts +++ b/src/mahoji/lib/abstracted_commands/stealingCreation.ts @@ -1,6 +1,6 @@ import { Time } from 'e'; -import { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; +import type { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; import { formatDuration, randomVariation } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; @@ -12,7 +12,7 @@ export async function stealingCreationCommand(user: MUser, channelID: string) { const quantity = Math.floor(calcMaxTripLength(user, 'StealingCreation') / gameTime); const duration = randomVariation(quantity * gameTime, 5); - let str = `${ + const str = `${ user.minionName } is now off to do ${quantity} Stealing Creation games. The total trip will take ${formatDuration(duration)}.`; diff --git a/src/mahoji/lib/abstracted_commands/strongHoldOfSecurityCommand.ts b/src/mahoji/lib/abstracted_commands/strongHoldOfSecurityCommand.ts index e4a0d924640..c3ff815ef08 100644 --- a/src/mahoji/lib/abstracted_commands/strongHoldOfSecurityCommand.ts +++ b/src/mahoji/lib/abstracted_commands/strongHoldOfSecurityCommand.ts @@ -1,6 +1,5 @@ import { Time } from 'e'; -import { prisma } from '../../../lib/settings/prisma'; import type { ActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; import { randomVariation } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; diff --git a/src/mahoji/lib/abstracted_commands/tearsOfGuthixCommand.ts b/src/mahoji/lib/abstracted_commands/tearsOfGuthixCommand.ts index ebc7f1c4e84..79502ffcdac 100644 --- a/src/mahoji/lib/abstracted_commands/tearsOfGuthixCommand.ts +++ b/src/mahoji/lib/abstracted_commands/tearsOfGuthixCommand.ts @@ -1,9 +1,10 @@ -import { notEmpty, objectEntries, Time } from 'e'; +import { Time, notEmpty, objectEntries } from 'e'; +import { formatDuration } from '@oldschoolgg/toolkit'; import { Emoji } from '../../../lib/constants'; import { SkillsEnum } from '../../../lib/skilling/types'; import type { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; -import { formatDuration, formatSkillRequirements, hasSkillReqs } from '../../../lib/util'; +import { formatSkillRequirements, hasSkillReqs } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { minionIsBusy } from '../../../lib/util/minionIsBusy'; diff --git a/src/mahoji/lib/abstracted_commands/temporossCommand.ts b/src/mahoji/lib/abstracted_commands/temporossCommand.ts index ef1767e59ac..27ade97a8db 100644 --- a/src/mahoji/lib/abstracted_commands/temporossCommand.ts +++ b/src/mahoji/lib/abstracted_commands/temporossCommand.ts @@ -1,7 +1,7 @@ -import { calcWhatPercent, reduceNumByPercent, Time } from 'e'; +import { Time, calcWhatPercent, reduceNumByPercent } from 'e'; import { getMinigameScore } from '../../../lib/settings/minigames'; -import { TemporossActivityTaskOptions } from '../../../lib/types/minions'; +import type { TemporossActivityTaskOptions } from '../../../lib/types/minions'; import { formatDuration } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; diff --git a/src/mahoji/lib/abstracted_commands/tiaraRunecraftCommand.ts b/src/mahoji/lib/abstracted_commands/tiaraRunecraftCommand.ts index 05ec26af51f..380951a317f 100644 --- a/src/mahoji/lib/abstracted_commands/tiaraRunecraftCommand.ts +++ b/src/mahoji/lib/abstracted_commands/tiaraRunecraftCommand.ts @@ -2,9 +2,10 @@ import { Time } from 'e'; import { Bank } from 'oldschooljs'; import { SkillsEnum } from 'oldschooljs/dist/constants'; +import { formatDuration } from '@oldschoolgg/toolkit'; import Runecraft from '../../../lib/skilling/skills/runecraft'; -import { TiaraRunecraftActivityTaskOptions } from '../../../lib/types/minions'; -import { formatDuration, stringMatches } from '../../../lib/util'; +import type { TiaraRunecraftActivityTaskOptions } from '../../../lib/types/minions'; +import { stringMatches } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; import { updateBankSetting } from '../../../lib/util/updateBankSetting'; diff --git a/src/mahoji/lib/abstracted_commands/tinkeringWorkshopCommand.ts b/src/mahoji/lib/abstracted_commands/tinkeringWorkshopCommand.ts index 0451d6a6c7e..8929b7fed15 100644 --- a/src/mahoji/lib/abstracted_commands/tinkeringWorkshopCommand.ts +++ b/src/mahoji/lib/abstracted_commands/tinkeringWorkshopCommand.ts @@ -1,10 +1,10 @@ import { Time } from 'e'; -import { MaterialType, materialTypes } from '../../../lib/invention'; -import { transactMaterialsFromUser } from '../../../lib/invention/inventions'; +import { type MaterialType, materialTypes } from '../../../lib/invention'; import { MaterialBank } from '../../../lib/invention/MaterialBank'; -import { ItemBank } from '../../../lib/types'; -import { TinkeringWorkshopOptions } from '../../../lib/types/minions'; +import { transactMaterialsFromUser } from '../../../lib/invention/inventions'; +import type { ItemBank } from '../../../lib/types'; +import type { TinkeringWorkshopOptions } from '../../../lib/types/minions'; import { formatDuration, randomVariation } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; @@ -28,15 +28,13 @@ export async function tinkeringWorkshopCommand(user: MUser, material: MaterialTy return `You don't have enough materials to workshop with this material, you need: ${materialCost}.`; } await transactMaterialsFromUser({ user, remove: materialCost }); - await userStatsUpdate(user.id, oldStats => { - return { - tworkshop_material_cost_bank: new MaterialBank(oldStats.tworkshop_material_cost_bank as ItemBank).add( - materialCost - ).bank - }; + const stats = await user.fetchStats({ tworkshop_material_cost_bank: true }); + await userStatsUpdate(user.id, { + tworkshop_material_cost_bank: new MaterialBank(stats.tworkshop_material_cost_bank as ItemBank).add(materialCost) + .bank }); - let str = `${ + const str = `${ user.minionName } is now off to do ${quantity}x Tinkering Workshop projects! The total trip will take ${formatDuration( duration diff --git a/src/mahoji/lib/abstracted_commands/titheFarmCommand.ts b/src/mahoji/lib/abstracted_commands/titheFarmCommand.ts index aa4a71b8185..0b54b08c3a7 100644 --- a/src/mahoji/lib/abstracted_commands/titheFarmCommand.ts +++ b/src/mahoji/lib/abstracted_commands/titheFarmCommand.ts @@ -1,10 +1,10 @@ -import { ChatInputCommandInteraction } from 'discord.js'; +import type { ChatInputCommandInteraction } from 'discord.js'; import { Time } from 'e'; import { Bank } from 'oldschooljs'; import { Emoji } from '../../../lib/constants'; import TitheFarmBuyables from '../../../lib/data/buyables/titheFarmBuyables'; -import { TitheFarmActivityTaskOptions } from '../../../lib/types/minions'; +import type { TitheFarmActivityTaskOptions } from '../../../lib/types/minions'; import { formatDuration, stringMatches } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { handleMahojiConfirmation } from '../../../lib/util/handleMahojiConfirmation'; @@ -68,9 +68,7 @@ export async function titheFarmShopCommand( _quantity?: number ) { const buyable = TitheFarmBuyables.find( - item => - stringMatches(buyableName, item.name) || - (item.aliases && item.aliases.some(alias => stringMatches(alias, buyableName))) + item => stringMatches(buyableName, item.name) || item.aliases?.some(alias => stringMatches(alias, buyableName)) ); if (!buyable) { @@ -90,7 +88,7 @@ export async function titheFarmShopCommand( return `You need ${cost} Tithe Farm points to make this purchase.`; } - let purchaseMsg = `${loot} for ${cost} Tithe Farm points`; + const purchaseMsg = `${loot} for ${cost} Tithe Farm points`; await handleMahojiConfirmation(interaction, `${user}, please confirm that you want to purchase ${purchaseMsg}.`); await userStatsUpdate( diff --git a/src/mahoji/lib/abstracted_commands/tobCommand.ts b/src/mahoji/lib/abstracted_commands/tobCommand.ts index 58af2c36b80..38c7d936ffe 100644 --- a/src/mahoji/lib/abstracted_commands/tobCommand.ts +++ b/src/mahoji/lib/abstracted_commands/tobCommand.ts @@ -3,23 +3,24 @@ import { Bank } from 'oldschooljs'; import { TOBRooms } from 'oldschooljs/dist/simulation/misc/TheatreOfBlood'; import { randomVariation } from 'oldschooljs/dist/util'; +import { formatDuration } from '@oldschoolgg/toolkit'; import { Emoji } from '../../../lib/constants'; import { gorajanArcherOutfit, gorajanOccultOutfit, gorajanWarriorOutfit } from '../../../lib/data/CollectionsExport'; import { getSimilarItems } from '../../../lib/data/similarItems'; import { + TENTACLE_CHARGES_PER_RAID, baseTOBUniques, calcTOBBaseDuration, calculateTOBDeaths, calculateTOBUserGearPercents, createTOBRaid, - minimumTOBSuppliesNeeded, - TENTACLE_CHARGES_PER_RAID + minimumTOBSuppliesNeeded } from '../../../lib/data/tob'; import { checkUserCanUseDegradeableItem, degradeItem } from '../../../lib/degradeableItems'; import { + InventionID, canAffordInventionBoost, inventionBoosts, - InventionID, inventionItemBoost } from '../../../lib/invention/inventions'; import { trackLoot } from '../../../lib/lootTrack'; @@ -27,9 +28,9 @@ import { blowpipeDarts } from '../../../lib/minions/functions/blowpipeCommand'; import getUserFoodFromBank from '../../../lib/minions/functions/getUserFoodFromBank'; import { setupParty } from '../../../lib/party'; import { getMinigameScore } from '../../../lib/settings/minigames'; -import { MakePartyOptions } from '../../../lib/types'; -import { TheatreOfBloodTaskOptions } from '../../../lib/types/minions'; -import { channelIsSendable, formatDuration, formatSkillRequirements, skillsMeetRequirements } from '../../../lib/util'; +import type { MakePartyOptions } from '../../../lib/types'; +import type { TheatreOfBloodTaskOptions } from '../../../lib/types/minions'; +import { channelIsSendable, formatSkillRequirements, skillsMeetRequirements } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; import { determineRunes } from '../../../lib/util/determineRunes'; @@ -49,13 +50,13 @@ const minStats = { const SCYTHE_CHARGES_PER_RAID = 200; -export async function calcTOBInput(u: MUser) { +async function calcTOBInput(u: MUser) { const items = new Bank(); const kc = await getMinigameScore(u.id, 'tob'); items.add('Super combat potion(4)', 1); items.add('Ranging potion(4)', 1); - let brewsNeeded = Math.max(1, 6 - Math.max(1, Math.ceil((kc + 1) / 10))); + const brewsNeeded = Math.max(1, 6 - Math.max(1, Math.ceil((kc + 1) / 10))); const restoresNeeded = Math.max(2, Math.floor(brewsNeeded / 3)); let healingNeeded = 60; @@ -82,11 +83,11 @@ export async function calcTOBInput(u: MUser) { return items; } -export async function checkTOBUser( +async function checkTOBUser( user: MUser, isHardMode: boolean, teamSize?: number, - quantity: number = 1 + quantity = 1 ): Promise<[false] | [true, string]> { if (!user.user.minion_hasBought) { return [true, `${user.usernameOrMention} doesn't have a minion`]; @@ -188,11 +189,11 @@ export async function checkTOBUser( } const dartsNeeded = 150 * quantity; if (blowpipeData.dartQuantity < dartsNeeded) { - return [true, `${user.usernameOrMention}, you need atleast ${dartsNeeded} darts in your blowpipe.`]; + return [true, `${user.usernameOrMention}, you need at least ${dartsNeeded} darts in your blowpipe.`]; } const scalesNeeded = 1000 * quantity; if (blowpipeData.scales < scalesNeeded) { - return [true, `${user.usernameOrMention}, you need atleast ${scalesNeeded} scales in your blowpipe.`]; + return [true, `${user.usernameOrMention}, you need at least ${scalesNeeded} scales in your blowpipe.`]; } const dartIndex = blowpipeDarts.indexOf(getOSItem(blowpipeData.dartID)); if (dartIndex < 5) { @@ -224,7 +225,7 @@ export async function checkTOBUser( if (!user.hasEquipped('Chincannon') && rangeGear.ammo!.quantity < arrowsRequired) { return [ true, - `${user.usernameOrMention}, you need atleast ${arrowsRequired} arrows equipped in your range setup.` + `${user.usernameOrMention}, you need at least ${arrowsRequired} arrows equipped in your range setup.` ]; } @@ -232,7 +233,7 @@ export async function checkTOBUser( const kc = await getMinigameScore(user.id, 'tob'); if (kc < 250) { - return [true, `${user.usernameOrMention} needs atleast 250 Theatre of Blood KC before doing Hard mode.`]; + return [true, `${user.usernameOrMention} needs at least 250 Theatre of Blood KC before doing Hard mode.`]; } if (!meleeGear.hasEquipped('Infernal cape')) { return [true, `${user.usernameOrMention} needs at least an Infernal cape to do Hard mode.`]; @@ -266,7 +267,7 @@ export async function checkTOBTeam( users: MUser[], isHardMode: boolean, solo: 'solo' | 'trio' | undefined, - quantity: number = 1 + quantity = 1 ): Promise { const userWithoutSupplies = users.find(u => !u.bank.has(minimumTOBSuppliesNeeded)); if (userWithoutSupplies) { @@ -280,7 +281,6 @@ export async function checkTOBTeam( if (user.minionIsBusy) return `${user.usernameOrMention}'s minion is busy.`; const checkResult = await checkTOBUser(user, isHardMode, solo === 'trio' ? 3 : users.length, quantity); if (!checkResult[0]) { - continue; } else { return checkResult[1]; } @@ -341,14 +341,14 @@ export async function tobStartCommand( if (isHardMode) { const normalKC = await getMinigameScore(user.id, 'tob'); if (normalKC < 250) { - return 'You need atleast 250 completions of the Theatre of Blood before you can attempt Hard Mode.'; + return 'You need at least 250 completions of the Theatre of Blood before you can attempt Hard Mode.'; } } if (user.minionIsBusy) { return "Your minion is busy, so you can't start a raid."; } - let maxSize = mahojiParseNumber({ input: maxSizeInput, min: 2, max: 5 }) ?? 5; + const maxSize = mahojiParseNumber({ input: maxSizeInput, min: 2, max: 5 }) ?? 5; const partyOptions: MakePartyOptions = { leader: user, @@ -422,7 +422,7 @@ export async function tobStartCommand( let totalDuration = 0; let totalFakeDuration = 0; - let deaths: number[][][] = []; + const deaths: number[][][] = []; let chinCannonUser: MUser | null = null; const wipedRooms: (number | null)[] = []; @@ -479,7 +479,7 @@ export async function tobStartCommand( preChincannonCost.add(u.gear.range.ammo!.item, 100); } const { realCost } = await u.specialRemoveItems(preChincannonCost.multiply(qty)); - await userStatsBankUpdate(u.id, 'tob_cost', realCost); + await userStatsBankUpdate(u, 'tob_cost', realCost); const effectiveCost = realCost.clone().remove('Coins', realCost.amount('Coins')); totalCost.add(effectiveCost); if (isChincannonUser) { diff --git a/src/mahoji/lib/abstracted_commands/trekCommand.ts b/src/mahoji/lib/abstracted_commands/trekCommand.ts index ce623cfd861..6e74620c8db 100644 --- a/src/mahoji/lib/abstracted_commands/trekCommand.ts +++ b/src/mahoji/lib/abstracted_commands/trekCommand.ts @@ -1,4 +1,4 @@ -import { ChatInputCommandInteraction } from 'discord.js'; +import type { ChatInputCommandInteraction } from 'discord.js'; import { objectEntries, randInt, reduceNumByPercent } from 'e'; import { Bank } from 'oldschooljs'; @@ -6,10 +6,10 @@ import TrekShopItems, { TrekExperience } from '../../../lib/data/buyables/trekBu import { MorytaniaDiary, userhasDiaryTier } from '../../../lib/diaries'; import { GearStat } from '../../../lib/gear/types'; import { difficulties, rewardTokens, trekBankBoosts } from '../../../lib/minions/data/templeTrekking'; -import { AddXpParams, GearRequirement } from '../../../lib/minions/types'; +import type { AddXpParams, GearRequirement } from '../../../lib/minions/types'; import { getMinigameScore } from '../../../lib/settings/minigames'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { TempleTrekkingActivityTaskOptions } from '../../../lib/types/minions'; +import type { TempleTrekkingActivityTaskOptions } from '../../../lib/types/minions'; import { formatDuration, percentChance, readableStatName, stringMatches } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; @@ -41,9 +41,9 @@ export async function trekCommand(user: MUser, channelID: string, difficulty: st ); if (setup === 'melee') { - if (maxMeleeStat[0] !== GearStat.AttackCrush) delete newRequirements.attack_crush; - if (maxMeleeStat[0] !== GearStat.AttackSlash) delete newRequirements.attack_slash; - if (maxMeleeStat[0] !== GearStat.AttackStab) delete newRequirements.attack_stab; + if (maxMeleeStat[0] !== GearStat.AttackCrush) newRequirements.attack_crush = undefined; + if (maxMeleeStat[0] !== GearStat.AttackSlash) newRequirements.attack_slash = undefined; + if (maxMeleeStat[0] !== GearStat.AttackStab) newRequirements.attack_stab = undefined; } else { newRequirements = requirements; } @@ -52,8 +52,8 @@ export async function trekCommand(user: MUser, channelID: string, difficulty: st if (!meetsRequirements) { return `You don't have the requirements to do ${tier.difficulty} treks! Your ${readableStatName( unmetKey! - )} stat in your ${setup} setup is ${has}, but you need atleast ${ - tier.minimumGearRequirements[setup]![unmetKey!] + )} stat in your ${setup} setup is ${has}, but you need at least ${ + tier.minimumGearRequirements[setup]?.[unmetKey!] }.`; } } @@ -61,7 +61,7 @@ export async function trekCommand(user: MUser, channelID: string, difficulty: st } if (qp < 30) { - return 'You need atleast level 30 QP to do Temple Trekking.'; + return 'You need at least level 30 QP to do Temple Trekking.'; } if (minLevel !== undefined && user.combatLevel < minLevel) { @@ -150,9 +150,7 @@ export async function trekShop( ) { const userBank = user.bank; const specifiedItem = TrekShopItems.find( - item => - stringMatches(reward, item.name) || - (item.aliases && item.aliases.some(alias => stringMatches(alias, reward))) + item => stringMatches(reward, item.name) || item.aliases?.some(alias => stringMatches(alias, reward)) ); if (!specifiedItem) { @@ -161,22 +159,22 @@ export async function trekShop( }).join(', ')}.`; } - let inbankquantity = + const inbankquantity = difficulty === 'Easy' ? userBank.amount(rewardTokens.easy) : difficulty === 'Medium' - ? userBank.amount(rewardTokens.medium) - : userBank.amount(rewardTokens.hard); + ? userBank.amount(rewardTokens.medium) + : userBank.amount(rewardTokens.hard); if (quantity === undefined) { quantity = inbankquantity; } if (quantity > inbankquantity || quantity === 0) { return "You don't have enough reward tokens for that."; } - let outItems = new Bank(); + const outItems = new Bank(); - let inItems = new Bank(); - let outXP: AddXpParams[] = [ + const inItems = new Bank(); + const outXP: AddXpParams[] = [ { skillName: SkillsEnum.Agility, amount: 0, diff --git a/src/mahoji/lib/abstracted_commands/troubleBrewingCommand.ts b/src/mahoji/lib/abstracted_commands/troubleBrewingCommand.ts index dad7f59c2c0..651fc5c0a63 100644 --- a/src/mahoji/lib/abstracted_commands/troubleBrewingCommand.ts +++ b/src/mahoji/lib/abstracted_commands/troubleBrewingCommand.ts @@ -1,13 +1,13 @@ import { Time } from 'e'; +import { formatDuration } from '@oldschoolgg/toolkit'; import type { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; -import { formatDuration } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; export async function troubleBrewingStartCommand(user: MUser, channelID: string) { - let timePerGame = Time.Minute * 20; - let maxTripLength = calcMaxTripLength(user, 'TroubleBrewing'); + const timePerGame = Time.Minute * 20; + const maxTripLength = calcMaxTripLength(user, 'TroubleBrewing'); const quantity = Math.floor(maxTripLength / timePerGame); const duration = quantity * timePerGame; diff --git a/src/mahoji/lib/abstracted_commands/underwaterCommand.ts b/src/mahoji/lib/abstracted_commands/underwaterCommand.ts index 476d521271e..98222dbee54 100644 --- a/src/mahoji/lib/abstracted_commands/underwaterCommand.ts +++ b/src/mahoji/lib/abstracted_commands/underwaterCommand.ts @@ -1,11 +1,11 @@ -import { randFloat, reduceNumByPercent, Time } from 'e'; +import { Time, randFloat, reduceNumByPercent } from 'e'; import { Bank } from 'oldschooljs'; -import { UnderwaterAgilityThievingTrainingSkill } from '../../../lib/constants'; +import type { UnderwaterAgilityThievingTrainingSkill } from '../../../lib/constants'; import { formatDuration } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; -import { UnderwaterAgilityThievingTaskOptions } from './../../../lib/types/minions'; +import type { UnderwaterAgilityThievingTaskOptions } from './../../../lib/types/minions'; export async function underwaterAgilityThievingCommand( channelID: string, @@ -25,9 +25,10 @@ export async function underwaterAgilityThievingCommand( return 'You need Graceful top, legs and gloves to do Underwater Agility and Thieving.'; } - if (minutes < 1 || !Number.isInteger(minutes) || isNaN(minutes)) return 'Please specify a valid number of minutes.'; + if (minutes < 1 || !Number.isInteger(minutes) || Number.isNaN(minutes)) + return 'Please specify a valid number of minutes.'; - let tripLength = Time.Minute * minutes; + const tripLength = Time.Minute * minutes; if (tripLength > maxTripLength) { return `${user.minionName} can't go on trips longer than ${formatDuration( diff --git a/src/mahoji/lib/abstracted_commands/useCommand.ts b/src/mahoji/lib/abstracted_commands/useCommand.ts index 52d1d9cac84..e6afd84d60e 100644 --- a/src/mahoji/lib/abstracted_commands/useCommand.ts +++ b/src/mahoji/lib/abstracted_commands/useCommand.ts @@ -1,8 +1,8 @@ +import type { CommandResponse } from '@oldschoolgg/toolkit'; import { AttachmentBuilder, bold } from 'discord.js'; -import { notEmpty, objectEntries, randArrItem, randInt, Time } from 'e'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; +import { Time, notEmpty, objectEntries, randArrItem, randInt } from 'e'; import { Bank } from 'oldschooljs'; -import { Item } from 'oldschooljs/dist/meta/types'; +import type { Item } from 'oldschooljs/dist/meta/types'; import { divinationEnergies } from '../../../lib/bso/divination'; import { BitField } from '../../../lib/constants'; @@ -39,7 +39,7 @@ interface Usable { items: Item[]; run: (user: MUser) => CommandResponse | Awaited; } -export const usables: Usable[] = []; +const usables: Usable[] = []; interface UsableUnlock { item: Item; diff --git a/src/mahoji/lib/abstracted_commands/vasaCommand.ts b/src/mahoji/lib/abstracted_commands/vasaCommand.ts index 16465bfbf71..825d5186f2c 100644 --- a/src/mahoji/lib/abstracted_commands/vasaCommand.ts +++ b/src/mahoji/lib/abstracted_commands/vasaCommand.ts @@ -1,6 +1,5 @@ -import { EmbedBuilder, TextChannel } from 'discord.js'; -import { randInt, sumArr, Time } from 'e'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; +import { EmbedBuilder, type TextChannel } from 'discord.js'; +import { Time, randInt, sumArr } from 'e'; import { Bank } from 'oldschooljs'; import { VasaMagus } from '../../../lib/minions/data/killableMonsters/custom/bosses/VasaMagus'; @@ -8,7 +7,7 @@ import { BossInstance } from '../../../lib/structures/Boss'; import { Gear } from '../../../lib/structures/Gear'; import { formatDuration } from '../../../lib/util'; -export async function vasaCommand(user: MUser, channelID: string, quantity?: number): CommandResponse { +export async function vasaCommand(user: MUser, channelID: string, quantity?: number) { const instance = new BossInstance({ leader: user, id: VasaMagus.id, @@ -78,7 +77,7 @@ ${bossUsers.map(u => `**${u.user.usernameOrMention}**: ${u.debugStr}`).join('\n\ return { embeds: [embed.data], - content: instance.boosts.length > 0 ? `**Boosts:** ${instance.boosts.join(', ')}.` : undefined + content: instance.boosts.length > 0 ? `**Boosts:** ${instance.boosts.join(', ')}.` : 'No boosts.' }; } catch (err: any) { return `The mass failed to start for this reason: ${err}.`; diff --git a/src/mahoji/lib/abstracted_commands/volcanicMineCommand.ts b/src/mahoji/lib/abstracted_commands/volcanicMineCommand.ts index 13b2dd03602..540aaecac99 100644 --- a/src/mahoji/lib/abstracted_commands/volcanicMineCommand.ts +++ b/src/mahoji/lib/abstracted_commands/volcanicMineCommand.ts @@ -1,10 +1,10 @@ -import { ChatInputCommandInteraction } from 'discord.js'; -import { objectEntries, Time } from 'e'; +import type { ChatInputCommandInteraction } from 'discord.js'; +import { Time, objectEntries } from 'e'; import { Bank } from 'oldschooljs'; import { getMinigameScore } from '../../../lib/settings/minigames'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; +import type { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; import { formatDuration, formatSkillRequirements, hasSkillReqs, stringMatches } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; @@ -165,7 +165,7 @@ export async function volcanicMineCommand(user: MUser, channelID: string, gameQu boosts.push('20% more Mining XP for having Doug helping you!'); } - let duration = VolcanicMineGameTime * gameQuantity; + const duration = VolcanicMineGameTime * gameQuantity; const str = `${ user.minionName @@ -228,9 +228,7 @@ export async function volcanicMineShopCommand( } }); - return `You sucessfully bought **${quantity.toLocaleString()}x ${shopItem.name}** for ${( - shopItem.cost * quantity - ).toLocaleString()} Volcanic Mine points.${ + return `You sucessfully bought **${quantity.toLocaleString()}x ${shopItem.name}** for ${(shopItem.cost * quantity).toLocaleString()} Volcanic Mine points.${ shopItem.clOnly ? `\n${quantity > 1 ? 'These items were' : 'This item was'} directly added to your collection log.` : '' @@ -241,6 +239,6 @@ export async function volcanicMineStatsCommand(user: MUser) { const currentUserPoints = user.user.volcanic_mine_points; const kc = await getMinigameScore(user.id, 'volcanic_mine'); - return `You have ${currentUserPoints.toLocaleString()} Volanic Mine points points. + return `You have ${currentUserPoints.toLocaleString()} Volcanic Mine points. You have completed ${kc} games of Volcanic Mine.`; } diff --git a/src/mahoji/lib/abstracted_commands/warriorsGuildCommand.ts b/src/mahoji/lib/abstracted_commands/warriorsGuildCommand.ts index 7612b07fc38..b7a1857ea89 100644 --- a/src/mahoji/lib/abstracted_commands/warriorsGuildCommand.ts +++ b/src/mahoji/lib/abstracted_commands/warriorsGuildCommand.ts @@ -1,7 +1,7 @@ import { Time } from 'e'; import { Bank } from 'oldschooljs'; -import { ActivityTaskOptionsWithQuantity, AnimatedArmourActivityTaskOptions } from '../../../lib/types/minions'; +import type { ActivityTaskOptionsWithQuantity, AnimatedArmourActivityTaskOptions } from '../../../lib/types/minions'; import { formatDuration } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; @@ -82,7 +82,7 @@ async function cyclopsCommand(user: MUser, channelID: string, quantity: number | // Check if either 100 warrior guild tokens or attack cape (similar items in future) const amountTokens = userBank.amount('Warrior guild token'); if (!hasAttackCape && amountTokens < 100) { - return 'You need atleast 100 Warriors guild tokens to kill Cyclops.'; + return 'You need at least 100 Warriors guild tokens to kill Cyclops.'; } // If no quantity provided, set it to the max. if (!quantity) { @@ -107,7 +107,7 @@ async function cyclopsCommand(user: MUser, channelID: string, quantity: number | if (!hasAttackCape && amountTokens < tokensToSpend) { return `You don't have enough Warrior guild tokens to kill cyclopes for ${formatDuration( duration - )}, try a lower quantity. You need atleast ${Math.floor( + )}, try a lower quantity. You need at least ${Math.floor( (duration / Time.Minute) * 10 + 10 )}x Warrior guild tokens to kill ${quantity}x cyclopes.`; } @@ -120,7 +120,7 @@ async function cyclopsCommand(user: MUser, channelID: string, quantity: number | type: 'Cyclops' }); - let response = `${user.minionName} is now off to kill ${quantity}x Cyclops, it'll take around ${formatDuration( + const response = `${user.minionName} is now off to kill ${quantity}x Cyclops, it'll take around ${formatDuration( duration )} to finish. ${ hasAttackCape @@ -144,7 +144,7 @@ export async function warriorsGuildCommand( const atkLvl = user.skillLevel('attack'); const strLvl = user.skillLevel('strength'); if (atkLvl + strLvl < 130 && atkLvl !== 99 && strLvl !== 99) { - return "To enter the Warrior's Guild, your Attack and Strength levels must add up to atleast 130, or you must have level 99 in either."; + return "To enter the Warrior's Guild, your Attack and Strength levels must add up to at least 130, or you must have level 99 in either."; } if (choice === 'cyclops') { diff --git a/src/mahoji/lib/abstracted_commands/wintertodtCommand.ts b/src/mahoji/lib/abstracted_commands/wintertodtCommand.ts index b229c0c1d25..6e336ec5d6f 100644 --- a/src/mahoji/lib/abstracted_commands/wintertodtCommand.ts +++ b/src/mahoji/lib/abstracted_commands/wintertodtCommand.ts @@ -1,32 +1,38 @@ -import { calcWhatPercent, reduceNumByPercent, Time } from 'e'; +import { Time, calcWhatPercent, reduceNumByPercent } from 'e'; import { Bank } from 'oldschooljs'; import { SkillsEnum } from 'oldschooljs/dist/constants'; import { Eatables } from '../../../lib/data/eatables'; import { warmGear } from '../../../lib/data/filterables'; import { trackLoot } from '../../../lib/lootTrack'; -import { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; +import type { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; import { formatDuration } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; import { updateBankSetting } from '../../../lib/util/updateBankSetting'; -export async function wintertodtCommand(user: MUser, channelID: string) { +export async function wintertodtCommand(user: MUser, channelID: string, quantity?: number) { const fmLevel = user.skillLevel(SkillsEnum.Firemaking); const wcLevel = user.skillLevel(SkillsEnum.Woodcutting); if (fmLevel < 50) { return 'You need 50 Firemaking to have a chance at defeating the Wintertodt.'; } - const messages = []; - let durationPerTodt = Time.Minute * 7.3; // Up to a 10% boost for 99 WC const wcBoost = (wcLevel + 1) / 10; + const boosts: string[] = []; + const foodStr: string[] = []; + if (wcBoost > 1) { - messages.push(`**Boosts:** ${wcBoost.toFixed(2)}% for Woodcutting level\n`); + boosts.push(`**Boosts:** ${wcBoost.toFixed(2)}% for Woodcutting level`); + } + + if (user.hasEquippedOrInBank('Dwarven greataxe')) { + durationPerTodt /= 2; + boosts.push('2x faster for Dwarven greataxe.'); } durationPerTodt = reduceNumByPercent(durationPerTodt, wcBoost); @@ -45,24 +51,19 @@ export async function wintertodtCommand(user: MUser, channelID: string) { healAmountNeeded -= warmGearAmount * 15; durationPerTodt = reduceNumByPercent(durationPerTodt, 5 * warmGearAmount); - const boosts: string[] = []; - - if (user.hasEquippedOrInBank('Dwarven greataxe')) { - durationPerTodt /= 2; - boosts.push('2x faster for Dwarven greataxe.'); - } + const maxTripLength = calcMaxTripLength(user, 'Wintertodt'); + if (!quantity) quantity = Math.floor(maxTripLength / durationPerTodt); + quantity = Math.max(1, quantity); + const duration = durationPerTodt * quantity; - if (healAmountNeeded !== baseHealAmountNeeded) { - messages.push( - `${calcWhatPercent( - baseHealAmountNeeded - healAmountNeeded, - baseHealAmountNeeded - )}% less food for wearing warm gear` - ); + if (quantity > 1 && duration > maxTripLength) { + return `${user.minionName} can't go on PvM trips longer than ${formatDuration( + maxTripLength + )}, try a lower quantity. The highest amount you can do for Wintertodt is ${Math.floor( + maxTripLength / durationPerTodt + )}.`; } - const quantity = Math.floor(calcMaxTripLength(user, 'Wintertodt') / durationPerTodt); - for (const food of Eatables) { const healAmount = typeof food.healAmount === 'number' ? food.healAmount : food.healAmount(user); const amountNeeded = Math.ceil(healAmountNeeded / healAmount) * quantity; @@ -75,23 +76,21 @@ export async function wintertodtCommand(user: MUser, channelID: string) { continue; } - let foodStr: string = `**Food:** ${healAmountNeeded} HP/kill`; + foodStr.push(`**Food:** ${healAmountNeeded} HP/kill`); if (healAmountNeeded !== baseHealAmountNeeded) { - foodStr += `. Reduced from ${baseHealAmountNeeded}, -${calcWhatPercent( - baseHealAmountNeeded - healAmountNeeded, - baseHealAmountNeeded - )}% for wearing warm gear. `; - } else { - foodStr += '. '; + foodStr.push( + `Reduced from ${baseHealAmountNeeded}, -${calcWhatPercent( + baseHealAmountNeeded - healAmountNeeded, + baseHealAmountNeeded + )}% for wearing warm gear` + ); } - foodStr += ` **Removed ${amountNeeded}x ${food.name}**`; - - messages.push(foodStr); - const cost = new Bank().add(food.id, amountNeeded); + foodStr.push(`**Removed ${cost}**`); + await user.removeItemsFromBank(cost); // Track this food cost in Economy Stats @@ -114,8 +113,6 @@ export async function wintertodtCommand(user: MUser, channelID: string) { break; } - const duration = durationPerTodt * quantity; - await addSubTaskToActivityTask({ minigameID: 'wintertodt', userID: user.id, @@ -125,15 +122,13 @@ export async function wintertodtCommand(user: MUser, channelID: string) { type: 'Wintertodt' }); - let str = `${ + const str = `${ user.minionName } is now off to kill Wintertodt ${quantity}x times, their trip will take ${formatDuration( durationPerTodt * quantity - )}. (${formatDuration(durationPerTodt)} per todt)\n\n${messages.join(', ')}.`; - - if (boosts.length > 0) { - str += `**Boosts:** ${boosts.join(', ')}`; - } + )}. (${formatDuration(durationPerTodt)} per todt)\n\n${boosts.length > 0 ? `${boosts.join(', ')}\n` : ''}${ + foodStr.length > 0 ? foodStr.join(', ') : '' + }.`; return str; } diff --git a/src/mahoji/lib/abstracted_commands/zalcanoCommand.ts b/src/mahoji/lib/abstracted_commands/zalcanoCommand.ts index 57fd09530b5..7e984eb3ee4 100644 --- a/src/mahoji/lib/abstracted_commands/zalcanoCommand.ts +++ b/src/mahoji/lib/abstracted_commands/zalcanoCommand.ts @@ -1,9 +1,9 @@ -import { calcWhatPercent, percentChance, reduceNumByPercent, Time } from 'e'; +import { Time, calcWhatPercent, percentChance, reduceNumByPercent } from 'e'; import { ZALCANO_ID } from '../../../lib/constants'; import removeFoodFromUser from '../../../lib/minions/functions/removeFoodFromUser'; import { soteSkillRequirements } from '../../../lib/skilling/functions/questRequirements'; -import { ZalcanoActivityTaskOptions } from '../../../lib/types/minions'; +import type { ZalcanoActivityTaskOptions } from '../../../lib/types/minions'; import { formatDuration, hasSkillReqs } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; @@ -21,7 +21,7 @@ function calcPerformance(kcLearned: number, skillPercentage: number) { return Math.min(100, basePerformance); } -export async function zalcanoCommand(user: MUser, channelID: string) { +export async function zalcanoCommand(user: MUser, channelID: string, quantity?: number) { const [hasReqs, reason] = hasSkillReqs(user, soteSkillRequirements); if (!hasReqs) { return `To fight Zalcano, you need: ${reason}.`; @@ -59,9 +59,19 @@ export async function zalcanoCommand(user: MUser, channelID: string) { else if (kc > 50) healAmountNeeded = 3 * 12; else if (kc > 20) healAmountNeeded = 5 * 12; - const quantity = Math.floor(calcMaxTripLength(user, 'Zalcano') / baseTime); + const maxTripLength = calcMaxTripLength(user, 'Zalcano'); + if (!quantity) quantity = Math.floor(maxTripLength / baseTime); + quantity = Math.max(1, quantity); const duration = quantity * baseTime; + if (quantity > 1 && duration > maxTripLength) { + return `${user.minionName} can't go on PvM trips longer than ${formatDuration( + maxTripLength + )}, try a lower quantity. The highest amount you can do for Zalcano is ${Math.floor( + maxTripLength / baseTime + )}.`; + } + const { foodRemoved } = await removeFoodFromUser({ user, totalHealingNeeded: healAmountNeeded * quantity, diff --git a/src/mahoji/lib/bingo/BingoManager.ts b/src/mahoji/lib/bingo/BingoManager.ts index 98660161d6d..b85d5e1d6f5 100644 --- a/src/mahoji/lib/bingo/BingoManager.ts +++ b/src/mahoji/lib/bingo/BingoManager.ts @@ -1,18 +1,19 @@ -import { type Bingo, Prisma } from '@prisma/client'; +import type { Bingo, Prisma } from '@prisma/client'; import { ButtonBuilder, ButtonStyle, userMention } from 'discord.js'; -import { chunk, noOp, Time } from 'e'; -import { groupBy } from 'lodash'; +import { Time, chunk, noOp } from 'e'; +import groupBy from 'lodash/groupBy'; import { Bank } from 'oldschooljs'; import { toKMB } from 'oldschooljs/dist/util'; import * as ss from 'simple-statistics'; +import { addBanks } from '@oldschoolgg/toolkit'; import { Emoji } from '../../../lib/constants'; -import { prisma } from '../../../lib/settings/prisma'; -import { ItemBank } from '../../../lib/types'; + +import type { ItemBank } from '../../../lib/types'; import getOSItem from '../../../lib/util/getOSItem'; -import { addBanks } from '../../../lib/util/smallUtils'; import { sendToChannelID } from '../../../lib/util/webhook'; -import { generateTileName, isGlobalTile, rowsForSquare, StoredBingoTile, UniversalBingoTile } from './bingoUtil'; +import type { StoredBingoTile, UniversalBingoTile } from './bingoUtil'; +import { generateTileName, isGlobalTile, rowsForSquare } from './bingoUtil'; import { globalBingoTiles } from './globalTiles'; export const BingoTrophies = [ @@ -259,7 +260,7 @@ export class BingoManager { team.tilesCompletedCount >= t.guaranteedAt || 100 - t.percentile <= ss.quantileRank(tilesCompletedCounts, team.tilesCompletedCount) * 100 - )[0] ?? null + )[0] ?? null : null, rank: index + 1 })) diff --git a/src/mahoji/lib/bingo/bingoUtil.ts b/src/mahoji/lib/bingo/bingoUtil.ts index 1ebdf21db24..f202802c768 100644 --- a/src/mahoji/lib/bingo/bingoUtil.ts +++ b/src/mahoji/lib/bingo/bingoUtil.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { ItemBank } from '../../../lib/types'; +import type { ItemBank } from '../../../lib/types'; import { getItem } from '../../../lib/util/getOSItem'; import { globalBingoTiles } from './globalTiles'; @@ -25,7 +25,7 @@ export type UniversalBingoTile = { name: string; } & (OneOf | AllOf | BankTile | CustomReq); -export interface StoredGlobalTile { +interface StoredGlobalTile { global: number; } diff --git a/src/mahoji/lib/bingo/globalTiles.ts b/src/mahoji/lib/bingo/globalTiles.ts index b8d28ce9b84..6d6fbee1eae 100644 --- a/src/mahoji/lib/bingo/globalTiles.ts +++ b/src/mahoji/lib/bingo/globalTiles.ts @@ -1,11 +1,10 @@ import { uniqueArr } from 'e'; import { Bank } from 'oldschooljs'; - import { championScrolls, skillingPetsCL } from '../../../lib/data/CollectionsExport'; import { TanglerootTable } from '../../../lib/minions/data/killableMonsters/custom/Treebeard'; import { assert } from '../../../lib/util'; import resolveItems from '../../../lib/util/resolveItems'; -import { GlobalBingoTile } from './bingoUtil'; +import type { GlobalBingoTile } from './bingoUtil'; const otherSpiritShieldParts = resolveItems(['Blessed spirit shield', 'Holy elixir', 'Spirit shield']); const allSpiritShieldSets = [ diff --git a/src/mahoji/lib/buttonUserPicker.ts b/src/mahoji/lib/buttonUserPicker.ts index aa6d7632978..60a573a87f1 100644 --- a/src/mahoji/lib/buttonUserPicker.ts +++ b/src/mahoji/lib/buttonUserPicker.ts @@ -1,5 +1,5 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ComponentType } from 'discord.js'; -import { noOp, shuffleArr, Time } from 'e'; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, type ComponentType } from 'discord.js'; +import { Time, noOp, shuffleArr } from 'e'; import murmurhash from 'murmurhash'; import { channelIsSendable } from '../../lib/util'; @@ -47,7 +47,7 @@ export async function buttonUserPicker({ const { id } = i.user; const mUser = await mahojiUsersSettingsFetch(id, { minion_ironman: true }); const isCreator = id === creator; - let notAllowed = !ironmenAllowed && mUser.minion_ironman; + const notAllowed = !ironmenAllowed && mUser.minion_ironman; if (notAllowed && !isCreator) { i.reply({ ephemeral: true, content: "You aren't allowed to participate.." }); return; diff --git a/src/mahoji/lib/collectables.ts b/src/mahoji/lib/collectables.ts new file mode 100644 index 00000000000..27a78ffaeff --- /dev/null +++ b/src/mahoji/lib/collectables.ts @@ -0,0 +1,173 @@ +import { Time } from 'e'; +import { Bank } from 'oldschooljs'; +import type { Item } from 'oldschooljs/dist/meta/types'; +import type { Skills } from '../../lib/types'; +import getOSItem from '../../lib/util/getOSItem'; + +interface Collectable { + item: Item; + skillReqs?: Skills; + itemCost?: Bank; + quantity: number; + duration: number; + qpRequired?: number; + onlyTamesCan?: boolean; +} + +export const collectables: Collectable[] = [ + { + item: getOSItem('Blue dragon scale'), + quantity: 26, + itemCost: new Bank({ + 'Water rune': 1, + 'Air rune': 3, + 'Law rune': 1 + }), + skillReqs: { + agility: 70, + magic: 37 + }, + duration: Time.Minute * 2 + }, + { + item: getOSItem('Mort myre fungus'), + quantity: 100, + itemCost: new Bank({ + 'Prayer potion(4)': 1, + 'Ring of dueling(8)': 1 + }), + skillReqs: { + prayer: 50 + }, + duration: Time.Minute * 8.3, + qpRequired: 32 + }, + { + item: getOSItem('Flax'), + quantity: 28, + duration: Time.Minute * 1.68 + }, + { + item: getOSItem('Swamp toad'), + quantity: 28, + duration: Time.Minute * 1.68 + }, + { + item: getOSItem("Red spiders' eggs"), + quantity: 80, + itemCost: new Bank({ + 'Stamina potion(4)': 1 + }), + duration: Time.Minute * 8.5 + }, + { + item: getOSItem('Wine of zamorak'), + quantity: 27, + itemCost: new Bank({ + 'Law rune': 27, + 'Air rune': 27 + }), + skillReqs: { + magic: 33 + }, + duration: Time.Minute * 3.12 + }, + { + item: getOSItem('White berries'), + quantity: 27, + qpRequired: 22, + skillReqs: { + ranged: 60, + thieving: 50, + agility: 56, + crafting: 10, + fletching: 5, + cooking: 30 + }, + duration: Time.Minute * 4.05 + }, + { + item: getOSItem('Snape grass'), + quantity: 120, + itemCost: new Bank({ + 'Law rune': 12, + 'Astral rune': 12 + }), + duration: Time.Minute * 6.5, + qpRequired: 72 + }, + { + item: getOSItem('Snake weed'), + quantity: 150, + itemCost: new Bank({ + 'Ring of dueling(8)': 1 + }), + duration: Time.Minute * 30, + qpRequired: 3 + }, + { + item: getOSItem('Bucket of sand'), + quantity: 30, + itemCost: new Bank({ + 'Law rune': 1, + Coins: 30 * 25 + }), + duration: Time.Minute, + qpRequired: 30 + }, + { + item: getOSItem('Jangerberries'), + quantity: 224, + itemCost: new Bank({ + 'Ring of dueling(8)': 1 + }), + skillReqs: { + agility: 10 + }, + duration: Time.Minute * 24 + }, + // Miniquest to get Tarn's diary for Salve amulet (e)/(ei) + { + item: getOSItem("Tarn's diary"), + quantity: 1, + itemCost: new Bank({ + 'Prayer potion(4)': 2 + }), + skillReqs: { + slayer: 40, + attack: 60, + strength: 60, + ranged: 60, + defence: 60, + magic: 60 + }, + duration: 10 * Time.Minute, + qpRequired: 100 + }, + { + item: getOSItem('Neem drupe'), + quantity: 5, + itemCost: new Bank({ + 'Astral rune': 26, + 'Cosmic rune': 12 + }), + skillReqs: { + magic: 82, + herblore: 82, + agility: 92 + }, + duration: 5 * Time.Minute, + qpRequired: 82 + }, + { + item: getOSItem('Orange'), + quantity: 1, + duration: 2 * Time.Minute, + onlyTamesCan: true + }, + { + item: getOSItem('Cabbage'), + quantity: 28, + duration: 1.2 * Time.Minute + } +]; diff --git a/src/mahoji/lib/events.ts b/src/mahoji/lib/events.ts index 9b5bdf0ff36..7c1063e4d49 100644 --- a/src/mahoji/lib/events.ts +++ b/src/mahoji/lib/events.ts @@ -1,24 +1,72 @@ -import { bulkUpdateCommands } from 'mahoji/dist/lib/util'; -import { ItemBank } from 'oldschooljs/dist/meta/types'; +import type { ItemBank } from 'oldschooljs/dist/meta/types'; -import { DEV_SERVER_ID, production } from '../../config'; -import { cacheBadges } from '../../lib/badges'; -import { syncBlacklists } from '../../lib/blacklists'; -import { Channel, DISABLED_COMMANDS, globalConfig, META_CONSTANTS } from '../../lib/constants'; +import { bulkUpdateCommands } from '@oldschoolgg/toolkit'; +import { ActivityType, bold, time } from 'discord.js'; +import { Channel, META_CONSTANTS, globalConfig } from '../../lib/constants'; import { initCrons } from '../../lib/crons'; import { syncDoubleLoot } from '../../lib/doubleLoot'; -import { GrandExchange } from '../../lib/grandExchange'; -import { prisma } from '../../lib/settings/prisma'; + import { initTickers } from '../../lib/tickers'; -import { runTimedLoggedFn } from '../../lib/util'; -import { cacheCleanup } from '../../lib/util/cachedUserIDs'; +import { logWrapFn } from '../../lib/util'; import { mahojiClientSettingsFetch } from '../../lib/util/clientSettings'; -import { syncLinkedAccounts } from '../../lib/util/linkedAccountsUtil'; import { syncSlayerMaskLeaderboardCache } from '../../lib/util/slayerMaskLeaderboard'; import { sendToChannelID } from '../../lib/util/webhook'; -import { cacheUsernames } from '../commands/leaderboard'; import { CUSTOM_PRICE_CACHE } from '../commands/sell'; +export async function updateTestBotStatus(online = true) { + try { + if (globalConfig.isProduction) return; + const idMap: Record = { + '829398443821891634': '1265571664142270464', + '577488230539067403': '1265582554644217977', + '353484579840983042': '1265582554644217977', + '897549995446779964': '1265582743970910259', + '1158785741028081696': '1265583194108067925' + }; + const catChannelID = idMap[globalConfig.clientID]; + if (!catChannelID) return; + const cat = await globalClient.channels.fetch(catChannelID); + if (!cat || !cat.isTextBased() || cat.isDMBased()) { + console.log('Could not find status channel'); + return; + } + + const emoji = online ? '🟢' : '🔴'; + let text = ''; + if (online) { + text = `${emoji} ${globalClient.user.username} is ONLINE ${emoji} + +Turned on ${time(new Date(), 'R')}`; + text = bold(text); + } else { + text = `${emoji} ${globalClient.user.username} is offline ${emoji} + +Turned off ${time(new Date(), 'R')}`; + } + const message = await cat.messages + .fetch({ limit: 5 }) + .then(messages => messages.filter(m => m.author.id === globalClient.user!.id)) + .then(msg => msg.first()); + if (!message) { + await cat.send(text); + } else { + await message.edit(text); + } + if (online) { + await globalClient.user.setPresence({ + status: 'online', + activities: [ + { + name: `${emoji} ONLINE`, + type: ActivityType.Custom + } + ] + }); + } + } catch (err) { + console.error(err); + } +} export async function syncCustomPrices() { const clientData = await mahojiClientSettingsFetch({ custom_prices: true }); for (const [key, value] of Object.entries(clientData.custom_prices as ItemBank)) { @@ -26,55 +74,33 @@ export async function syncCustomPrices() { } } -export async function onStartup() { - globalClient.application.commands.fetch({ guildId: production ? undefined : DEV_SERVER_ID }); - - // Sync disabled commands - const disabledCommands = await prisma.clientStorage.upsert({ - where: { - id: globalConfig.clientID - }, - select: { disabled_commands: true }, - create: { - id: globalConfig.clientID - }, - update: {} - }); - - for (const command of disabledCommands!.disabled_commands) { - DISABLED_COMMANDS.add(command); - } - - // Sync blacklists - await syncBlacklists(); - - if (!production) { - console.log('Syncing commands locally...'); - await bulkUpdateCommands({ - client: globalClient.mahojiClient, - commands: globalClient.mahojiClient.commands.values, - guildID: DEV_SERVER_ID - }); - } +export const onStartup = logWrapFn('onStartup', async () => { + const syncTestBotCommands = globalConfig.isProduction + ? null + : bulkUpdateCommands({ + client: globalClient.mahojiClient, + commands: Array.from(globalClient.mahojiClient.commands.values()), + guildID: globalConfig.testingServerID + }); await syncDoubleLoot(); - runTimedLoggedFn('Syncing prices', syncCustomPrices); - - runTimedLoggedFn('Caching badges', cacheBadges); - runTimedLoggedFn('Cache Usernames', cacheUsernames); - cacheCleanup(); - - runTimedLoggedFn('Sync Linked Accounts', syncLinkedAccounts); - runTimedLoggedFn('Init Grand Exchange', GrandExchange.init.bind(GrandExchange)); initCrons(); initTickers(); - syncSlayerMaskLeaderboardCache(); - sendToChannelID(Channel.GeneralChannel, { - content: `I have just turned on! + const sendStartupMessage = globalConfig.isProduction + ? sendToChannelID(Channel.GeneralChannel, { + content: `I have just turned on!\n\n${META_CONSTANTS.RENDERED_STR}` + }).catch(console.error) + : null; -${META_CONSTANTS.RENDERED_STR}` - }).catch(console.error); -} + await Promise.all([ + globalClient.application.commands.fetch({ + guildId: globalConfig.isProduction ? undefined : globalConfig.testingServerID + }), + updateTestBotStatus(), + sendStartupMessage, + syncTestBotCommands + ]); +}); diff --git a/src/mahoji/lib/exitHandler.ts b/src/mahoji/lib/exitHandler.ts new file mode 100644 index 00000000000..14324c629bc --- /dev/null +++ b/src/mahoji/lib/exitHandler.ts @@ -0,0 +1,21 @@ +import { TimerManager } from '@sapphire/timer-manager'; + +import { updateTestBotStatus } from './events'; + +export async function exitCleanup() { + try { + globalClient.isShuttingDown = true; + console.log('Cleaning up and exiting...'); + TimerManager.destroy(); + await updateTestBotStatus(false); + await Promise.all([ + globalClient.destroy(), + prisma.$disconnect(), + redis.disconnect(), + roboChimpClient.$disconnect() + ]); + console.log('\nCleaned up and exited.'); + } catch (err) { + console.error(err); + } +} diff --git a/src/mahoji/lib/inhibitors.ts b/src/mahoji/lib/inhibitors.ts index 118bc2a904b..3b8dc41c182 100644 --- a/src/mahoji/lib/inhibitors.ts +++ b/src/mahoji/lib/inhibitors.ts @@ -1,14 +1,6 @@ -import { - ComponentType, - DMChannel, - Guild, - GuildMember, - InteractionReplyOptions, - PermissionsBitField, - TextChannel, - User -} from 'discord.js'; -import { roll } from 'e'; +import { PerkTier } from '@oldschoolgg/toolkit'; +import type { DMChannel, Guild, GuildMember, InteractionReplyOptions, TextChannel } from 'discord.js'; +import { ComponentType, PermissionsBitField } from 'discord.js'; import { OWNER_IDS, SupportServer } from '../../config'; import { BLACKLISTED_GUILDS, BLACKLISTED_USERS } from '../../lib/blacklists'; @@ -18,12 +10,10 @@ import { Channel, DISABLED_COMMANDS, gearValidationChecks, - minionBuyButton, - PerkTier + minionBuyButton } from '../../lib/constants'; -import { perkTierCache, syncPerkTierOfUser } from '../../lib/perkTiers'; -import { CategoryFlag } from '../../lib/types'; -import { formatDuration } from '../../lib/util'; +import type { CategoryFlag } from '../../lib/types'; +import { formatDuration, roll } from '../../lib/util'; import { minionIsBusy } from '../../lib/util/minionIsBusy'; import { mahojiGuildSettingsFetch, untrustedGuildSettingsCache } from '../guildSettings'; import { Cooldowns } from './Cooldowns'; @@ -47,7 +37,6 @@ export interface AbstractCommand { interface Inhibitor { name: string; run: (options: { - APIUser: User; user: MUser; command: AbstractCommand; guild: Guild | null; @@ -70,23 +59,6 @@ const inhibitors: Inhibitor[] = [ }, canBeDisabled: false }, - { - name: 'bots', - run: async ({ APIUser, user }) => { - if (!APIUser.bot) return false; - if ( - ![ - '798308589373489172', // BIRDIE#1963 - '902745429685469264' // Randy#0008 - ].includes(user.id) - ) { - return { content: 'Bots cannot use commands.' }; - } - return false; - }, - canBeDisabled: false, - silent: true - }, { name: 'hasMinion', run: async ({ user, command }) => { @@ -138,9 +110,9 @@ const inhibitors: Inhibitor[] = [ }, { name: 'disabled', - run: async ({ command, guild, APIUser }) => { + run: async ({ command, guild, user }) => { if ( - !OWNER_IDS.includes(APIUser.id) && + !OWNER_IDS.includes(user.id) && (command.attributes?.enabled === false || DISABLED_COMMANDS.has(command.name)) ) { return { content: 'This command is globally disabled.' }; @@ -159,11 +131,7 @@ const inhibitors: Inhibitor[] = [ run: async ({ member, guild, channel, user }) => { if (!guild || guild.id !== SupportServer) return false; if (channel.id !== Channel.General) return false; - - let perkTier = perkTierCache.get(user.id); - if (!perkTier) { - perkTier = syncPerkTierOfUser(user); - } + const perkTier = user.perkTier(); if (member && perkTier >= PerkTier.Two) { return false; } @@ -184,8 +152,7 @@ const inhibitors: Inhibitor[] = [ // Allow contributors + moderators to use disabled channels in SupportServer const userBitfield = user.bitfield; - const isStaff = - userBitfield.includes(BitField.isModerator) || userBitfield.includes(BitField.isContributor); + const isStaff = userBitfield.includes(BitField.isModerator); if (guild.id === SupportServer && isStaff) { return false; } @@ -261,11 +228,9 @@ export async function runInhibitors({ member, command, guild, - bypassInhibitors, - APIUser + bypassInhibitors }: { user: MUser; - APIUser: User; channel: TextChannel | DMChannel; member: GuildMember | null; command: AbstractCommand; @@ -286,7 +251,7 @@ export async function runInhibitors({ for (const { run, canBeDisabled, silent } of inhibitors) { if (bypassInhibitors && canBeDisabled) continue; - const result = await run({ user, channel, member, command, guild, APIUser }); + const result = await run({ user, channel, member, command, guild }); if (result !== false) { return { reason: result, silent: Boolean(silent) }; } diff --git a/src/mahoji/lib/mahojiCommandOptions.ts b/src/mahoji/lib/mahojiCommandOptions.ts index ce89bc65bdd..382040c6823 100644 --- a/src/mahoji/lib/mahojiCommandOptions.ts +++ b/src/mahoji/lib/mahojiCommandOptions.ts @@ -1,17 +1,15 @@ -import { toTitleCase } from '@oldschoolgg/toolkit'; -import { APIApplicationCommandOptionChoice, ApplicationCommandOptionType, User } from 'discord.js'; +import { type CommandOption, toTitleCase } from '@oldschoolgg/toolkit'; import { uniqueArr } from 'e'; -import { CommandOption } from 'mahoji/dist/lib/types'; import { Bank, Items } from 'oldschooljs'; -import { Item, ItemBank } from 'oldschooljs/dist/meta/types'; +import type { Item, ItemBank } from 'oldschooljs/dist/meta/types'; -import { secretItems } from '../../lib/constants'; +import { type APIApplicationCommandOptionChoice, ApplicationCommandOptionType, type User } from 'discord.js'; import { baseFilters, filterableTypes } from '../../lib/data/filterables'; import { GearSetupTypes } from '../../lib/gear/types'; -import { IMaterialBank, materialTypes } from '../../lib/invention'; +import { type IMaterialBank, materialTypes } from '../../lib/invention'; import { MaterialBank } from '../../lib/invention/MaterialBank'; import { effectiveMonsters } from '../../lib/minions/data/killableMonsters'; -import { prisma } from '../../lib/settings/prisma'; + import { SkillsEnum } from '../../lib/skilling/types'; import { globalPresets } from '../../lib/structures/Gear'; import getOSItem from '../../lib/util/getOSItem'; @@ -23,7 +21,7 @@ export const filterOption: CommandOption = { description: 'The filter you want to use.', required: false, autocomplete: async (value: string) => { - let res = !value + const res = !value ? filterableTypes : [...filterableTypes].filter(filter => filter.name.toLowerCase().includes(value.toLowerCase())); return [...res] @@ -44,18 +42,7 @@ export const itemOption = (filter?: (item: Item) => boolean): CommandOption => ( description: 'The item you want to pick.', required: false, autocomplete: async value => { - let res = itemArr.filter(i => i.key.includes(value.toLowerCase())).filter(i => !secretItems.includes(i.id)); - if (filter) res = res.filter(filter); - return res.map(i => ({ name: `${i.name}`, value: i.id.toString() })); - } -}); -export const equipableItemOption = (filter?: (item: Item) => boolean): CommandOption => ({ - type: ApplicationCommandOptionType.String, - name: 'item', - description: 'The item you want to pick.', - required: false, - autocomplete: async value => { - let res = allEquippableItems.filter(i => i.name.includes(value.toLowerCase())); + let res = itemArr.filter(i => i.key.includes(value.toLowerCase())).filter(i => !i.customItemData?.isSecret); if (filter) res = res.filter(filter); return res.map(i => ({ name: `${i.name}`, value: i.id.toString() })); } @@ -101,7 +88,7 @@ export const equippedItemOption = (): CommandOption => ({ autocomplete: async (value, user) => { const mUser = await mUserFetch(user.id); - let results: APIApplicationCommandOptionChoice[] = []; + const results: APIApplicationCommandOptionChoice[] = []; const entries: [string, Item[]][] = Object.entries(mUser.gear).map(entry => [ entry[0], entry[1].allItems(false).map(getOSItem) @@ -175,10 +162,3 @@ export const ownedMaterialOption = { })); } } as const; -export function generateRandomBank(size: number) { - const bank = new Bank(); - for (let i = 0; i < size; i++) { - bank.add(allEquippableItems[i]); - } - return bank; -} diff --git a/src/mahoji/lib/postCommand.ts b/src/mahoji/lib/postCommand.ts index e035601aaf0..eceb1598555 100644 --- a/src/mahoji/lib/postCommand.ts +++ b/src/mahoji/lib/postCommand.ts @@ -1,11 +1,12 @@ -import { CommandOptions } from 'mahoji/dist/lib/types'; +import type { CommandOptions } from '@oldschoolgg/toolkit'; import { modifyBusyCounter } from '../../lib/busyCounterCache'; import { busyImmuneCommands, shouldTrackCommand } from '../../lib/constants'; -import { prisma } from '../../lib/settings/prisma'; + +import { TimerManager } from '@sapphire/timer-manager'; import { makeCommandUsage } from '../../lib/util/commandUsage'; import { logError } from '../../lib/util/logError'; -import { AbstractCommand } from './inhibitors'; +import type { AbstractCommand } from './inhibitors'; export async function postCommand({ abstractCommand, @@ -28,15 +29,8 @@ export async function postCommand({ continueDeltaMillis: number | null; }): Promise { if (!busyImmuneCommands.includes(abstractCommand.name)) { - setTimeout(() => modifyBusyCounter(userID, -1), 1000); + TimerManager.setTimeout(() => modifyBusyCounter(userID, -1), 1000); } - debugLog('Postcommand', { - type: 'RUN_COMMAND', - command_name: abstractCommand.name, - user_id: userID, - guild_id: guildID, - channel_id: channelID - }); if (shouldTrackCommand(abstractCommand, args)) { const commandUsage = makeCommandUsage({ userID, @@ -45,14 +39,16 @@ export async function postCommand({ commandName: abstractCommand.name, args, isContinue, - flags: null, inhibited, continueDeltaMillis }); try { await prisma.$transaction([ prisma.commandUsage.create({ - data: commandUsage + data: commandUsage, + select: { + id: true + } }), prisma.user.update({ where: { @@ -60,6 +56,9 @@ export async function postCommand({ }, data: { last_command_date: new Date() + }, + select: { + id: true } }) ]); diff --git a/src/mahoji/lib/preCommand.ts b/src/mahoji/lib/preCommand.ts index 766c44151c0..1232d995fb7 100644 --- a/src/mahoji/lib/preCommand.ts +++ b/src/mahoji/lib/preCommand.ts @@ -1,53 +1,14 @@ -import { InteractionReplyOptions, TextChannel, User } from 'discord.js'; -import { CommandOptions } from 'mahoji/dist/lib/types'; +import { type CommandOptions, cleanUsername } from '@oldschoolgg/toolkit'; +import type { InteractionReplyOptions, TextChannel, User } from 'discord.js'; import { modifyBusyCounter, userIsBusy } from '../../lib/busyCounterCache'; -import { badges, badgesCache, busyImmuneCommands, Emoji, usernameCache } from '../../lib/constants'; -import { prisma } from '../../lib/settings/prisma'; -import { removeMarkdownEmojis, stripEmojis } from '../../lib/util'; -import { CACHED_ACTIVE_USER_IDS } from '../../lib/util/cachedUserIDs'; -import { AbstractCommand, runInhibitors } from './inhibitors'; +import { busyImmuneCommands } from '../../lib/constants'; -function cleanUsername(username: string) { - return removeMarkdownEmojis(username).substring(0, 32); -} -export async function syncNewUserUsername(user: MUser, username: string) { - const newUsername = cleanUsername(username); - const newUser = await prisma.newUser.findUnique({ - where: { id: user.id } - }); - if (!newUser || newUser.username !== newUsername) { - await prisma.newUser.upsert({ - where: { - id: user.id - }, - update: { - username - }, - create: { - id: user.id, - username - } - }); - } - let name = stripEmojis(username); - usernameCache.set(user.id, name); - const rawBadges = user.user.badges.map(num => badges[num]); - if (user.isIronman) { - rawBadges.push(Emoji.Ironman); - } - badgesCache.set(user.id, rawBadges.join(' ')); -} +import { logWrapFn } from '../../lib/util'; +import type { AbstractCommand } from './inhibitors'; +import { runInhibitors } from './inhibitors'; -export async function preCommand({ - abstractCommand, - userID, - guildID, - channelID, - bypassInhibitors, - apiUser, - options -}: { +interface PreCommandOptions { apiUser: User | null; abstractCommand: AbstractCommand; userID: string; @@ -55,46 +16,48 @@ export async function preCommand({ channelID: string | bigint; bypassInhibitors: boolean; options: CommandOptions; -}): Promise< +} + +type PrecommandReturn = Promise< | undefined | { reason: InteractionReplyOptions; - silent: boolean; dontRunPostCommand?: boolean; } -> { - debugLog('Attempt to run command', { - type: 'RUN_COMMAND', - command_name: abstractCommand.name, - user_id: userID, - guild_id: guildID, - channel_id: channelID, - options - }); - CACHED_ACTIVE_USER_IDS.add(userID); +>; +export const preCommand: (opts: PreCommandOptions) => PrecommandReturn = logWrapFn('PreCommand', rawPreCommand); +async function rawPreCommand({ + abstractCommand, + userID, + guildID, + channelID, + bypassInhibitors, + apiUser +}: PreCommandOptions): PrecommandReturn { if (globalClient.isShuttingDown) { return { - silent: true, reason: { content: 'The bot is currently restarting, please try again later.' }, dontRunPostCommand: true }; } - const user = await mUserFetch(userID); - user.checkBankBackground(); + + const username = apiUser?.username ? cleanUsername(apiUser?.username) : undefined; + const user = await mUserFetch(userID, { + username + }); + + // TODO: user.checkBankBackground(); if (userIsBusy(userID) && !bypassInhibitors && !busyImmuneCommands.includes(abstractCommand.name)) { - return { silent: true, reason: { content: 'You cannot use a command right now.' }, dontRunPostCommand: true }; + return { reason: { content: 'You cannot use a command right now.' }, dontRunPostCommand: true }; } if (!busyImmuneCommands.includes(abstractCommand.name)) modifyBusyCounter(userID, 1); const guild = guildID ? globalClient.guilds.cache.get(guildID.toString()) : null; const member = guild?.members.cache.get(userID.toString()); const channel = globalClient.channels.cache.get(channelID.toString()) as TextChannel; - if (apiUser) { - await syncNewUserUsername(user, apiUser.username); - } + const inhibitResult = await runInhibitors({ user, - APIUser: await globalClient.fetchUser(user.id), guild: guild ?? null, member: member ?? null, command: abstractCommand, diff --git a/src/mahoji/lib/util.ts b/src/mahoji/lib/util.ts index 32f5d61c1e0..95dc8f2e737 100644 --- a/src/mahoji/lib/util.ts +++ b/src/mahoji/lib/util.ts @@ -1,8 +1,7 @@ -import { Prisma } from '@prisma/client'; +import type { Prisma } from '@prisma/client'; import { isObject } from 'e'; -import { ICommand, MahojiClient } from 'mahoji'; -import { CommandOptions, MahojiUserOption } from 'mahoji/dist/lib/types'; +import type { CommandOptions, ICommand, MahojiClient, MahojiUserOption } from '@oldschoolgg/toolkit'; import { Cooldowns } from './Cooldowns'; import type { AbstractCommand, AbstractCommandAttributes } from './inhibitors'; @@ -10,7 +9,7 @@ export interface OSBMahojiCommand extends ICommand { attributes?: Omit; } -export function isMahojiUserOption(data: any): data is MahojiUserOption { +function isMahojiUserOption(data: any): data is MahojiUserOption { return 'user' in data && 'id' in data.user; } @@ -25,7 +24,7 @@ interface CompressedArg { [key: string]: string | number | boolean | null | undefined | CompressedArg; } function compressMahojiArgs(options: CommandOptions) { - let newOptions: CompressedArg = {}; + const newOptions: CompressedArg = {}; for (const [key, val] of Object.entries(options) as [ keyof CommandOptions, CommandOptions[keyof CommandOptions] @@ -71,7 +70,7 @@ export function getCommandArgs( } export function allAbstractCommands(mahojiClient: MahojiClient): AbstractCommand[] { - return mahojiClient.commands.values.map(convertMahojiCommandToAbstractCommand); + return Array.from(mahojiClient.commands.values()).map(convertMahojiCommandToAbstractCommand); } export function resetCooldown(user: MUser, key?: string) { diff --git a/src/mahoji/mahojiSettings.ts b/src/mahoji/mahojiSettings.ts index a9f1f44ec79..cdae187d7ab 100644 --- a/src/mahoji/mahojiSettings.ts +++ b/src/mahoji/mahojiSettings.ts @@ -1,18 +1,25 @@ -import { evalMathExpression } from '@oldschoolgg/toolkit/dist/util/expressionParser'; +import { evalMathExpression } from '@oldschoolgg/toolkit'; import type { Prisma, User, UserStats } from '@prisma/client'; -import { isFunction, objectEntries, round } from 'e'; +import { isObject, objectEntries, round } from 'e'; import { Bank } from 'oldschooljs'; +import type { SelectedUserStats } from '../lib/MUser'; import { globalConfig } from '../lib/constants'; import { getSimilarItems } from '../lib/data/similarItems'; import { GearStat } from '../lib/gear'; import type { KillableMonster } from '../lib/minions/types'; -import type { SelectedUserStats } from '../lib/MUser'; -import { prisma } from '../lib/settings/prisma'; -import { Rune } from '../lib/skilling/skills/runecraft'; + +import type { Rune } from '../lib/skilling/skills/runecraft'; import { hasGracefulEquipped } from '../lib/structures/Gear'; import type { ItemBank } from '../lib/types'; -import { anglerBoosts, formatItemReqs, hasSkillReqs, itemNameFromID, readableStatName } from '../lib/util'; +import { + type JsonKeys, + anglerBoosts, + formatItemReqs, + hasSkillReqs, + itemNameFromID, + readableStatName +} from '../lib/util'; import { mahojiClientSettingsFetch, mahojiClientSettingsUpdate } from '../lib/util/clientSettings'; import resolveItems from '../lib/util/resolveItems'; @@ -34,7 +41,7 @@ export function mahojiParseNumber({ return parsed; } -export type SelectedUser = { +type SelectedUser = { [K in keyof T]: K extends keyof User ? User[K] : never; }; @@ -76,38 +83,35 @@ export async function trackClientBankStats( }); } +export async function fetchUserStats( + userID: string, + selectKeys: T +): Promise> { + const keysToSelect = Object.keys(selectKeys).length === 0 ? { user_id: true } : selectKeys; + const result = await prisma.userStats.upsert({ + where: { + user_id: BigInt(userID) + }, + create: { + user_id: BigInt(userID) + }, + update: {}, + select: keysToSelect + }); + + return result as unknown as SelectedUserStats; +} + export async function userStatsUpdate( userID: string, - data: Omit | ((u: UserStats) => Prisma.UserStatsUpdateInput), + data: Omit, selectKeys?: T ): Promise> { const id = BigInt(userID); - let keys: object | undefined = selectKeys; if (!selectKeys || Object.keys(selectKeys).length === 0) { keys = { user_id: true }; } - - if (isFunction(data)) { - const userStats = await prisma.userStats.upsert({ - create: { - user_id: id - }, - update: {}, - where: { - user_id: id - } - }); - - return (await prisma.userStats.update({ - data: data(userStats), - where: { - user_id: id - }, - select: keys - })) as SelectedUserStats; - } - await prisma.userStats.upsert({ create: { user_id: id @@ -116,7 +120,7 @@ export async function userStatsUpdate; } -export async function userStatsBankUpdate(userID: string, key: keyof UserStats, bank: Bank) { - await userStatsUpdate( - userID, - u => ({ - [key]: bank.clone().add(u[key] as ItemBank).bank - }), - {} - ); -} - -export async function multipleUserStatsBankUpdate(userID: string, updates: Partial>) { +export async function userStatsBankUpdate(user: MUser | string, key: JsonKeys, bank: Bank) { + if (!key) throw new Error('No key provided to userStatsBankUpdate'); + const userID = typeof user === 'string' ? user : user.id; + const stats = + typeof user === 'string' + ? await fetchUserStats(userID, { [key]: true }) + : await user.fetchStats({ [key]: true }); + const currentItemBank = stats[key] as ItemBank; + if (!isObject(currentItemBank)) { + throw new Error(`Key ${key} is not an object.`); + } await userStatsUpdate( userID, - u => { - let updateObj: Prisma.UserStatsUpdateInput = {}; - for (const [key, bank] of objectEntries(updates)) { - updateObj[key] = bank!.clone().add(u[key] as ItemBank).bank; - } - return updateObj; + { + [key]: bank.clone().add(currentItemBank).bank }, - {} + { [key]: true } ); } @@ -298,8 +298,8 @@ export function hasMonsterRequirements(user: MUser, monster: KillableMonster) { false, `You don't have the requirements to kill ${monster.name}! Your ${readableStatName( unmetKey! - )} stat in your ${setup} setup is ${has}, but you need atleast ${ - monster.minimumGearRequirements[setup]![unmetKey!] + )} stat in your ${setup} setup is ${has}, but you need at least ${ + monster.minimumGearRequirements[setup]?.[unmetKey!] }.` ]; } @@ -310,7 +310,7 @@ export function hasMonsterRequirements(user: MUser, monster: KillableMonster) { return [true]; } -export function resolveAvailableItemBoosts(user: MUser, monster: KillableMonster) { +export function resolveAvailableItemBoosts(user: MUser, monster: KillableMonster, _isInWilderness = false) { const boosts = new Bank(); if (monster.itemInBankBoosts) { for (const boostSet of monster.itemInBankBoosts) { @@ -319,7 +319,7 @@ export function resolveAvailableItemBoosts(user: MUser, monster: KillableMonster // find the highest boost that the player has for (const [itemID, boostAmount] of Object.entries(boostSet)) { - const parsedId = parseInt(itemID); + const parsedId = Number.parseInt(itemID); if (!user.hasEquippedOrInBank(parsedId)) { continue; } @@ -374,12 +374,13 @@ export async function addToGPTaxBalance(userID: string | string, amount: number) ]); } -export async function addToOpenablesScores(mahojiUser: MUser, kcBank: Bank) { +export async function addToOpenablesScores(user: MUser, kcBank: Bank) { + const stats = await user.fetchStats({ openable_scores: true }); const { openable_scores: newOpenableScores } = await userStatsUpdate( - mahojiUser.id, - ({ openable_scores }) => ({ - openable_scores: new Bank(openable_scores as ItemBank).add(kcBank).bank - }), + user.id, + { + openable_scores: new Bank(stats.openable_scores as ItemBank).add(kcBank).bank + }, { openable_scores: true } ); return new Bank(newOpenableScores as ItemBank); diff --git a/src/scripts/build.ts b/src/scripts/build.ts new file mode 100644 index 00000000000..5756d7e67d8 --- /dev/null +++ b/src/scripts/build.ts @@ -0,0 +1,142 @@ +import '../lib/safeglobals'; + +import { createHash } from 'node:crypto'; +import { existsSync, readFileSync, writeFileSync } from 'node:fs'; +import path from 'node:path'; +import fg from 'fast-glob'; + +import { production } from '../config.js'; +import { BOT_TYPE } from '../lib/constants'; +import { getSystemInfo } from '../lib/systemInfo.js'; +import { execAsync, runTimedLoggedFn } from './scriptUtil.js'; + +const args = process.argv.slice(2); + +const hasArg = (arg: string) => args.includes(arg); + +const forceRebuild = hasArg('--clean'); + +if (!existsSync('./cache.json')) { + writeFileSync('./cache.json', `${JSON.stringify({}, null, ' ')}\n`); +} +const currentCache = JSON.parse(readFileSync('./cache.json', 'utf-8')); + +function doHash(string: string | Buffer) { + return createHash('sha256').update(string).digest('hex'); +} + +function getFileHash(filePath: string) { + try { + return doHash(readFileSync(filePath)); + } catch { + return null; + } +} + +function getCacheHash(cachePath: string, key: string): string | null { + if (!existsSync(cachePath)) return null; + const cache = JSON.parse(readFileSync(cachePath, 'utf-8')); + return cache[key] || null; +} + +function setCacheValue(key: string, value: string | number) { + if (process.env.TEST) return; + const cache = JSON.parse(readFileSync(cacheFilePath, 'utf-8')); + cache[key] = value; + writeFileSync(cacheFilePath, `${JSON.stringify(cache, null, ' ')}\n`); +} + +function shouldGeneratePrismaClient( + schemaPath: string, + cachePath: string, + cacheKey: string, + clientPath: string +): boolean { + if (!existsSync(clientPath)) return true; + const currentHash = getFileHash(schemaPath); + const cachedHash = getCacheHash(cachePath, cacheKey); + if (currentHash !== cachedHash) { + setCacheValue(cacheKey, currentHash!); + return true; + } + return false; +} + +const cacheFilePath = './cache.json'; + +async function handlePrismaClientGeneration() { + const prismaSchemaPaths = [ + { schema: 'prisma/robochimp.prisma', client: 'node_modules/@prisma/client', key: 'robochimpPrismaSchemaHash' }, + { schema: 'prisma/schema.prisma', client: 'node_modules/@prisma/robochimp', key: `${BOT_TYPE}SchemaHash` } + ]; + + let shouldRunGen = false; + for (const { schema, client, key } of prismaSchemaPaths) { + if (shouldGeneratePrismaClient(schema, cacheFilePath, key, client)) { + shouldRunGen = true; + break; + } + } + + if (shouldRunGen || forceRebuild) { + await Promise.all([ + execAsync('yarn prisma generate --no-hints --schema prisma/robochimp.prisma'), + execAsync('yarn prisma db push') + ]); + } +} + +async function checkForWipingDistFolder() { + const allTypescriptFiles = await fg('**/**/*.ts', { cwd: path.join('src'), onlyFiles: true }); + allTypescriptFiles.sort(); + const hash = doHash(allTypescriptFiles.join('\n')); + if (currentCache.typescriptFilesHash !== hash || forceRebuild) { + console.log(' Removing dist folder'); + await execAsync('yarn wipedist'); + setCacheValue('typescriptFilesHash', hash); + } +} + +async function handleTypescriptCompilation() { + await execAsync('yarn build:tsc'); +} + +async function handleCreatables() { + const allCreatablesFiles = await fg(['./src/lib/data/creatables/*.ts', './src/lib/data/creatables.ts'], { + cwd: path.join('src'), + onlyFiles: true + }); + const hash = doHash(allCreatablesFiles.join('\n')); + if (currentCache.creatablesHash !== hash || forceRebuild) { + console.log(' Rebuilding creatables.txt file'); + const { renderCreatablesFile } = await import('./renderCreatablesFile.js'); + renderCreatablesFile(); + setCacheValue('creatablesHash', hash); + } +} + +async function handleCommandsJSON() { + const cmdFile = `./src/lib/data/${BOT_TYPE.toLowerCase()}.commands.json`; + const currentFileHash = getFileHash(cmdFile); + if (currentFileHash === null || currentCache.commandsHash !== currentFileHash) { + console.log(' Updating commands json file'); + const { commandsFile } = await import('./renderCommandsFile.js'); + await commandsFile(); + setCacheValue('commandsHash', getFileHash(cmdFile)!); + } +} + +async function main() { + if (production || process.env.NODE_ENV === 'production') { + throw new Error("Don't run build script in production!"); + } + console.log((await getSystemInfo()).singleStr); + await runTimedLoggedFn('Prisma Client / Wipe Dist', () => + Promise.all([handlePrismaClientGeneration(), checkForWipingDistFolder()]) + ); + await runTimedLoggedFn('Yarn Installation', () => execAsync('yarn')); + await runTimedLoggedFn('Typescript Compilation', handleTypescriptCompilation); + await runTimedLoggedFn('Post Build', () => Promise.all([handleCreatables(), handleCommandsJSON()])); +} + +runTimedLoggedFn('Build', main); diff --git a/src/scripts/integration-tests.ts b/src/scripts/integration-tests.ts deleted file mode 100644 index b36ed6fec1c..00000000000 --- a/src/scripts/integration-tests.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { execSync } from 'child_process'; -import { sleep } from 'e'; - -async function main() { - try { - execSync('docker compose up -d --wait', { stdio: 'inherit' }); - - console.log('Waiting...'); - await sleep(2000); - - console.log('Getting ready...'); - execSync('dotenv -e .env.test -- prisma db push --schema="./prisma/schema.prisma"', { stdio: 'inherit' }); - execSync('dotenv -e .env.test -- prisma db push --schema="./prisma/robochimp.prisma"', { stdio: 'inherit' }); - - console.log('Building...'); - execSync('yarn prebuild:scripts', { stdio: 'inherit' }); - execSync('yarn build:esbuild', { stdio: 'inherit' }); - - console.log('Starting tests...'); - let runs = 1; - for (let i = 0; i < runs; i++) { - console.log(`Starting run ${i + 1}/${runs}`); - execSync('vitest run --config vitest.integration.config.mts', { - stdio: 'inherit', - encoding: 'utf-8' - }); - console.log(`Finished run ${i + 1}/${runs}`); - } - } catch (err) { - console.error(err); - throw new Error(err as any); - } finally { - console.log('Shutting down containers...'); - execSync('docker-compose down', { stdio: 'inherit' }); - } -} - -main(); diff --git a/src/scripts/no-prisma.ts b/src/scripts/no-prisma.ts deleted file mode 100644 index 3af22f06f26..00000000000 --- a/src/scripts/no-prisma.ts +++ /dev/null @@ -1,2 +0,0 @@ -// @ts-ignore -global.prisma = { $on: () => {} }; diff --git a/src/scripts/remove-item.ts b/src/scripts/remove-item.ts index 8d73fa51647..6432788661c 100644 --- a/src/scripts/remove-item.ts +++ b/src/scripts/remove-item.ts @@ -1,24 +1,12 @@ -import '../lib/data/itemAliases'; -import '../lib/itemMods'; +import '../lib/safeglobals'; -import { writeFileSync } from 'fs'; +import { writeFileSync } from 'node:fs'; import { EquipmentSlot } from 'oldschooljs/dist/meta/types'; import { GearSetupTypes } from '../lib/gear/types'; /* PSQL Function that needs to be created */ const extraFunctions = ` -CREATE OR REPLACE FUNCTION remove_jsonb_keys(data jsonb, keys text[]) -RETURNS jsonb LANGUAGE plpgsql AS $$ -declare - key text; -BEGIN - FOREACH key IN ARRAY keys LOOP - data := data - key; - END LOOP; - RETURN data; -END; -$$; CREATE OR REPLACE FUNCTION array_remove_multiple(original_array anyarray, values_to_remove anyarray) RETURNS anyarray AS $$ BEGIN @@ -41,16 +29,14 @@ FINAL_QUERY += `\n${extraFunctions}\n\n`; FINAL_QUERY += '\nBEGIN;\n'; -FINAL_QUERY += GearSetupTypes.map(gearType => +FINAL_QUERY += GearSetupTypes.flatMap(gearType => Object.values(EquipmentSlot).map( slot => `UPDATE users SET "gear.${gearType}" = jsonb_set("gear.${gearType}"::jsonb, ARRAY['${slot}'], 'null'::jsonb) WHERE "gear.${gearType}"->'${slot}'->>'item' = ANY(${arrayToRemove});` ) -) - .flat() - .join('\n'); +).join('\n'); -const removeFromBankQuery = (column: string) => `"${column}" = remove_jsonb_keys("${column}"::jsonb, ${arrayToRemove})`; +const removeFromBankQuery = (column: string) => `"${column}" = "${column}"::jsonb - ${arrayToRemove}`; const removeFromArrayQuery = (column: string) => `"${column}" = array_remove_multiple("${column}", ${intArrayToRemove})`; diff --git a/src/scripts/renderCommandsFile.ts b/src/scripts/renderCommandsFile.ts new file mode 100644 index 00000000000..39d741d36c8 --- /dev/null +++ b/src/scripts/renderCommandsFile.ts @@ -0,0 +1,52 @@ +import { execSync } from 'node:child_process'; +import { writeFileSync } from 'node:fs'; +import { stringMatches } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; + +import { BOT_TYPE } from '../lib/constants'; +import { allCommands } from '../mahoji/commands/allCommands'; +import type { AbstractCommand } from '../mahoji/lib/inhibitors'; +import { convertMahojiCommandToAbstractCommand } from '../mahoji/lib/util'; + +async function renderCommands() { + return allCommands + .map(c => convertMahojiCommandToAbstractCommand(c)) + .filter(c => { + const has = typeof c.attributes?.description === 'string' && c.attributes.description.length > 1; + if (!has) { + console.log(`Command ${c.name} has no description/attributes.`); + } + return has; + }) + .filter(i => !['admin', 'testpotato'].includes(i.name)) + .map((cmd: AbstractCommand) => { + const mahojiCommand = allCommands.find(i => stringMatches(i.name, cmd.name)); + if (!mahojiCommand) { + throw new Error(`Could not find mahoji command for ${cmd.name}`); + } + const subOptions: string[] = []; + for (const option of mahojiCommand.options) { + if ( + option.type === ApplicationCommandOptionType.SubcommandGroup || + option.type === ApplicationCommandOptionType.Subcommand + ) { + subOptions.push(option.name); + } + } + return { + name: cmd.name, + desc: cmd.attributes?.description, + examples: cmd.attributes?.examples, + flags: cmd.attributes?.categoryFlags, + subOptions + }; + }) + .sort((a, b) => a.name.localeCompare(b.name)); +} + +export async function commandsFile() { + const commands = await renderCommands(); + const path = `./src/lib/data/${BOT_TYPE.toLowerCase()}.commands.json`; + writeFileSync(path, `${JSON.stringify(commands, null, ' ')}\n`); + execSync(`npx biome check --write ${path}`); +} diff --git a/src/scripts/render-creatables-file.ts b/src/scripts/renderCreatablesFile.ts similarity index 82% rename from src/scripts/render-creatables-file.ts rename to src/scripts/renderCreatablesFile.ts index b6755eabbc3..0c00ca8b456 100644 --- a/src/scripts/render-creatables-file.ts +++ b/src/scripts/renderCreatablesFile.ts @@ -1,14 +1,11 @@ -import './no-prisma'; -import '../lib/data/itemAliases'; - +import { writeFileSync } from 'node:fs'; import { isFunction } from 'e'; -import { writeFileSync } from 'fs'; import { Bank } from 'oldschooljs'; import Createables from '../lib/data/createables'; import { makeTable } from '../lib/util/smallUtils'; -export function main() { +export function renderCreatablesFile() { const headers = ['Item name', 'Input Items', 'Output Items', 'GP Cost']; const rows = Createables.map(i => { return [ @@ -21,5 +18,3 @@ export function main() { writeFileSync('./src/lib/data/creatablesTable.txt', makeTable(headers, rows)); } - -main(); diff --git a/src/scripts/scriptUtil.ts b/src/scripts/scriptUtil.ts new file mode 100644 index 00000000000..8dd600afc41 --- /dev/null +++ b/src/scripts/scriptUtil.ts @@ -0,0 +1,31 @@ +import { type ExecOptions, exec as execNonPromise } from 'node:child_process'; +import { promisify } from 'node:util'; +import { Stopwatch } from '@oldschoolgg/toolkit'; + +const rawExecAsync = promisify(execNonPromise); + +export async function execAsync(command: string | string[], options?: ExecOptions): Promise { + try { + console.log(' Running command:', command); + + const results = Array.isArray(command) + ? await Promise.all(command.map(cmd => rawExecAsync(cmd, options))) + : [await rawExecAsync(command, options)]; + + for (const result of results) { + if (result.stderr) { + console.error(result.stderr); + } + } + } catch (err) { + console.error(err); + } +} + +export async function runTimedLoggedFn(name: string, fn: () => Promise) { + const stopwatch = new Stopwatch(); + stopwatch.start(); + await fn(); + stopwatch.stop(); + console.log(`Finished ${name} in ${stopwatch.toString()}`); +} diff --git a/src/scripts/troubleshooter.ts b/src/scripts/troubleshooter.ts new file mode 100644 index 00000000000..bea2ae0cb16 --- /dev/null +++ b/src/scripts/troubleshooter.ts @@ -0,0 +1,33 @@ +import { existsSync, rmSync } from 'node:fs'; + +import { getSystemInfo } from '../lib/systemInfo'; +import { execAsync, runTimedLoggedFn } from './scriptUtil'; + +async function yarnRefreshLockfile() { + await execAsync('yarn --refresh-lockfile --check-cache'); +} + +async function deleteFoldersAndFiles() { + const paths = ['dist', 'cache.json', 'logs', 'coverage']; + for (const p of paths) { + if (existsSync(p)) { + console.log(` Deleting ${p}`); + rmSync(p, { recursive: true, force: true }); + } + } +} + +async function main() { + const info = await getSystemInfo(); + console.log(info.singleStr); + await runTimedLoggedFn('Delete Folders and Files', deleteFoldersAndFiles); + await runTimedLoggedFn('Yarn Refresh Lockfile', yarnRefreshLockfile); + console.log(`\n\nDone. Try to run "yarn build" now. If you are still having issues: + +- Delete the node_modules folder +- Kill all node.js processes in task manager +- Reboot your computer +- Ask for help in discord, showing your error/issue.`); +} + +main(); diff --git a/src/tasks/minions/HunterActivity/aerialFishingActivity.ts b/src/tasks/minions/HunterActivity/aerialFishingActivity.ts index 677b8714704..077b397a259 100644 --- a/src/tasks/minions/HunterActivity/aerialFishingActivity.ts +++ b/src/tasks/minions/HunterActivity/aerialFishingActivity.ts @@ -5,7 +5,7 @@ import addSkillingClueToLoot from '../../../lib/minions/functions/addSkillingClu import Fishing from '../../../lib/skilling/skills/fishing'; import aerialFishingCreatures from '../../../lib/skilling/skills/hunter/aerialFishing'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; +import type { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; import { roll, skillingPetDropRate } from '../../../lib/util'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { anglerBoostPercent } from '../../../mahoji/mahojiSettings'; @@ -13,7 +13,7 @@ import { anglerBoostPercent } from '../../../mahoji/mahojiSettings'; export const aerialFishingTask: MinionTask = { type: 'AerialFishing', async run(data: ActivityTaskOptionsWithQuantity) { - let { quantity, userID, channelID } = data; + const { quantity, userID, channelID } = data; const user = await mUserFetch(userID); const currentHuntLevel = user.skillLevel(SkillsEnum.Hunter); const currentFishLevel = user.skillLevel(SkillsEnum.Fishing); @@ -38,7 +38,7 @@ export const aerialFishingTask: MinionTask = { if (roll(100 - ((maxRoll - 40) * 25) / 59)) { molchPearls++; } - let currentRoll = randInt(0, maxRoll); + const currentRoll = randInt(0, maxRoll); loot.add(bluegill.table.roll()); if ( @@ -82,7 +82,7 @@ export const aerialFishingTask: MinionTask = { // If they have the entire angler outfit, give an extra 2.5% xp bonus if ( user.hasEquippedOrInBank( - Object.keys(Fishing.anglerItems).map(i => parseInt(i)), + Object.keys(Fishing.anglerItems).map(i => Number.parseInt(i)), 'every' ) ) { @@ -92,7 +92,7 @@ export const aerialFishingTask: MinionTask = { } else { // For each angler item, check if they have it, give its' XP boost if so. for (const [itemID, bonus] of Object.entries(Fishing.anglerItems)) { - if (user.hasEquippedOrInBank(parseInt(itemID))) { + if (user.hasEquippedOrInBank(Number.parseInt(itemID))) { const amountToAdd = Math.floor(fishXpReceived * (bonus / 100)); fishXpReceived += amountToAdd; bonusXP += amountToAdd; diff --git a/src/tasks/minions/HunterActivity/birdhouseActivity.ts b/src/tasks/minions/HunterActivity/birdhouseActivity.ts index c1cb97e2203..a88179389da 100644 --- a/src/tasks/minions/HunterActivity/birdhouseActivity.ts +++ b/src/tasks/minions/HunterActivity/birdhouseActivity.ts @@ -1,11 +1,11 @@ -import { Prisma } from '@prisma/client'; +import type { Prisma } from '@prisma/client'; import { randFloat, roll } from 'e'; import { Bank } from 'oldschooljs'; import birdhouses from '../../../lib/skilling/skills/hunter/birdHouseTrapping'; -import { BirdhouseData } from '../../../lib/skilling/skills/hunter/defaultBirdHouseTrap'; +import type { BirdhouseData } from '../../../lib/skilling/skills/hunter/defaultBirdHouseTrap'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { BirdhouseActivityTaskOptions } from '../../../lib/types/minions'; +import type { BirdhouseActivityTaskOptions } from '../../../lib/types/minions'; import { birdhouseLimit } from '../../../lib/util'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import itemID from '../../../lib/util/itemID'; @@ -26,7 +26,7 @@ export const birdHouseTask: MinionTask = { const user = await mUserFetch(userID); let hunterXP = 0; let craftingXP = 0; - let strungRabbitFoot = user.hasEquipped('Strung rabbit foot'); + const strungRabbitFoot = user.hasEquipped('Strung rabbit foot'); const loot = new Bank(); const birdhouse = birdhouses.find(_birdhouse => _birdhouse.name === birdhouseName); diff --git a/src/tasks/minions/HunterActivity/driftNetActivity.ts b/src/tasks/minions/HunterActivity/driftNetActivity.ts index 0cc9e9b65db..97ecfe55821 100644 --- a/src/tasks/minions/HunterActivity/driftNetActivity.ts +++ b/src/tasks/minions/HunterActivity/driftNetActivity.ts @@ -2,7 +2,7 @@ import { Bank } from 'oldschooljs'; import driftNetCreatures from '../../../lib/skilling/skills/hunter/driftNet'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; +import type { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import resolveItems from '../../../lib/util/resolveItems'; @@ -44,7 +44,7 @@ const shelldonFish = resolveItems([ export const driftNetTask: MinionTask = { type: 'DriftNet', async run(data: ActivityTaskOptionsWithQuantity) { - let { quantity, userID, channelID, duration } = data; + const { quantity, userID, channelID, duration } = data; const user = await mUserFetch(userID); const currentHuntLevel = user.skillLevel(SkillsEnum.Hunter); const currentFishLevel = user.skillLevel(SkillsEnum.Fishing); @@ -52,7 +52,7 @@ export const driftNetTask: MinionTask = { // Current fishable creatures const fishShoal = driftNetCreatures.find(_fish => _fish.name === 'Fish shoal')!; - let fishShoalCaught = quantity; + const fishShoalCaught = quantity; // Build up loot table based on fishing level const fishTable = fishShoal.table.clone(); diff --git a/src/tasks/minions/HunterActivity/hunterActivity.ts b/src/tasks/minions/HunterActivity/hunterActivity.ts index 44779bd2a49..c9a77a8898e 100644 --- a/src/tasks/minions/HunterActivity/hunterActivity.ts +++ b/src/tasks/minions/HunterActivity/hunterActivity.ts @@ -1,29 +1,23 @@ -import { Prisma } from '@prisma/client'; -import { randInt, Time } from 'e'; +import type { Prisma } from '@prisma/client'; +import { Time, randInt } from 'e'; import { Bank } from 'oldschooljs'; -import { EquipmentSlot, ItemBank } from 'oldschooljs/dist/meta/types'; +import { EquipmentSlot, type ItemBank } from 'oldschooljs/dist/meta/types'; -import { chargePortentIfHasCharges, PortentID } from '../../../lib/bso/divination'; +import { increaseBankQuantitesByPercent } from '@oldschoolgg/toolkit'; +import { PortentID, chargePortentIfHasCharges } from '../../../lib/bso/divination'; import { GLOBAL_BSO_XP_MULTIPLIER, MAX_LEVEL, PeakTier } from '../../../lib/constants'; import { globalDroprates } from '../../../lib/data/globalDroprates'; -import { UserFullGearSetup } from '../../../lib/gear'; +import type { UserFullGearSetup } from '../../../lib/gear'; import { hasWildyHuntGearEquipped } from '../../../lib/gear/functions/hasWildyHuntGearEquipped'; -import { inventionBoosts, InventionID, inventionItemBoost } from '../../../lib/invention/inventions'; +import { InventionID, inventionBoosts, inventionItemBoost } from '../../../lib/invention/inventions'; import { trackLoot } from '../../../lib/lootTrack'; import { calcLootXPHunting, generateHerbiTable } from '../../../lib/skilling/functions/calcsHunter'; import Hunter from '../../../lib/skilling/skills/hunter/hunter'; -import { Creature, SkillsEnum } from '../../../lib/skilling/types'; -import { Gear } from '../../../lib/structures/Gear'; -import { Skills } from '../../../lib/types'; -import { HunterActivityTaskOptions } from '../../../lib/types/minions'; -import { - clAdjustedDroprate, - increaseBankQuantitesByPercent, - roll, - skillingPetDropRate, - stringMatches, - toKMB -} from '../../../lib/util'; +import { type Creature, SkillsEnum } from '../../../lib/skilling/types'; +import type { Gear } from '../../../lib/structures/Gear'; +import type { Skills } from '../../../lib/types'; +import type { HunterActivityTaskOptions } from '../../../lib/types/minions'; +import { clAdjustedDroprate, roll, skillingPetDropRate, stringMatches, toKMB } from '../../../lib/util'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import itemID from '../../../lib/util/itemID'; import { updateBankSetting } from '../../../lib/util/updateBankSetting'; @@ -109,7 +103,7 @@ export function calculateHunterResult({ noRandomness ); - let crystalImpling = creature.name === 'Crystal impling'; + const crystalImpling = creature.name === 'Crystal impling'; if (creature.wildy) { let riskPkChance = creature.id === BLACK_CHIN_ID ? 100 : 200; @@ -148,8 +142,8 @@ export function calculateHunterResult({ } if (gotPked && !died && !invincible) { if (bank.amount('Saradomin brew(4)') >= 10 && bank.amount('Super restore(4)') >= 5) { - let lostBrew = randInt(1, 10); - let lostRestore = randInt(1, 5); + const lostBrew = randInt(1, 10); + const lostRestore = randInt(1, 5); const cost = new Bank().add('Saradomin brew(4)', lostBrew).add('Super restore(4)', lostRestore); totalCost.add(cost); @@ -294,7 +288,7 @@ export const hunterTask: MinionTask = { if (!creature) return; - let crystalImpling = creature.name === 'Crystal impling'; + const crystalImpling = creature.name === 'Crystal impling'; let graceful = false; if (userHasGracefulEquipped(user)) { @@ -309,7 +303,7 @@ export const hunterTask: MinionTask = { user, inventionID: InventionID.ArcaneHarvester, duration: quantity * Time.Minute * 4 - }) + }) : null; const minutes = Math.ceil(duration / Time.Minute); @@ -389,7 +383,7 @@ export const hunterTask: MinionTask = { ? '.' : ` ${quantity}x times, due to clever creatures you missed out on ${ quantity - successfulQuantity - }x catches. ` + }x catches. ` }${xpStr}`; str += `\n\nYou received: ${loot}.`; diff --git a/src/tasks/minions/PrayerActivity/offeringActivity.ts b/src/tasks/minions/PrayerActivity/offeringActivity.ts index 03b8e175ae6..884bdc7f5c1 100644 --- a/src/tasks/minions/PrayerActivity/offeringActivity.ts +++ b/src/tasks/minions/PrayerActivity/offeringActivity.ts @@ -3,7 +3,7 @@ import { percentChance, randInt } from 'e'; import { zealOutfit } from '../../../lib/shadesKeys'; import Prayer from '../../../lib/skilling/skills/prayer'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { OfferingActivityTaskOptions } from '../../../lib/types/minions'; +import type { OfferingActivityTaskOptions } from '../../../lib/types/minions'; import { roll } from '../../../lib/util'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; diff --git a/src/tasks/minions/PrayerActivity/scatteringActivity.ts b/src/tasks/minions/PrayerActivity/scatteringActivity.ts index 2483a028156..a94d07b378e 100644 --- a/src/tasks/minions/PrayerActivity/scatteringActivity.ts +++ b/src/tasks/minions/PrayerActivity/scatteringActivity.ts @@ -2,7 +2,7 @@ import { Bank } from 'oldschooljs'; import Prayer from '../../../lib/skilling/skills/prayer'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { ScatteringActivityTaskOptions } from '../../../lib/types/minions'; +import type { ScatteringActivityTaskOptions } from '../../../lib/types/minions'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { userStatsBankUpdate } from '../../../mahoji/mahojiSettings'; @@ -22,9 +22,9 @@ export const scatteringTask: MinionTask = { duration }); - let str = `${user}, ${user.minionName} finished scattering ${quantity}x ${ash.name}. ${xpRes}`; + const str = `${user}, ${user.minionName} finished scattering ${quantity}x ${ash.name}. ${xpRes}`; - await userStatsBankUpdate(user.id, 'scattered_ashes_bank', new Bank().add(ash.inputId, quantity)); + await userStatsBankUpdate(user, 'scattered_ashes_bank', new Bank().add(ash.inputId, quantity)); handleTripFinish(user, channelID, str, undefined, data, null); } diff --git a/src/tasks/minions/agilityActivity.ts b/src/tasks/minions/agilityActivity.ts index 652a20f1943..52bc2e785a9 100644 --- a/src/tasks/minions/agilityActivity.ts +++ b/src/tasks/minions/agilityActivity.ts @@ -1,17 +1,18 @@ -import { increaseNumByPercent, randInt, roll, Time } from 'e'; +import { Time, increaseNumByPercent, randInt, roll } from 'e'; import { Bank } from 'oldschooljs'; -import { ItemBank } from 'oldschooljs/dist/meta/types'; +import type { ItemBank } from 'oldschooljs/dist/meta/types'; -import { chargePortentIfHasCharges, PortentID } from '../../lib/bso/divination'; +import { addItemToBank, randomVariation, toKMB } from 'oldschooljs/dist/util'; +import { PortentID, chargePortentIfHasCharges } from '../../lib/bso/divination'; import { MIN_LENGTH_FOR_PET } from '../../lib/constants'; import { globalDroprates } from '../../lib/data/globalDroprates'; import { ArdougneDiary, userhasDiaryTier } from '../../lib/diaries'; import { isDoubleLootActive } from '../../lib/doubleLoot'; import Agility from '../../lib/skilling/skills/agility'; import { calcUserGorajanShardChance } from '../../lib/skilling/skills/dung/dungDbFunctions'; -import { Course, SkillsEnum } from '../../lib/skilling/types'; -import { AgilityActivityTaskOptions } from '../../lib/types/minions'; -import { addItemToBank, clAdjustedDroprate, randomVariation, skillingPetDropRate, toKMB } from '../../lib/util'; +import { type Course, SkillsEnum } from '../../lib/skilling/types'; +import type { AgilityActivityTaskOptions } from '../../lib/types/minions'; +import { clAdjustedDroprate, skillingPetDropRate } from '../../lib/util'; import getOSItem from '../../lib/util/getOSItem'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; import { updateClientGPTrackSetting, userStatsUpdate } from '../../mahoji/mahojiSettings'; @@ -56,7 +57,7 @@ function calculateMarks({ } if (usingHarry) { - let harryBonus = Math.ceil(randomVariation(totalMarks * 2, 10)); + const harryBonus = Math.ceil(randomVariation(totalMarks * 2, 10)); boosts.push(`Harry found you ${harryBonus - totalMarks}x extra Marks of grace.`); totalMarks = harryBonus; } else if (course.id !== 5 && agilityLevel >= course.level + 20) { @@ -141,7 +142,7 @@ export function calculateAgilityResult({ export const agilityTask: MinionTask = { type: 'Agility', async run(data: AgilityActivityTaskOptions) { - let { courseID, quantity, userID, channelID, duration, alch } = data; + const { courseID, quantity, userID, channelID, duration, alch } = data; const minutes = Math.round(duration / Time.Minute); const user = await mUserFetch(userID); const currentLevel = user.skillLevel(SkillsEnum.Agility); @@ -170,14 +171,15 @@ export const agilityTask: MinionTask = { hasAgilityPortent: portentResult.didCharge }); + const stats = await user.fetchStats({ laps_scores: true }); const { laps_scores: newLapScores } = await userStatsUpdate( user.id, - ({ laps_scores }) => ({ - laps_scores: addItemToBank(laps_scores as ItemBank, course.id, successfulLaps), + { + laps_scores: addItemToBank(stats.laps_scores as ItemBank, course.id, successfulLaps), xp_from_graceful_portent: { increment: portentXP } - }), + }, { laps_scores: true } ); diff --git a/src/tasks/minions/alchingActivity.ts b/src/tasks/minions/alchingActivity.ts index 9caf8616be7..1c324f33ec7 100644 --- a/src/tasks/minions/alchingActivity.ts +++ b/src/tasks/minions/alchingActivity.ts @@ -3,7 +3,7 @@ import { Bank } from 'oldschooljs'; import { MIN_LENGTH_FOR_PET } from '../../lib/constants'; import { SkillsEnum } from '../../lib/skilling/types'; -import { AlchingActivityTaskOptions } from '../../lib/types/minions'; +import type { AlchingActivityTaskOptions } from '../../lib/types/minions'; import { roll } from '../../lib/util'; import getOSItem from '../../lib/util/getOSItem'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; @@ -12,7 +12,7 @@ import { updateClientGPTrackSetting } from '../../mahoji/mahojiSettings'; export const alchingTask: MinionTask = { type: 'Alching', async run(data: AlchingActivityTaskOptions) { - let { itemID, quantity, channelID, alchValue, userID, duration } = data; + const { itemID, quantity, channelID, alchValue, userID, duration } = data; const user = await mUserFetch(userID); const loot = new Bank({ Coins: alchValue }); diff --git a/src/tasks/minions/bossEventActivity.ts b/src/tasks/minions/bossEventActivity.ts index 44a6dea9234..135c73f66f6 100644 --- a/src/tasks/minions/bossEventActivity.ts +++ b/src/tasks/minions/bossEventActivity.ts @@ -1,8 +1,8 @@ import { Bank } from 'oldschooljs'; import { bossEvents } from '../../lib/bossEvents'; -import { BossUser } from '../../lib/structures/Boss'; -import { NewBossOptions } from '../../lib/types/minions'; +import type { BossUser } from '../../lib/structures/Boss'; +import type { NewBossOptions } from '../../lib/types/minions'; export const bossEventTask: MinionTask = { type: 'BossEvent', diff --git a/src/tasks/minions/bso/bathhousesActivity.ts b/src/tasks/minions/bso/bathhousesActivity.ts index f5fe88db902..5692b94bbf0 100644 --- a/src/tasks/minions/bso/bathhousesActivity.ts +++ b/src/tasks/minions/bso/bathhousesActivity.ts @@ -1,5 +1,5 @@ import { baxtorianBathhousesActivity } from '../../../lib/baxtorianBathhouses'; -import { BathhouseTaskOptions } from '../../../lib/types/minions'; +import type { BathhouseTaskOptions } from '../../../lib/types/minions'; export const bathhouseTask: MinionTask = { type: 'BaxtorianBathhouses', diff --git a/src/tasks/minions/bso/bonanzaActivity.ts b/src/tasks/minions/bso/bonanzaActivity.ts index 2972ab87ef6..83535c4a5ce 100644 --- a/src/tasks/minions/bso/bonanzaActivity.ts +++ b/src/tasks/minions/bso/bonanzaActivity.ts @@ -5,8 +5,8 @@ import { MAX_LEVEL } from '../../../lib/constants'; import { spectatorClothes } from '../../../lib/data/CollectionsExport'; import { incrementMinigameScore } from '../../../lib/settings/settings'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { getAllUserTames, TameSpeciesID } from '../../../lib/tames'; -import { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; +import { TameSpeciesID, getAllUserTames } from '../../../lib/tames'; +import type { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { tameName } from '../../../lib/util/tameUtil'; diff --git a/src/tasks/minions/bso/disassemblingActivity.ts b/src/tasks/minions/bso/disassemblingActivity.ts index 3c3f5714aa4..6f123937d2d 100644 --- a/src/tasks/minions/bso/disassemblingActivity.ts +++ b/src/tasks/minions/bso/disassemblingActivity.ts @@ -1,13 +1,13 @@ import { userMention } from 'discord.js'; -import { roll, Time } from 'e'; +import { Time, roll } from 'e'; import { Bank } from 'oldschooljs'; -import { ItemBank } from 'oldschooljs/dist/meta/types'; +import type { ItemBank } from 'oldschooljs/dist/meta/types'; import { Emoji } from '../../../lib/constants'; -import { inventionBoosts, transactMaterialsFromUser } from '../../../lib/invention/inventions'; import { MaterialBank } from '../../../lib/invention/MaterialBank'; +import { inventionBoosts, transactMaterialsFromUser } from '../../../lib/invention/inventions'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { DisassembleTaskOptions } from '../../../lib/types/minions'; +import type { DisassembleTaskOptions } from '../../../lib/types/minions'; import { mahojiClientSettingsFetch, mahojiClientSettingsUpdate } from '../../../lib/util/clientSettings'; import getOSItem from '../../../lib/util/getOSItem'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; diff --git a/src/tasks/minions/bso/doaActivity.ts b/src/tasks/minions/bso/doaActivity.ts index 7f0782c7c74..2e08435bfdc 100644 --- a/src/tasks/minions/bso/doaActivity.ts +++ b/src/tasks/minions/bso/doaActivity.ts @@ -1,64 +1,25 @@ import { formatOrdinal } from '@oldschoolgg/toolkit'; -import { randArrItem, reduceNumByPercent, roll, Time, uniqueArr } from 'e'; -import { Bank, LootTable } from 'oldschooljs'; +import { Time, randArrItem, reduceNumByPercent, roll, uniqueArr } from 'e'; +import { Bank } from 'oldschooljs'; import { SkillsEnum } from 'oldschooljs/dist/constants'; -import { ItemBank } from 'oldschooljs/dist/meta/types'; +import type { ItemBank } from 'oldschooljs/dist/meta/types'; import { drawChestLootImage } from '../../../lib/bankImage'; +import { DOANonUniqueTable } from '../../../lib/bso/doa/doaLootTable'; import { Emoji, Events } from '../../../lib/constants'; import { doaCL, doaMetamorphPets } from '../../../lib/data/CollectionsExport'; import { globalDroprates } from '../../../lib/data/globalDroprates'; -import { chanceOfDOAUnique, DOARooms, pickUniqueToGiveUser } from '../../../lib/depthsOfAtlantis'; +import { DOARooms, chanceOfDOAUnique, pickUniqueToGiveUser } from '../../../lib/depthsOfAtlantis'; import { trackLoot } from '../../../lib/lootTrack'; import { resolveAttackStyles } from '../../../lib/minions/functions'; import { incrementMinigameScore } from '../../../lib/settings/settings'; -import { DragonTable } from '../../../lib/simulation/grandmasterClue'; -import { runeAlchablesTable, StoneSpiritTable } from '../../../lib/simulation/sharedTables'; import { TeamLoot } from '../../../lib/simulation/TeamLoot'; -import { DOAOptions } from '../../../lib/types/minions'; +import type { DOAOptions } from '../../../lib/types/minions'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import resolveItems from '../../../lib/util/resolveItems'; import { updateBankSetting } from '../../../lib/util/updateBankSetting'; import { userStatsUpdate } from '../../../mahoji/mahojiSettings'; -const DragonFletchingTable = new LootTable() - .add('Dragon arrowtips', [10, 20]) - .add('Dragon dart tip', [10, 20]) - .add('Dragon javelin heads', [10, 20]); - -const RareGemRockTable = new LootTable() - .add('Uncut sapphire', 1, 9) - .add('Uncut emerald', 1, 5) - .add('Uncut ruby', 1, 5) - .add('Uncut diamond', 1, 4); - -const RareOreTable = new LootTable() - .add('Mithril ore', [10, 70], 55) - .add('Adamantite ore', [15, 50], 55) - .add('Runite ore', [1, 20], 45) - .add('Amethyst', [1, 15], 45); - -const BaseNonUniqueTable = new LootTable() - .add(RareGemRockTable, [80, 120], undefined, { multiply: true }) - .add(DragonFletchingTable, [15, 20], undefined, { multiply: true }) - .add(runeAlchablesTable, [25, 30], undefined, { multiply: true }) - .add(RareOreTable, [11, 15], undefined, { multiply: true }) - .add(DragonTable, [11, 18], undefined, { multiply: true }) - .add(StoneSpiritTable, [30, 60], undefined, { multiply: true }); - -const DOAClueTable = new LootTable() - .add('Clue scroll (medium)', 1, 5) - .add('Clue scroll (hard)', 1, 4) - .add('Clue scroll (elite)', 1, 3) - .add('Clue scroll (master)', 1, 2) - .add('Clue scroll (grandmaster)', 1, 2); - -export const DOANonUniqueTable = new LootTable() - .tertiary(100, 'Oceanic dye') - .oneIn(40, 'Shark tooth') - .every(DOAClueTable, 2, { multiply: true }) - .every(BaseNonUniqueTable, 3, { multiply: true }); - async function handleDOAXP(user: MUser, qty: number, isCm: boolean) { let rangeXP = 10_000 * qty; let magicXP = 1500 * qty; @@ -121,21 +82,20 @@ export const doaTask: MinionTask = { } // Increment all users attempts await Promise.all( - allUsers.map(i => + allUsers.map(async u => { + const stats = await u.fetchStats({ doa_room_attempts_bank: true }); + const currentKCBank = new Bank(stats.doa_room_attempts_bank as ItemBank); userStatsUpdate( - i.id, - u => { - const currentKCBank = new Bank(u.doa_room_attempts_bank as ItemBank); - return { - doa_room_attempts_bank: currentKCBank.add(newRoomAttempts).bank, - doa_attempts: { - increment: quantity - } - }; + u.id, + { + doa_room_attempts_bank: currentKCBank.add(newRoomAttempts).bank, + doa_attempts: { + increment: quantity + } }, {} - ) - ) + ); + }) ); if (raids.every(i => i.wipedRoom !== null)) { @@ -212,12 +172,12 @@ export const doaTask: MinionTask = { let resultMessage = isSolo ? `${leaderSoloUser}, your minion finished ${quantity === 1 ? 'a' : `${quantity}x`}${ cm ? ' Challenge Mode' : '' - } Depths of Atlantis raid${quantity > 1 ? 's' : ''}! Your KC is now ${ + } Depths of Atlantis raid${quantity > 1 ? 's' : ''}! Your KC is now ${ minigameIncrementResult[0].newScore - }.\n` + }.\n` : `<@${leader}> Your${cm ? ' Challenge Mode' : ''} Depths of Atlantis Raid${ quantity > 1 ? 's have' : ' has' - } finished.\n`; + } finished.\n`; const shouldShowImage = allUsers.length <= 3 && totalLoot.entries().every(i => i[1].length <= 6); @@ -231,15 +191,14 @@ export const doaTask: MinionTask = { collectionLog: true }); + const stats = await user.fetchStats({ doa_loot: true }); await userStatsUpdate( user.id, - u => { - return { - doa_total_minutes_raided: { - increment: Math.floor(duration / Time.Minute) - }, - doa_loot: new Bank(u.doa_loot as ItemBank).add(totalLoot.get(userID)).bank - }; + { + doa_total_minutes_raided: { + increment: Math.floor(duration / Time.Minute) + }, + doa_loot: new Bank(stats.doa_loot as ItemBank).add(totalLoot.get(userID)).bank }, {} ); @@ -310,7 +269,7 @@ export const doaTask: MinionTask = { } ], type: 'Depths of Atlantis' - }) + }) : undefined, data, null @@ -330,7 +289,7 @@ export const doaTask: MinionTask = { customTexts: [] })), type: 'Depths of Atlantis' - }) + }) : undefined, data, null diff --git a/src/tasks/minions/bso/dungeoneeringActivity.ts b/src/tasks/minions/bso/dungeoneeringActivity.ts index ee9f7044cec..93eb9a08b63 100644 --- a/src/tasks/minions/bso/dungeoneeringActivity.ts +++ b/src/tasks/minions/bso/dungeoneeringActivity.ts @@ -1,7 +1,7 @@ -import { reduceNumByPercent, Time } from 'e'; +import { Time, reduceNumByPercent } from 'e'; import { Bank } from 'oldschooljs'; -import { chargePortentIfHasCharges, PortentID } from '../../../lib/bso/divination'; +import { PortentID, chargePortentIfHasCharges } from '../../../lib/bso/divination'; import { MysteryBoxes } from '../../../lib/bsoOpenables'; import { isDoubleLootActive } from '../../../lib/doubleLoot'; import { @@ -10,7 +10,7 @@ import { numberOfGorajanOutfitsEquipped } from '../../../lib/skilling/skills/dung/dungDbFunctions'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { DungeoneeringOptions } from '../../../lib/types/minions'; +import type { DungeoneeringOptions } from '../../../lib/types/minions'; import { randomVariation, roll, toKMB } from '../../../lib/util'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { userStatsUpdate } from '../../../mahoji/mahojiSettings'; @@ -79,7 +79,7 @@ export function calculateDungeoneeringResult({ loot.add('Gorajan bonecrusher (u)'); } - let portentXP = Math.ceil(tokens * 16.646); + const portentXP = Math.ceil(tokens * 16.646); if (hasDungeonPortent && tokens > 0) { xp += portentXP; tokens = 0; @@ -134,11 +134,11 @@ export const dungeoneeringTask: MinionTask = { } if (portentXP > 0) { - await userStatsUpdate(user.id, () => ({ + await userStatsUpdate(user.id, { xp_from_dungeon_portent: { increment: portentXP } - })); + }); } if (portentResult.didCharge) { diff --git a/src/tasks/minions/bso/fishingContestActivity.ts b/src/tasks/minions/bso/fishingContestActivity.ts index c2ac234130b..ab0ac4b221b 100644 --- a/src/tasks/minions/bso/fishingContestActivity.ts +++ b/src/tasks/minions/bso/fishingContestActivity.ts @@ -4,11 +4,11 @@ import { Bank } from 'oldschooljs'; import { MysteryBoxes } from '../../../lib/bsoOpenables'; import { catchFishAtLocation, fishingLocations } from '../../../lib/fishingContest'; import { trackLoot } from '../../../lib/lootTrack'; -import { prisma } from '../../../lib/settings/prisma'; + import { incrementMinigameScore } from '../../../lib/settings/settings'; import { ClueTable } from '../../../lib/simulation/sharedTables'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { FishingContestOptions } from '../../../lib/types/minions'; +import type { FishingContestOptions } from '../../../lib/types/minions'; import getOSItem from '../../../lib/util/getOSItem'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { updateBankSetting } from '../../../lib/util/updateBankSetting'; @@ -29,7 +29,7 @@ export const fishingContestTask: MinionTask = { const { newScore } = await incrementMinigameScore(userID, 'fishing_contest', 1); const fishLocation = fishingLocations.find(i => i.id === location)!; - let caughtFish = []; + const caughtFish = []; for (let i = 0; i < quantity; i++) { const fish = await catchFishAtLocation({ user, location: fishLocation }); caughtFish.push(fish); @@ -44,7 +44,7 @@ export const fishingContestTask: MinionTask = { }); const loot = new Bank(); - let tackleBoxChance = user.hasEquipped('Fishing master cape') ? 2 : 3; + const tackleBoxChance = user.hasEquipped('Fishing master cape') ? 2 : 3; if (roll(tackleBoxChance)) { for (const [tackleBox, fishLevel] of [ ['Basic tackle box', 75], @@ -86,12 +86,12 @@ export const fishingContestTask: MinionTask = { await user.addItemsToBank({ items: loot, collectionLog: true }); - let fishingXP = calculateFishingContestXP({ + const fishingXP = calculateFishingContestXP({ fishSizeCM: caughtFish[0].lengthCentimetres, fishingLevel: user.skillLevel(SkillsEnum.Fishing) }); - let xpStr = await user.addXP({ + const xpStr = await user.addXP({ skillName: SkillsEnum.Fishing, amount: fishingXP, duration diff --git a/src/tasks/minions/bso/fogActivity.ts b/src/tasks/minions/bso/fogActivity.ts index ad5d8be5bf9..5d897e5ff3a 100644 --- a/src/tasks/minions/bso/fogActivity.ts +++ b/src/tasks/minions/bso/fogActivity.ts @@ -2,7 +2,7 @@ import { Bank } from 'oldschooljs'; import { userHasFlappy } from '../../../lib/invention/inventions'; import { incrementMinigameScore } from '../../../lib/settings/settings'; -import { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; +import type { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; export const fogTask: MinionTask = { diff --git a/src/tasks/minions/bso/guthixianCacheActivity.ts b/src/tasks/minions/bso/guthixianCacheActivity.ts index b0ea4ee458d..d83eefbe2ee 100644 --- a/src/tasks/minions/bso/guthixianCacheActivity.ts +++ b/src/tasks/minions/bso/guthixianCacheActivity.ts @@ -1,7 +1,7 @@ import { roll } from 'e'; import { Bank } from 'oldschooljs'; -import { chargePortentIfHasCharges, PortentID } from '../../../lib/bso/divination'; +import { PortentID, chargePortentIfHasCharges } from '../../../lib/bso/divination'; import { Emoji } from '../../../lib/constants'; import { divinersOutfit } from '../../../lib/data/CollectionsExport'; import { incrementMinigameScore } from '../../../lib/settings/minigames'; @@ -13,10 +13,10 @@ import { handleTripFinish } from '../../../lib/util/handleTripFinish'; export const guthixianCacheTask: MinionTask = { type: 'GuthixianCache', async run(data: MinigameActivityTaskOptionsWithNoChanges) { - let { userID, channelID, duration } = data; + const { userID, channelID, duration } = data; const user = await mUserFetch(userID); - let xp = user.skillLevel('divination') * user.skillLevel('divination') * 2.5; + const xp = user.skillLevel('divination') * user.skillLevel('divination') * 2.5; const xpRes = await user.addXP({ skillName: SkillsEnum.Divination, amount: xp, diff --git a/src/tasks/minions/bso/ignecarusActivity.ts b/src/tasks/minions/bso/ignecarusActivity.ts index 187c71ee052..bd672908f5c 100644 --- a/src/tasks/minions/bso/ignecarusActivity.ts +++ b/src/tasks/minions/bso/ignecarusActivity.ts @@ -11,11 +11,11 @@ import { } from '../../../lib/minions/data/killableMonsters/custom/bosses/Ignecarus'; import { addMonsterXP } from '../../../lib/minions/functions'; import announceLoot from '../../../lib/minions/functions/announceLoot'; -import { prisma } from '../../../lib/settings/prisma'; + import { TeamLoot } from '../../../lib/simulation/TeamLoot'; import { getUsersCurrentSlayerInfo } from '../../../lib/slayer/slayerUtil'; -import { BossUser } from '../../../lib/structures/Boss'; -import { NewBossOptions } from '../../../lib/types/minions'; +import type { BossUser } from '../../../lib/structures/Boss'; +import type { NewBossOptions } from '../../../lib/types/minions'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { updateBankSetting } from '../../../lib/util/updateBankSetting'; import { sendToChannelID } from '../../../lib/util/webhook'; @@ -108,7 +108,9 @@ export const ignecarusTask: MinionTask = { taskQuantity: quantity }); await user.addItemsToBank({ items: loot, collectionLog: true }); - const purple = Object.keys(loot.bank).some(itemID => IgnecarusNotifyDrops.includes(parseInt(itemID))); + const purple = Object.keys(loot.bank).some(itemID => + IgnecarusNotifyDrops.includes(Number.parseInt(itemID)) + ); resultStr += `\n${purple ? Emoji.Purple : ''}${user} received ${loot}.`; announceLoot({ diff --git a/src/tasks/minions/bso/kalphiteKingActivity.ts b/src/tasks/minions/bso/kalphiteKingActivity.ts index e9aeb694687..cced73c8641 100644 --- a/src/tasks/minions/bso/kalphiteKingActivity.ts +++ b/src/tasks/minions/bso/kalphiteKingActivity.ts @@ -9,10 +9,10 @@ import { trackLoot } from '../../../lib/lootTrack'; import { KalphiteKingMonster } from '../../../lib/minions/data/killableMonsters/custom/bosses/KalphiteKing'; import { addMonsterXP } from '../../../lib/minions/functions'; import announceLoot from '../../../lib/minions/functions/announceLoot'; -import { prisma } from '../../../lib/settings/prisma'; + import { TeamLoot } from '../../../lib/simulation/TeamLoot'; import { getUsersCurrentSlayerInfo } from '../../../lib/slayer/slayerUtil'; -import { BossActivityTaskOptions } from '../../../lib/types/minions'; +import type { BossActivityTaskOptions } from '../../../lib/types/minions'; import { getKalphiteKingGearStats } from '../../../lib/util/getKalphiteKingGearStats'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { makeBankImage } from '../../../lib/util/makeBankImage'; @@ -60,7 +60,7 @@ export const kalphiteKingTask: MinionTask = { } if (teamFailed || percentChance(user.chanceOfDeath)) { - deaths[user.id] = Boolean(deaths[user.id]) ? deaths[user.id] + 1 : 1; + deaths[user.id] = deaths[user.id] ? deaths[user.id] + 1 : 1; // Mark user as dead this kill: deathsThisKill[user.id] = 1; } else { @@ -78,7 +78,7 @@ export const kalphiteKingTask: MinionTask = { if (!winner) continue; teamsLoot.add(winner, loot); - kcAmounts[winner] = Boolean(kcAmounts[winner]) ? ++kcAmounts[winner] : 1; + kcAmounts[winner] = kcAmounts[winner] ? ++kcAmounts[winner] : 1; } const leaderUser = parsedUsers.find(p => p.id === userID)?.user ?? parsedUsers[0].user; @@ -89,14 +89,14 @@ export const kalphiteKingTask: MinionTask = { let soloItemsAdded = null; const totalLoot = new Bank(); - for (let [userID, loot] of teamsLoot.entries()) { + for (const [userID, loot] of teamsLoot.entries()) { const { user } = parsedUsers.find(p => p.id === userID)!; if (!user) continue; totalLoot.add(loot); const { previousCL, itemsAdded } = await user.addItemsToBank({ items: loot, collectionLog: true }); const kcToAdd = kcAmounts[user.id]; if (kcToAdd) await user.incrementKC(KalphiteKingMonster.id, kcToAdd); - const purple = Object.keys(loot.bank).some(id => kalphiteKingCL.includes(parseInt(id))); + const purple = Object.keys(loot.bank).some(id => kalphiteKingCL.includes(Number.parseInt(id))); const usersTask = await getUsersCurrentSlayerInfo(user.id); const isOnTask = @@ -104,7 +104,7 @@ export const kalphiteKingTask: MinionTask = { usersTask.currentTask !== null && usersTask.assignedTask.monsters.includes(KalphiteKingMonster.id); - let xpStr = await addMonsterXP(user, { + const xpStr = await addMonsterXP(user, { monsterID: KalphiteKingMonster.id, quantity: Math.ceil(quantity / users.length), duration, @@ -189,7 +189,7 @@ export const kalphiteKingTask: MinionTask = { user: leaderUser, previousCL: soloPrevCl ?? undefined }) - ).file.attachment; + ).file.attachment; handleTripFinish( leaderUser, channelID, @@ -197,9 +197,9 @@ export const kalphiteKingTask: MinionTask = { ? `${leaderUser}, ${leaderUser.minionName} died in all their attempts to kill the Kalphite King, they apologize and promise to try harder next time.` : `${leaderUser}, ${leaderUser.minionName} finished killing ${quantity} ${ KalphiteKingMonster.name - }, you died ${deaths[userID] ?? 0} times. Your Kalphite King KC is now ${await leaderUser.getKC( + }, you died ${deaths[userID] ?? 0} times. Your Kalphite King KC is now ${await leaderUser.getKC( KalphiteKingMonster.id - )}.\n\n${soloXP}`, + )}.\n\n${soloXP}`, image!, data, soloItemsAdded diff --git a/src/tasks/minions/bso/kibbleActivity.ts b/src/tasks/minions/bso/kibbleActivity.ts index 2cea9e9898b..88ee1de4e5d 100644 --- a/src/tasks/minions/bso/kibbleActivity.ts +++ b/src/tasks/minions/bso/kibbleActivity.ts @@ -2,13 +2,13 @@ import { Bank } from 'oldschooljs'; import { kibbles } from '../../../lib/data/kibble'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { KibbleOptions } from '../../../lib/types/minions'; +import type { KibbleOptions } from '../../../lib/types/minions'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; export const kibbleTask: MinionTask = { type: 'KibbleMaking', async run(data: KibbleOptions) { - let { quantity, channelID, userID, kibbleType, duration } = data; + const { quantity, channelID, userID, kibbleType, duration } = data; const user = await mUserFetch(userID); const kibble = kibbles.find(k => k.type === kibbleType)!; diff --git a/src/tasks/minions/bso/kingGoldemarActivity.ts b/src/tasks/minions/bso/kingGoldemarActivity.ts index 4a06e156182..69ff9ef75e9 100644 --- a/src/tasks/minions/bso/kingGoldemarActivity.ts +++ b/src/tasks/minions/bso/kingGoldemarActivity.ts @@ -8,11 +8,11 @@ import KingGoldemar, { KingGoldemarLootTable } from '../../../lib/minions/data/killableMonsters/custom/bosses/KingGoldemar'; import { addMonsterXP } from '../../../lib/minions/functions'; -import { prisma } from '../../../lib/settings/prisma'; + import { TeamLoot } from '../../../lib/simulation/TeamLoot'; -import { calcDwwhChance, gpCostPerKill } from '../../../lib/structures/Boss'; -import { NewBossOptions } from '../../../lib/types/minions'; -import { formatDuration, roll, toKMB } from '../../../lib/util'; +import { calcDwwhChance } from '../../../lib/structures/Boss'; +import type { NewBossOptions } from '../../../lib/types/minions'; +import { roll } from '../../../lib/util'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { updateBankSetting } from '../../../lib/util/updateBankSetting'; @@ -33,7 +33,7 @@ export const kingGoldemarTask: MinionTask = { const { channelID, users: idArr, duration, bossUsers } = data; const deaths: MUser[] = []; const users: MUser[] = await Promise.all(idArr.map(i => mUserFetch(i))); - const solo = users.length < 2 ? true : false; + const solo = users.length < 2; const getUser = (id: string) => users.find(u => u.id === id)!; const dwwhTable: MUser[] = []; @@ -70,7 +70,7 @@ export const kingGoldemarTask: MinionTask = { await Promise.all(users.map(u => u.incrementKC(KingGoldemar.id, 1))); - let dwwhChance = calcDwwhChance(users); + const dwwhChance = calcDwwhChance(users); const gotDWWH = roll(dwwhChance); const dwwhRecipient = gotDWWH ? randArrItem(dwwhTable) : null; @@ -107,10 +107,10 @@ export const kingGoldemarTask: MinionTask = { (gotDWWH && dwwhRecipient) || trickDidActivate ? `${ trickDidActivate ? userGettingTricked.usernameOrMention : dwwhRecipient?.usernameOrMention - } delivers a crushing blow to King Goldemars warhammer, breaking it. The king has no choice but to flee the chambers, **leaving behind his broken hammer.**` + } delivers a crushing blow to King Goldemars warhammer, breaking it. The king has no choice but to flee the chambers, **leaving behind his broken hammer.**` : `${ solo ? 'You' : 'Your team' - } brought King Goldemar to a very weak state, he fled the chambers before he could be killed and escaped through a secret exit, promising to get revenge on you.`; + } brought King Goldemar to a very weak state, he fled the chambers before he could be killed and escaped through a secret exit, promising to get revenge on you.`; let resultStr = `${tagAll}\n\n${killStr}\n\n${Emoji.Casket} **Loot:**`; @@ -172,12 +172,6 @@ export const kingGoldemarTask: MinionTask = { )}.`; } - if (1 > 2) { - resultStr += `\n\nAt this rate, it will take approximately ${dwwhChance} trips (${formatDuration( - dwwhChance * duration - )}) to receive a DWWH, costing ${toKMB(dwwhChance * gpCostPerKill(users[0]))} GP. 1 in ${dwwhChance}`; - } - if (!solo) { handleTripFinish(users[0], channelID, resultStr, undefined, data, null); } else { diff --git a/src/tasks/minions/bso/memoryHarvestActivity.ts b/src/tasks/minions/bso/memoryHarvestActivity.ts index ec4dc8749de..8e089a0d183 100644 --- a/src/tasks/minions/bso/memoryHarvestActivity.ts +++ b/src/tasks/minions/bso/memoryHarvestActivity.ts @@ -2,16 +2,16 @@ import { calcPercentOfNum, increaseNumByPercent } from 'e'; import { Bank } from 'oldschooljs'; import { + type DivinationEnergy, + MemoryHarvestType, calcEnergyPerMemory, - divinationEnergies, - DivinationEnergy, - MemoryHarvestType + divinationEnergies } from '../../../lib/bso/divination'; import { Emoji } from '../../../lib/constants'; import { inventionBoosts } from '../../../lib/invention/inventions'; import { SkillsEnum } from '../../../lib/skilling/types'; import type { MemoryHarvestOptions } from '../../../lib/types/minions'; -import { calculateAverageTimeForSuccess, formatDuration, roll } from '../../../lib/util'; +import { roll } from '../../../lib/util'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { userStatsBankUpdate } from '../../../mahoji/mahojiSettings'; @@ -22,14 +22,14 @@ const MEMORIES_PER_HARVEST = SECONDS_TO_HARVEST * 2; export const totalTimePerRound = SECONDS_TO_HARVEST + SECONDS_TO_CONVERT * MEMORIES_PER_HARVEST; function calcConversionResult(hasBoon: boolean, method: MemoryHarvestType, energy: DivinationEnergy) { - let convertToXPXP = hasBoon ? energy.convertBoon ?? energy.convertNormal : energy.convertNormal; + const convertToXPXP = hasBoon ? energy.convertBoon ?? energy.convertNormal : energy.convertNormal; switch (method) { case MemoryHarvestType.ConvertToXP: { return { xp: convertToXPXP }; } case MemoryHarvestType.ConvertToEnergy: { - let xp = 1 + Math.ceil(convertToXPXP / 3.5); + const xp = 1 + Math.ceil(convertToXPXP / 3.5); return { xp }; } case MemoryHarvestType.ConvertWithEnergyToXP: { @@ -80,7 +80,7 @@ export function memoryHarvestResult({ for (let i = 0; i < rounds; i++) { // Step 1: Harvest memories - let memoriesHarvested = MEMORIES_PER_HARVEST; + const memoriesHarvested = MEMORIES_PER_HARVEST; totalMemoriesHarvested += memoriesHarvested; @@ -129,7 +129,7 @@ export function memoryHarvestResult({ } } - let clueChance = 1200; + const clueChance = 1200; // Step 3: Roll for pet for (let t = 0; t < memoriesHarvested; t++) { if (roll(petChance)) { @@ -174,7 +174,7 @@ export function memoryHarvestResult({ totalDivinationXP, totalMemoriesHarvested, petChancePerMemory: petChance, - avgPetTime: calculateAverageTimeForSuccess((totalMemoriesHarvested / petChance) * 100, duration), + avgPetTime: totalMemoriesHarvested / petChance / duration, boosts, energyPerMemory }; @@ -183,7 +183,7 @@ export function memoryHarvestResult({ export const memoryHarvestTask: MinionTask = { type: 'MemoryHarvest', async run(data: MemoryHarvestOptions) { - let { + const { userID, channelID, duration, @@ -207,19 +207,18 @@ export const memoryHarvestTask: MinionTask = { didGetGuthixianBoost = true; } - const { boosts, totalDivinationXP, totalMemoriesHarvested, petChancePerMemory, loot, avgPetTime, cost } = - memoryHarvestResult({ - duration, - hasBoon, - energy, - harvestMethod: harvestMethodIndex, - hasWispBuster, - hasGuthixianBoost: didGetGuthixianBoost, - hasDivineHand, - isUsingDivinationPotion, - hasMasterCape: user.hasEquippedOrInBank('Divination master cape'), - rounds - }); + const { boosts, totalDivinationXP, totalMemoriesHarvested, loot, cost } = memoryHarvestResult({ + duration, + hasBoon, + energy, + harvestMethod: harvestMethodIndex, + hasWispBuster, + hasGuthixianBoost: didGetGuthixianBoost, + hasDivineHand, + isUsingDivinationPotion, + hasMasterCape: user.hasEquippedOrInBank('Divination master cape'), + rounds + }); if (cost.length > 0) { if (!user.owns(cost)) { @@ -246,9 +245,7 @@ export const memoryHarvestTask: MinionTask = { energy.type } memories, and turning them into ${ harvestMethodIndex === MemoryHarvestType.ConvertToEnergy ? 'energies' : 'XP' - }. ${xpRes}. - -Pet chance 1 in ${petChancePerMemory.toLocaleString()}, ${formatDuration(avgPetTime)} on average to get pet`; + }. ${xpRes}.`; if (loot.length > 0) { await userStatsBankUpdate(user.id, 'divination_loot', loot); diff --git a/src/tasks/minions/bso/moktangActivity.ts b/src/tasks/minions/bso/moktangActivity.ts index 8a1a46564d9..8a2d10e7643 100644 --- a/src/tasks/minions/bso/moktangActivity.ts +++ b/src/tasks/minions/bso/moktangActivity.ts @@ -1,4 +1,4 @@ -import { formatOrdinal } from '@oldschoolgg/toolkit'; +import { formatOrdinal, increaseBankQuantitesByPercent } from '@oldschoolgg/toolkit'; import { userMention } from 'discord.js'; import { randInt } from 'e'; import { Bank } from 'oldschooljs'; @@ -11,13 +11,13 @@ import { MOKTANG_ID, MoktangLootTable } from '../../../lib/minions/data/killable import { FletchingTipsTable, HighTierStoneSpiritTable, + StoneSpiritTable, lowRuneHighAdamantTable, - runeWeaponTable, - StoneSpiritTable + runeWeaponTable } from '../../../lib/simulation/sharedTables'; import Smithing from '../../../lib/skilling/skills/smithing'; -import { MoktangTaskOptions } from '../../../lib/types/minions'; -import { increaseBankQuantitesByPercent, itemNameFromID } from '../../../lib/util'; +import type { MoktangTaskOptions } from '../../../lib/types/minions'; +import { itemNameFromID } from '../../../lib/util'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { makeBankImage } from '../../../lib/util/makeBankImage'; import resolveItems from '../../../lib/util/resolveItems'; @@ -31,7 +31,7 @@ export const moktangTask: MinionTask = { await user.incrementKC(MOKTANG_ID, qty); - let loot = new Bank(); + const loot = new Bank(); for (let i = 0; i < qty; i++) { loot.add(MoktangLootTable.roll()); @@ -98,7 +98,7 @@ export const moktangTask: MinionTask = { } } - let str = `${userMention(data.userID)}, ${ + const str = `${userMention(data.userID)}, ${ user.minionName } finished killing ${qty}x Moktang. ${bonusPercent}% bonus loot because of your Mining level. Received ${loot}. diff --git a/src/tasks/minions/bso/monkeyRumbleActivity.ts b/src/tasks/minions/bso/monkeyRumbleActivity.ts index 9ace616b6be..39996a76347 100644 --- a/src/tasks/minions/bso/monkeyRumbleActivity.ts +++ b/src/tasks/minions/bso/monkeyRumbleActivity.ts @@ -1,10 +1,10 @@ -import { randArrItem, roll, Time, uniqueArr } from 'e'; +import { Time, randArrItem, roll, uniqueArr } from 'e'; import { Bank, LootTable } from 'oldschooljs'; import { monkeyHeadImage, monkeyTierOfUser } from '../../../lib/monkeyRumble'; import { incrementMinigameScore } from '../../../lib/settings/settings'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { MonkeyRumbleOptions } from '../../../lib/types/minions'; +import type { MonkeyRumbleOptions } from '../../../lib/types/minions'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { updateBankSetting } from '../../../lib/util/updateBankSetting'; @@ -31,8 +31,8 @@ export const mrTask: MinionTask = { }); const monkeyTier = monkeyTierOfUser(user); - let tierBonusXP = quantity * monkeyTier * 1233; - let strengthBonusXP = quantity * user.skillLevel(SkillsEnum.Strength) * 1000; + const tierBonusXP = quantity * monkeyTier * 1233; + const strengthBonusXP = quantity * user.skillLevel(SkillsEnum.Strength) * 1000; const strXP = Math.floor(tierBonusXP + strengthBonusXP) / 5; let xpStr = await user.addXP({ @@ -57,7 +57,7 @@ export const mrTask: MinionTask = { const tokens = Math.ceil(quantity * (monkeyTier / 1.2)); const loot = new Bank().add('Rumble token', tokens); - let files = []; + const files = []; const specialMonkeys = monkeys.filter(m => m.special); for (const monkey of specialMonkeys) { const unique = rewardTable.roll(); diff --git a/src/tasks/minions/bso/naxxusActivity.ts b/src/tasks/minions/bso/naxxusActivity.ts index c941c28cde6..2c2a46e1285 100644 --- a/src/tasks/minions/bso/naxxusActivity.ts +++ b/src/tasks/minions/bso/naxxusActivity.ts @@ -1,45 +1,13 @@ -import { roll } from 'e'; -import { Bank, LootTable } from 'oldschooljs'; - +import { rollNaxxusLoot } from '../../../lib/bso/naxxus/rollNaxxusLoot'; import { trackLoot } from '../../../lib/lootTrack'; -import { Naxxus, NaxxusLootTable } from '../../../lib/minions/data/killableMonsters/custom/bosses/Naxxus'; +import { Naxxus } from '../../../lib/minions/data/killableMonsters/custom/bosses/Naxxus'; import { addMonsterXP } from '../../../lib/minions/functions'; import announceLoot from '../../../lib/minions/functions/announceLoot'; -import { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; +import type { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { makeBankImage } from '../../../lib/util/makeBankImage'; import { updateBankSetting } from '../../../lib/util/updateBankSetting'; -export function rollNaxxusLoot(quantity: number = 1, cl?: Bank) { - const loot = new Bank(); - loot.add(NaxxusLootTable.roll(quantity)); - - // Handle uniques => Don't give duplicates until log full - const uniqueChance = 150; - // Add new uniques to a dummy CL to support multiple uniques per trip. - const tempClWithNewUniques = cl ? cl.clone() : new Bank(); - for (let i = 0; i < quantity; i++) { - if (roll(uniqueChance)) { - const uniques = [ - { name: 'Dark crystal', weight: 2 }, - { name: 'Abyssal gem', weight: 3 }, - { name: 'Tattered tome', weight: 2 }, - { name: 'Spellbound ring', weight: 3 } - ]; - - const filteredUniques = uniques.filter(u => !tempClWithNewUniques.has(u.name)); - const uniqueTable = filteredUniques.length === 0 ? uniques : filteredUniques; - const lootTable = new LootTable(); - uniqueTable.map(u => lootTable.add(u.name, 1, u.weight)); - - const unique = lootTable.roll(); - tempClWithNewUniques.add(unique); - loot.add(unique); - } - } - return loot; -} - export const naxxusTask: MinionTask = { type: 'Naxxus', async run(data: ActivityTaskOptionsWithQuantity) { diff --git a/src/tasks/minions/bso/nexActivity.ts b/src/tasks/minions/bso/nexActivity.ts index cebd74f2679..c3b9f2d9ded 100644 --- a/src/tasks/minions/bso/nexActivity.ts +++ b/src/tasks/minions/bso/nexActivity.ts @@ -10,7 +10,7 @@ import { addMonsterXP } from '../../../lib/minions/functions'; import announceLoot from '../../../lib/minions/functions/announceLoot'; import { NEX_UNIQUE_DROPRATE, NexMonster } from '../../../lib/nex'; import { TeamLoot } from '../../../lib/simulation/TeamLoot'; -import { BossActivityTaskOptions } from '../../../lib/types/minions'; +import type { BossActivityTaskOptions } from '../../../lib/types/minions'; import { roll } from '../../../lib/util'; import { getNexGearStats } from '../../../lib/util/getNexGearStats'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; @@ -59,7 +59,7 @@ export const nexTask: MinionTask = { } if (teamFailed || percentChance(user.chanceOfDeath)) { - deaths[user.id] = Boolean(deaths[user.id]) ? deaths[user.id] + 1 : 1; + deaths[user.id] = deaths[user.id] ? deaths[user.id] + 1 : 1; // Mark user as dead this kill: deathsThisKill[user.id] = 1; } else { @@ -80,7 +80,7 @@ export const nexTask: MinionTask = { if (!winner) continue; teamsLoot.add(winner, loot); - kcAmounts[winner] = Boolean(kcAmounts[winner]) ? ++kcAmounts[winner] : 1; + kcAmounts[winner] = kcAmounts[winner] ? ++kcAmounts[winner] : 1; } const leaderUser = parsedUsers.find(p => p.id === userID)?.user ?? parsedUsers[0].user; @@ -91,7 +91,7 @@ export const nexTask: MinionTask = { let soloPrevCl = new Bank(); let soloItemsAdded: Bank = new Bank(); - for (let [userID, loot] of teamsLoot.entries()) { + for (const [userID, loot] of teamsLoot.entries()) { const { user } = parsedUsers.find(p => p.id === userID)!; if (!user) continue; let xpStr = ''; @@ -115,7 +115,7 @@ export const nexTask: MinionTask = { const kcToAdd = kcAmounts[user.id]; if (kcToAdd) await user.incrementKC(NexMonster.id, kcToAdd); - const purple = Object.keys(loot.bank).some(id => nexCL.includes(parseInt(id))); + const purple = Object.keys(loot.bank).some(id => nexCL.includes(Number.parseInt(id))); resultStr += `${purple ? Emoji.Purple : ''} **${user} received:** ||${new Bank(loot)}||\n`; @@ -173,7 +173,7 @@ export const nexTask: MinionTask = { user: leaderUser, previousCL: soloPrevCl }) - ).file.attachment; + ).file.attachment; handleTripFinish( leaderUser, channelID, @@ -181,9 +181,9 @@ export const nexTask: MinionTask = { ? `${leaderUser}, ${leaderUser.minionName} died in all their attempts to kill Nex, they apologize and promise to try harder next time.` : `${leaderUser}, ${leaderUser.minionName} finished killing ${quantity} ${ NexMonster.name - }, you died ${deaths[userID] ?? 0} times. Your Nex KC is now ${await leaderUser.getKC( + }, you died ${deaths[userID] ?? 0} times. Your Nex KC is now ${await leaderUser.getKC( NexMonster.id - )}.\n\n${soloXP}`, + )}.\n\n${soloXP}`, image!, data, soloItemsAdded diff --git a/src/tasks/minions/bso/ouraniaDeliveryServiceActivity.ts b/src/tasks/minions/bso/ouraniaDeliveryServiceActivity.ts index 5169180801c..81cf32566cf 100644 --- a/src/tasks/minions/bso/ouraniaDeliveryServiceActivity.ts +++ b/src/tasks/minions/bso/ouraniaDeliveryServiceActivity.ts @@ -7,7 +7,7 @@ import { trackLoot } from '../../../lib/lootTrack'; import { incrementMinigameScore } from '../../../lib/settings/settings'; import { ExoticSeedsTable } from '../../../lib/simulation/sharedTables'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; +import type { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { updateBankSetting } from '../../../lib/util/updateBankSetting'; @@ -44,7 +44,7 @@ export const odsTask: MinionTask = { } }); - let totalXP = level * (quantity * randFloat(39, 41)); + const totalXP = level * (quantity * randFloat(39, 41)); const xpRes = await user.addXP({ skillName: SkillsEnum.Runecraft, amount: totalXP, @@ -53,7 +53,7 @@ export const odsTask: MinionTask = { let str = `${user}, ${user.minionName} finished completing ${quantity}x Ourania deliveries, you received ${tokens} tokens. ${xpRes}`; - let loot = new Bank(); + const loot = new Bank(); for (let i = 0; i < quantity; i++) { loot.add(OuraniaTipTable.roll()); } diff --git a/src/tasks/minions/bso/researchActivity.ts b/src/tasks/minions/bso/researchActivity.ts index 2ff7483fadc..a41c521e963 100644 --- a/src/tasks/minions/bso/researchActivity.ts +++ b/src/tasks/minions/bso/researchActivity.ts @@ -1,5 +1,5 @@ import { researchTask } from '../../../lib/invention/research'; -import { ResearchTaskOptions } from '../../../lib/types/minions'; +import type { ResearchTaskOptions } from '../../../lib/types/minions'; export const researchActivityTask: MinionTask = { type: 'Research', diff --git a/src/tasks/minions/bso/stealingCreationActivity.ts b/src/tasks/minions/bso/stealingCreationActivity.ts index 909e466f0d3..c1374827bee 100644 --- a/src/tasks/minions/bso/stealingCreationActivity.ts +++ b/src/tasks/minions/bso/stealingCreationActivity.ts @@ -2,7 +2,7 @@ import { Bank } from 'oldschooljs'; import { userHasFlappy } from '../../../lib/invention/inventions'; import { incrementMinigameScore } from '../../../lib/settings/settings'; -import { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; +import type { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; export const scTask: MinionTask = { diff --git a/src/tasks/minions/bso/tinkeringWorkshopActivity.ts b/src/tasks/minions/bso/tinkeringWorkshopActivity.ts index 9d69d723459..d840b31d2f2 100644 --- a/src/tasks/minions/bso/tinkeringWorkshopActivity.ts +++ b/src/tasks/minions/bso/tinkeringWorkshopActivity.ts @@ -1,19 +1,19 @@ import { randArrItem, randInt, roll } from 'e'; import { Bank } from 'oldschooljs'; -import { ItemBank } from 'oldschooljs/dist/meta/types'; +import type { ItemBank } from 'oldschooljs/dist/meta/types'; import { inventorOutfit } from '../../../lib/data/CollectionsExport'; import { MaterialBank } from '../../../lib/invention/MaterialBank'; import { incrementMinigameScore } from '../../../lib/settings/settings'; import { ClueTable } from '../../../lib/simulation/sharedTables'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { TinkeringWorkshopOptions } from '../../../lib/types/minions'; +import type { TinkeringWorkshopOptions } from '../../../lib/types/minions'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { userStatsUpdate } from '../../../mahoji/mahojiSettings'; function tinkerLoot(user: MUser, quantity: number) { const loot = new Bank(); - let effectiveBank = user.allItemsOwned.clone(); + const effectiveBank = user.allItemsOwned.clone(); for (let i = 0; i < quantity; i++) { const outfitPieceNotOwned = randArrItem(inventorOutfit.filter(p => !effectiveBank.has(p))); if (roll(6)) { @@ -23,7 +23,6 @@ function tinkerLoot(user: MUser, quantity: number) { if (outfitPieceNotOwned && roll(16)) { loot.add(outfitPieceNotOwned); effectiveBank.add(outfitPieceNotOwned); - continue; } } return loot; @@ -49,16 +48,16 @@ export const twTask: MinionTask = { if (data.material === 'junk') xp = Math.floor(xp / 2); const xpStr = await user.addXP({ amount: xp, skillName: SkillsEnum.Invention, duration }); - await userStatsUpdate(user.id, oldStats => { - return { - tinker_workshop_mats_bank: new MaterialBank(oldStats.tinker_workshop_mats_bank as ItemBank).add( - data.material, - quantity - ).bank, - tworkshop_xp_gained: { - increment: xp - } - }; + + const oldStats = await user.fetchStats({ tinker_workshop_mats_bank: true }); + await userStatsUpdate(user.id, { + tinker_workshop_mats_bank: new MaterialBank(oldStats.tinker_workshop_mats_bank as ItemBank).add( + data.material, + quantity + ).bank, + tworkshop_xp_gained: { + increment: xp + } }); handleTripFinish( diff --git a/src/tasks/minions/bso/turaelsTrialsActivity.ts b/src/tasks/minions/bso/turaelsTrialsActivity.ts index 35ca97278bd..eb6b7b15889 100644 --- a/src/tasks/minions/bso/turaelsTrialsActivity.ts +++ b/src/tasks/minions/bso/turaelsTrialsActivity.ts @@ -1,9 +1,9 @@ import { Bank } from 'oldschooljs'; -import { TuraelsTrialsMethod } from '../../../lib/bso/turaelsTrials'; +import type { TuraelsTrialsMethod } from '../../../lib/bso/turaelsTrials'; import { incrementMinigameScore } from '../../../lib/settings/settings'; import { XPBank } from '../../../lib/structures/Banks'; -import { TuraelsTrialsOptions } from '../../../lib/types/minions'; +import type { TuraelsTrialsOptions } from '../../../lib/types/minions'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { trackClientBankStats, userStatsBankUpdate } from '../../../mahoji/mahojiSettings'; @@ -33,7 +33,7 @@ export function calculateTuraelsTrialsResult({ quantity, method }: { quantity: n export const turaelsTrialsTask: MinionTask = { type: 'TuraelsTrials', async run(data: TuraelsTrialsOptions) { - let { q: quantity, channelID, userID, duration, m: method } = data; + const { q: quantity, channelID, userID, duration, m: method } = data; const user = await mUserFetch(userID); const result = calculateTuraelsTrialsResult({ quantity, method }); diff --git a/src/tasks/minions/bso/vasaMagusActivity.ts b/src/tasks/minions/bso/vasaMagusActivity.ts index 183434e1145..b09848ca134 100644 --- a/src/tasks/minions/bso/vasaMagusActivity.ts +++ b/src/tasks/minions/bso/vasaMagusActivity.ts @@ -1,6 +1,6 @@ import { objectEntries, randArrItem, randInt, roll } from 'e'; import { Bank, Monsters } from 'oldschooljs'; -import Monster from 'oldschooljs/dist/structures/Monster'; +import type Monster from 'oldschooljs/dist/structures/Monster'; import { globalDroprates } from '../../../lib/data/globalDroprates'; import { isDoubleLootActive } from '../../../lib/doubleLoot'; @@ -10,7 +10,7 @@ import { bossKillables } from '../../../lib/minions/data/killableMonsters/bosses import { VasaMagus, VasaMagusLootTable } from '../../../lib/minions/data/killableMonsters/custom/bosses/VasaMagus'; import { addMonsterXP } from '../../../lib/minions/functions'; import announceLoot from '../../../lib/minions/functions/announceLoot'; -import { NewBossOptions } from '../../../lib/types/minions'; +import type { NewBossOptions } from '../../../lib/types/minions'; import { clAdjustedDroprate, getMonster, itemNameFromID } from '../../../lib/util'; import getOSItem from '../../../lib/util/getOSItem'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; @@ -56,8 +56,8 @@ export const vasaTask: MinionTask = { for (let i = 0; i < quantity; i++) { loot.add(VasaMagusLootTable.roll()); if (roll(petDroprate)) loot.add('Voidling'); - let mon = randArrItem(vasaBosses); - let qty = randInt(1, 3); + const mon = randArrItem(vasaBosses); + const qty = randInt(1, 3); lootOf[mon.name] = (lootOf[mon.name] ?? 0) + qty; loot.add(mon.kill(qty, {})); } @@ -72,7 +72,7 @@ export const vasaTask: MinionTask = { loot.multiply(2); } - let pet = user.user.minion_equippedPet; + const pet = user.user.minion_equippedPet; if (pet && kittens.includes(pet) && roll(1)) { await user.update({ minion_equippedPet: getOSItem('Magic kitten').id diff --git a/src/tasks/minions/butlerActivity.ts b/src/tasks/minions/butlerActivity.ts index 6072d9cbc0f..cfcd2eed75f 100644 --- a/src/tasks/minions/butlerActivity.ts +++ b/src/tasks/minions/butlerActivity.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { ButlerActivityTaskOptions } from '../../lib/types/minions'; +import type { ButlerActivityTaskOptions } from '../../lib/types/minions'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; export const butlerTask: MinionTask = { @@ -13,7 +13,7 @@ export const butlerTask: MinionTask = { [plankID]: plankQuantity }); - let str = `${user}, ${user.minionName} finished creating planks, you received ${loot}.`; + const str = `${user}, ${user.minionName} finished creating planks, you received ${loot}.`; await user.addItemsToBank({ items: loot, collectionLog: true }); diff --git a/src/tasks/minions/camdozaalActivity/camdozaalFishingActivity.ts b/src/tasks/minions/camdozaalActivity/camdozaalFishingActivity.ts index af3332cc5ce..596e847d32a 100644 --- a/src/tasks/minions/camdozaalActivity/camdozaalFishingActivity.ts +++ b/src/tasks/minions/camdozaalActivity/camdozaalFishingActivity.ts @@ -3,7 +3,7 @@ import { Bank, LootTable } from 'oldschooljs'; import addSkillingClueToLoot from '../../../lib/minions/functions/addSkillingClueToLoot'; import Fishing from '../../../lib/skilling/skills/fishing'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; +import type { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; import { roll, skillingPetDropRate } from '../../../lib/util'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { makeBankImage } from '../../../lib/util/makeBankImage'; @@ -11,7 +11,7 @@ import { makeBankImage } from '../../../lib/util/makeBankImage'; export const camdozaalFishingTask: MinionTask = { type: 'CamdozaalFishing', async run(data: ActivityTaskOptionsWithQuantity) { - let { userID, channelID, quantity, duration } = data; + const { userID, channelID, quantity, duration } = data; const user = await mUserFetch(userID); const currentFishLevel = user.skillLevel(SkillsEnum.Fishing); @@ -74,7 +74,7 @@ export const camdozaalFishingTask: MinionTask = { // If user has the entire angler outfit, give an extra 2.5% xp bonus if ( user.gear.skilling.hasEquipped( - Object.keys(Fishing.anglerItems).map(i => parseInt(i)), + Object.keys(Fishing.anglerItems).map(i => Number.parseInt(i)), true ) ) { @@ -84,7 +84,7 @@ export const camdozaalFishingTask: MinionTask = { } else { // For each angler item, check if they have it, give its' XP boost for (const [itemID, bonus] of Object.entries(Fishing.anglerItems)) { - if (user.hasEquipped(parseInt(itemID))) { + if (user.hasEquipped(Number.parseInt(itemID))) { const amountToAdd = Math.floor(fishingXpReceived * (bonus / 100)); fishingXpReceived += amountToAdd; bonusXP += amountToAdd; @@ -107,7 +107,7 @@ export const camdozaalFishingTask: MinionTask = { } // Add clue scrolls - let clueScrollChance = guppy.clueScrollChance!; + const clueScrollChance = guppy.clueScrollChance!; addSkillingClueToLoot(user, SkillsEnum.Fishing, quantity, clueScrollChance, loot); // Heron Pet roll diff --git a/src/tasks/minions/camdozaalActivity/camdozaalMiningActivity.ts b/src/tasks/minions/camdozaalActivity/camdozaalMiningActivity.ts index b7a67c98638..6688067a711 100644 --- a/src/tasks/minions/camdozaalActivity/camdozaalMiningActivity.ts +++ b/src/tasks/minions/camdozaalActivity/camdozaalMiningActivity.ts @@ -3,7 +3,7 @@ import { Bank, LootTable } from 'oldschooljs'; import addSkillingClueToLoot from '../../../lib/minions/functions/addSkillingClueToLoot'; import Mining from '../../../lib/skilling/skills/mining'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; +import type { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; import { roll, skillingPetDropRate } from '../../../lib/util'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { makeBankImage } from '../../../lib/util/makeBankImage'; @@ -11,7 +11,7 @@ import { makeBankImage } from '../../../lib/util/makeBankImage'; export const camdozaalMiningTask: MinionTask = { type: 'CamdozaalMining', async run(data: ActivityTaskOptionsWithQuantity) { - let { quantity, userID, channelID, duration } = data; + const { quantity, userID, channelID, duration } = data; const user = await mUserFetch(userID); const camdozaalMine = Mining.CamdozaalMine; @@ -60,7 +60,7 @@ export const camdozaalMiningTask: MinionTask = { // If user has the entire prospector outfit, give an extra 2.5% xp bonus if ( user.gear.skilling.hasEquipped( - Object.keys(Mining.prospectorItems).map(i => parseInt(i)), + Object.keys(Mining.prospectorItems).map(i => Number.parseInt(i)), true ) ) { @@ -70,7 +70,7 @@ export const camdozaalMiningTask: MinionTask = { } else { // For each prospector item, check if they have it, give its' XP boost for (const [itemID, bonus] of Object.entries(Mining.prospectorItems)) { - if (user.hasEquipped(parseInt(itemID))) { + if (user.hasEquipped(Number.parseInt(itemID))) { const amountToAdd = Math.floor(miningXpReceived * (bonus / 100)); miningXpReceived += amountToAdd; bonusXP += amountToAdd; @@ -93,7 +93,7 @@ export const camdozaalMiningTask: MinionTask = { } // Add clue scrolls - let clueScrollChance = Mining.CamdozaalMine.clueScrollChance!; + const clueScrollChance = Mining.CamdozaalMine.clueScrollChance!; addSkillingClueToLoot(user, SkillsEnum.Fishing, quantity, clueScrollChance, loot); // Rock golem roll diff --git a/src/tasks/minions/camdozaalActivity/camdozaalSmithingActivity.ts b/src/tasks/minions/camdozaalActivity/camdozaalSmithingActivity.ts index 0916cd488a1..9b8d53faf24 100644 --- a/src/tasks/minions/camdozaalActivity/camdozaalSmithingActivity.ts +++ b/src/tasks/minions/camdozaalActivity/camdozaalSmithingActivity.ts @@ -1,7 +1,7 @@ import { Bank, LootTable } from 'oldschooljs'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; +import type { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { makeBankImage } from '../../../lib/util/makeBankImage'; @@ -20,7 +20,7 @@ const barroniteDepositLootTable = new LootTable() export const camdozaalSmithingTask: MinionTask = { type: 'CamdozaalSmithing', async run(data: ActivityTaskOptionsWithQuantity) { - let { quantity, userID, channelID, duration } = data; + const { quantity, userID, channelID, duration } = data; const user = await mUserFetch(userID); // Count loot received during trip @@ -30,7 +30,7 @@ export const camdozaalSmithingTask: MinionTask = { } // Add up the xp from the trip - let smithingXpReceived = quantity * 30; + const smithingXpReceived = quantity * 30; // Add xp to user const xpRes = await user.addXP({ @@ -41,7 +41,7 @@ export const camdozaalSmithingTask: MinionTask = { }); // Trip finish message - let str = `${user}, ${user.minionName} finished smithing in Camdozaal! ${xpRes}`; + const str = `${user}, ${user.minionName} finished smithing in Camdozaal! ${xpRes}`; // Give the user the items from the trip const { previousCL, itemsAdded } = await transactItems({ diff --git a/src/tasks/minions/castingActivity.ts b/src/tasks/minions/castingActivity.ts index 7cee3071d75..49a0eba7477 100644 --- a/src/tasks/minions/castingActivity.ts +++ b/src/tasks/minions/castingActivity.ts @@ -1,12 +1,12 @@ import { Castables } from '../../lib/skilling/skills/magic/castables'; import { SkillsEnum } from '../../lib/skilling/types'; -import { CastingActivityTaskOptions } from '../../lib/types/minions'; +import type { CastingActivityTaskOptions } from '../../lib/types/minions'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; export const castingTask: MinionTask = { type: 'Casting', async run(data: CastingActivityTaskOptions) { - let { spellID, quantity, userID, channelID, duration } = data; + const { spellID, quantity, userID, channelID, duration } = data; const user = await mUserFetch(userID); const spell = Castables.find(i => i.id === spellID)!; @@ -51,7 +51,7 @@ export const castingTask: MinionTask = { }); } - let str = `${user}, ${user.minionName} finished casting ${quantity}x ${spell.name}, you received ${ + const str = `${user}, ${user.minionName} finished casting ${quantity}x ${spell.name}, you received ${ loot ?? 'no items' }. ${xpRes} ${craftXpRes}${prayerXpRes}`; diff --git a/src/tasks/minions/clueActivity.ts b/src/tasks/minions/clueActivity.ts index c4f0f40f2ba..fbf52080ec1 100644 --- a/src/tasks/minions/clueActivity.ts +++ b/src/tasks/minions/clueActivity.ts @@ -1,10 +1,9 @@ -import { randInt, Time } from 'e'; +import { Time, randInt, roll } from 'e'; import { Bank } from 'oldschooljs'; import LootTable from 'oldschooljs/dist/structures/LootTable'; import { ClueTiers } from '../../lib/clues/clueTiers'; -import { ClueActivityTaskOptions } from '../../lib/types/minions'; -import { roll } from '../../lib/util'; +import type { ClueActivityTaskOptions } from '../../lib/types/minions'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; import { updateBankSetting } from '../../lib/util/updateBankSetting'; import { userStatsBankUpdate } from '../../mahoji/mahojiSettings'; @@ -28,7 +27,7 @@ const possibleFound = new LootTable() export const clueTask: MinionTask = { type: 'ClueCompletion', async run(data: ClueActivityTaskOptions) { - const { clueID, userID, channelID, quantity, duration } = data; + const { ci: clueID, userID, channelID, q: quantity, duration } = data; const clueTier = ClueTiers.find(mon => mon.id === clueID)!; const user = await mUserFetch(userID); @@ -38,10 +37,10 @@ export const clueTask: MinionTask = { quantity > 1 ? 's' : '' } in your bank. You can open this casket using \`/open name:${clueTier.name}\``; - let loot = new Bank().add(clueTier.id, quantity); + const loot = new Bank().add(clueTier.id, quantity); if (user.usingPet('Zippy') && duration > Time.Minute * 5) { - let bonusLoot = new Bank(); + const bonusLoot = new Bank(); const numberOfMinutes = Math.floor(duration / Time.Minute); for (let i = 0; i < numberOfMinutes / randInt(5, 10); i++) { diff --git a/src/tasks/minions/collectingActivity.ts b/src/tasks/minions/collectingActivity.ts index f5351b9de93..8f9f1557f1f 100644 --- a/src/tasks/minions/collectingActivity.ts +++ b/src/tasks/minions/collectingActivity.ts @@ -2,15 +2,15 @@ import { Time } from 'e'; import { Bank } from 'oldschooljs'; import { MorytaniaDiary, userhasDiaryTier } from '../../lib/diaries'; -import { CollectingOptions } from '../../lib/types/minions'; +import type { CollectingOptions } from '../../lib/types/minions'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; import { updateBankSetting } from '../../lib/util/updateBankSetting'; -import { collectables } from '../../mahoji/lib/abstracted_commands/collectCommand'; +import { collectables } from '../../mahoji/lib/collectables'; export const collectingTask: MinionTask = { type: 'Collecting', async run(data: CollectingOptions) { - let { collectableID, quantity, userID, channelID, duration } = data; + const { collectableID, quantity, userID, channelID, duration } = data; const user = await mUserFetch(userID); const collectable = collectables.find(c => c.item.id === collectableID)!; @@ -28,11 +28,6 @@ export const collectingTask: MinionTask = { collectionLog: true, itemsToAdd: loot }); - await transactItems({ - userID: user.id, - collectionLog: true, - itemsToAdd: loot - }); let str = `${user}, ${user.minionName} finished collecting ${totalQuantity}x ${ collectable.item.name diff --git a/src/tasks/minions/colosseumActivity.ts b/src/tasks/minions/colosseumActivity.ts new file mode 100644 index 00000000000..479a12821f9 --- /dev/null +++ b/src/tasks/minions/colosseumActivity.ts @@ -0,0 +1,130 @@ +import { randArrItem } from 'e'; +import { Bank } from 'oldschooljs'; +import type { ItemBank } from 'oldschooljs/dist/meta/types'; + +import { ColosseumWaveBank, colosseumWaves } from '../../lib/colosseum'; +import { refundChargeBank } from '../../lib/degradeableItems'; +import { trackLoot } from '../../lib/lootTrack'; +import { incrementMinigameScore } from '../../lib/settings/minigames'; +import { ChargeBank } from '../../lib/structures/Bank'; +import type { ColoTaskOptions } from '../../lib/types/minions'; +import { handleTripFinish } from '../../lib/util/handleTripFinish'; +import { makeBankImage } from '../../lib/util/makeBankImage'; +import resolveItems from '../../lib/util/resolveItems'; +import { updateBankSetting } from '../../lib/util/updateBankSetting'; +import { userStatsBankUpdate, userStatsUpdate } from '../../mahoji/mahojiSettings'; + +const sunfireItems = resolveItems(['Sunfire fanatic helm', 'Sunfire fanatic cuirass', 'Sunfire fanatic chausses']); + +export const colosseumTask: MinionTask = { + type: 'Colosseum', + async run(data: ColoTaskOptions) { + const { + channelID, + userID, + loot: possibleLoot, + diedAt, + maxGlory, + scytheCharges, + venatorBowCharges, + bloodFuryCharges + } = data; + const user = await mUserFetch(userID); + + const newKCs = new ColosseumWaveBank(); + for (let i = 0; i < (diedAt ? diedAt - 1 : 12); i++) { + newKCs.add(i + 1); + } + const stats = await user.fetchStats({ colo_kc_bank: true, colo_max_glory: true }); + for (const [key, value] of Object.entries(stats.colo_kc_bank as ItemBank)) + newKCs.add(Number.parseInt(key), value); + await userStatsUpdate(user.id, { colo_kc_bank: newKCs._bank }); + const newKCsStr = `${newKCs + .entries() + .map(([kc, amount]) => `Wave ${kc}: ${amount} KC`) + .join(', ')}`; + + let scytheRefund = 0; + let venatorBowRefund = 0; + let bloodFuryRefund = 0; + + const newWaveKcStr = !diedAt || diedAt > 1 ? `New wave KCs: ${newKCsStr}.` : 'No new KCs.'; + if (diedAt) { + const wave = colosseumWaves.find(i => i.waveNumber === diedAt)!; + + let str = `${user}, you died on wave ${diedAt} to ${randArrItem([ + ...(wave?.reinforcements ?? []), + ...wave.enemies + ])}, and received no loot. ${newWaveKcStr}`; + + // Calculate refund for unused charges + const completionPercentage = (diedAt - 1) / 12; + if (scytheCharges > 0) scytheRefund = Math.ceil(scytheCharges * (1 - completionPercentage)); + if (venatorBowCharges > 0) venatorBowRefund = Math.ceil(venatorBowCharges * (1 - completionPercentage)); + if (bloodFuryCharges > 0) bloodFuryRefund = Math.ceil(bloodFuryCharges * (1 - completionPercentage)); + + const chargeBank = new ChargeBank(); + if (scytheRefund > 0) chargeBank.add('scythe_of_vitur_charges', scytheRefund); + if (venatorBowRefund > 0) chargeBank.add('venator_bow_charges', venatorBowRefund); + if (bloodFuryRefund > 0) chargeBank.add('blood_fury_charges', bloodFuryRefund); + + if (chargeBank.length() > 0) { + const refundResults = await refundChargeBank(user, chargeBank); + + const refundMessages = refundResults + .map(result => `${result.userMessage} Total charges: ${result.totalCharges}.`) + .join('\n'); + + str += `\n${refundMessages}`; + } + + return handleTripFinish(user, channelID, str, undefined, data, null); + } + + await incrementMinigameScore(user.id, 'colosseum'); + + const loot = new Bank().add(possibleLoot); + + const missingItems = sunfireItems.filter(id => !user.cl.has(id)); + const itemsTheyHave = sunfireItems.filter(id => user.cl.has(id)); + if (missingItems.length > 0) { + for (const item of sunfireItems) { + if (loot.has(item) && itemsTheyHave.includes(item)) { + loot.remove(item); + loot.add(randArrItem(missingItems)); + } + } + } + + const { previousCL } = await user.addItemsToBank({ items: loot, collectionLog: true }); + + await updateBankSetting('colo_loot', loot); + await userStatsBankUpdate(user, 'colo_loot', loot); + await trackLoot({ + totalLoot: loot, + id: 'colo', + type: 'Minigame', + changeType: 'loot', + duration: data.duration, + kc: 1, + users: [ + { + id: user.id, + loot, + duration: data.duration + } + ] + }); + + let str = `${user}, you completed the Colosseum! You received: ${loot}. ${newWaveKcStr}`; + + if (!stats.colo_max_glory || maxGlory > stats.colo_max_glory) { + await userStatsUpdate(user.id, { colo_max_glory: maxGlory }); + str += ` Your new max glory is ${maxGlory}!`; + } + + const image = await makeBankImage({ bank: loot, title: 'Colosseum Loot', user, previousCL }); + + return handleTripFinish(user, channelID, str, image.file.attachment, data, loot); + } +}; diff --git a/src/tasks/minions/combatRingActivity.ts b/src/tasks/minions/combatRingActivity.ts new file mode 100644 index 00000000000..737d82c8168 --- /dev/null +++ b/src/tasks/minions/combatRingActivity.ts @@ -0,0 +1,55 @@ +import { Bank } from 'oldschooljs'; + +import type { ActivityTaskOptionsWithNoChanges } from '../../lib/types/minions'; +import { handleTripFinish } from '../../lib/util/handleTripFinish'; + +export const combatRingTask: MinionTask = { + type: 'CombatRing', + async run(data: ActivityTaskOptionsWithNoChanges) { + const { channelID, userID } = data; + const user = await mUserFetch(userID); + + const loot = new Bank({ + 'Shayzien boots (1)': 1, + 'Shayzien gloves (1)': 1, + 'Shayzien greaves (1)': 1, + 'Shayzien helm (1)': 1, + 'Shayzien platebody (1)': 1, + 'Shayzien boots (2)': 1, + 'Shayzien gloves (2)': 1, + 'Shayzien greaves (2)': 1, + 'Shayzien helm (2)': 1, + 'Shayzien platebody (2)': 1, + 'Shayzien boots (3)': 1, + 'Shayzien gloves (3)': 1, + 'Shayzien greaves (3)': 1, + 'Shayzien helm (3)': 1, + 'Shayzien platebody (3)': 1, + 'Shayzien boots (4)': 1, + 'Shayzien gloves (4)': 1, + 'Shayzien greaves (4)': 1, + 'Shayzien helm (4)': 1, + 'Shayzien platebody (4)': 1, + 'Shayzien boots (5)': 1, + 'Shayzien gloves (5)': 1, + 'Shayzien greaves (5)': 1, + 'Shayzien helm (5)': 1, + 'Shayzien body (5)': 1 + }); + + await transactItems({ + userID: user.id, + collectionLog: true, + itemsToAdd: loot + }); + + handleTripFinish( + user, + channelID, + `${user}, ${user.minionName} finished the Combat Ring, and received ${loot}.`, + undefined, + data, + loot + ); + } +}; diff --git a/src/tasks/minions/constructionActivity.ts b/src/tasks/minions/constructionActivity.ts index 4575c4fefc3..fa66a22d0d0 100644 --- a/src/tasks/minions/constructionActivity.ts +++ b/src/tasks/minions/constructionActivity.ts @@ -1,10 +1,10 @@ -import { calcPercentOfNum } from 'e'; +import { calcPercentOfNum, roll } from 'e'; import { Bank } from 'oldschooljs'; import Constructables from '../../lib/skilling/skills/construction/constructables'; import { SkillsEnum } from '../../lib/skilling/types'; -import { ConstructionActivityTaskOptions } from '../../lib/types/minions'; -import { calcBabyYagaHouseDroprate, roll } from '../../lib/util'; +import type { ConstructionActivityTaskOptions } from '../../lib/types/minions'; +import { calcBabyYagaHouseDroprate } from '../../lib/util'; import { calcConBonusXP } from '../../lib/util/calcConBonusXP'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; diff --git a/src/tasks/minions/cookingActivity.ts b/src/tasks/minions/cookingActivity.ts index 78f955c231b..d91880b70b1 100644 --- a/src/tasks/minions/cookingActivity.ts +++ b/src/tasks/minions/cookingActivity.ts @@ -1,3 +1,4 @@ +import { roll } from 'e'; import { Bank } from 'oldschooljs'; import { MIN_LENGTH_FOR_PET } from '../../lib/constants'; @@ -6,8 +7,8 @@ import { KourendKebosDiary, userhasDiaryTier } from '../../lib/diaries'; import calcBurntCookables from '../../lib/skilling/functions/calcBurntCookables'; import Cooking from '../../lib/skilling/skills/cooking/cooking'; import { SkillsEnum } from '../../lib/skilling/types'; -import { CookingActivityTaskOptions } from '../../lib/types/minions'; -import { clAdjustedDroprate, roll } from '../../lib/util'; +import type { CookingActivityTaskOptions } from '../../lib/types/minions'; +import { clAdjustedDroprate } from '../../lib/util'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; export const cookingTask: MinionTask = { diff --git a/src/tasks/minions/craftingActivity.ts b/src/tasks/minions/craftingActivity.ts index 2a4c58c6d0c..54f91c577c5 100644 --- a/src/tasks/minions/craftingActivity.ts +++ b/src/tasks/minions/craftingActivity.ts @@ -3,7 +3,7 @@ import { Bank } from 'oldschooljs'; import { Craftables } from '../../lib/skilling/skills/crafting/craftables'; import { SkillsEnum } from '../../lib/skilling/types'; -import { CraftingActivityTaskOptions } from '../../lib/types/minions'; +import type { CraftingActivityTaskOptions } from '../../lib/types/minions'; import { randFloat } from '../../lib/util'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; @@ -20,7 +20,7 @@ export const craftingTask: MinionTask = { if (item.outputMultiple) { sets = ' sets of'; } - let quantityToGive = item.outputMultiple ? quantity * item.outputMultiple : quantity; + const quantityToGive = item.outputMultiple ? quantity * item.outputMultiple : quantity; const loot = new Bank(); let crushed = 0; diff --git a/src/tasks/minions/cutLeapingFishActivity.ts b/src/tasks/minions/cutLeapingFishActivity.ts index 8af6667aa07..c1309df9e57 100644 --- a/src/tasks/minions/cutLeapingFishActivity.ts +++ b/src/tasks/minions/cutLeapingFishActivity.ts @@ -3,13 +3,13 @@ import { Bank } from 'oldschooljs'; import LeapingFish from '../../lib/skilling/skills/cooking/leapingFish'; import { SkillsEnum } from '../../lib/skilling/types'; -import { CutLeapingFishActivityTaskOptions } from '../../lib/types/minions'; +import type { CutLeapingFishActivityTaskOptions } from '../../lib/types/minions'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; export const cutLeapingFishTask: MinionTask = { type: 'CutLeapingFish', async run(data: CutLeapingFishActivityTaskOptions) { - let { fishID, userID, channelID, quantity, duration } = data; + const { fishID, userID, channelID, quantity, duration } = data; const user = await mUserFetch(userID); const barbarianFish = LeapingFish.find(LeapingFish => LeapingFish.item.id === fishID)!; @@ -59,7 +59,7 @@ export const cutLeapingFishTask: MinionTask = { } } - let loot = new Bank(); + const loot = new Bank(); loot.add('Roe', roeCreated); loot.add('Caviar', caviarCreated); @@ -75,7 +75,7 @@ export const cutLeapingFishTask: MinionTask = { duration }); - let str = `${user}, ${user.minionName} finished cutting ${quantity}x ${barbarianFish.item.name}. ${xpRes}\n\n You received: ${loot}.`; + const str = `${user}, ${user.minionName} finished cutting ${quantity}x ${barbarianFish.item.name}. ${xpRes}\n\n You received: ${loot}.`; await transactItems({ userID: user.id, diff --git a/src/tasks/minions/darkAltarActivity.ts b/src/tasks/minions/darkAltarActivity.ts index b4c2fcd3de5..d59faa11226 100644 --- a/src/tasks/minions/darkAltarActivity.ts +++ b/src/tasks/minions/darkAltarActivity.ts @@ -4,7 +4,7 @@ import { Bank } from 'oldschooljs'; import { darkAltarRunes } from '../../lib/minions/functions/darkAltarCommand'; import { bloodEssence, raimentBonus } from '../../lib/skilling/functions/calcsRunecrafting'; import { SkillsEnum } from '../../lib/skilling/types'; -import { DarkAltarOptions } from '../../lib/types/minions'; +import type { DarkAltarOptions } from '../../lib/types/minions'; import { skillingPetDropRate } from '../../lib/util'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; @@ -53,7 +53,7 @@ export const darkAltarTask: MinionTask = { runeQuantity += bonusBlood; } - let loot = new Bank().add(runeData.item.id, runeQuantity); + const loot = new Bank().add(runeData.item.id, runeQuantity); const { petDropRate } = skillingPetDropRate(user, SkillsEnum.Runecraft, runeData.petChance); for (let i = 0; i < quantity; i++) { if (roll(petDropRate)) { diff --git a/src/tasks/minions/enchantingActivity.ts b/src/tasks/minions/enchantingActivity.ts index 185a3d9c5aa..8f997bef2f4 100644 --- a/src/tasks/minions/enchantingActivity.ts +++ b/src/tasks/minions/enchantingActivity.ts @@ -1,12 +1,12 @@ import { Enchantables } from '../../lib/skilling/skills/magic/enchantables'; import { SkillsEnum } from '../../lib/skilling/types'; -import { EnchantingActivityTaskOptions } from '../../lib/types/minions'; +import type { EnchantingActivityTaskOptions } from '../../lib/types/minions'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; export const enchantingTask: MinionTask = { type: 'Enchanting', async run(data: EnchantingActivityTaskOptions) { - let { itemID, quantity, userID, channelID, duration } = data; + const { itemID, quantity, userID, channelID, duration } = data; const user = await mUserFetch(userID); const enchantable = Enchantables.find(fletchable => fletchable.id === itemID)!; @@ -25,7 +25,7 @@ export const enchantingTask: MinionTask = { itemsToAdd: loot }); - let str = `${user}, ${user.minionName} finished enchanting ${quantity}x ${enchantable.name}, you received ${loot}. ${xpRes}`; + const str = `${user}, ${user.minionName} finished enchanting ${quantity}x ${enchantable.name}, you received ${loot}. ${xpRes}`; handleTripFinish(user, channelID, str, undefined, data, loot); } diff --git a/src/tasks/minions/farmingActivity.ts b/src/tasks/minions/farmingActivity.ts index 368c5ae3dd2..e53fc76a019 100644 --- a/src/tasks/minions/farmingActivity.ts +++ b/src/tasks/minions/farmingActivity.ts @@ -1,29 +1,24 @@ -import { randInt, Time } from 'e'; +import { Time, randInt } from 'e'; import { Bank, Monsters } from 'oldschooljs'; +import { increaseBankQuantitesByPercent } from '@oldschoolgg/toolkit'; import { MysteryBoxes } from '../../lib/bsoOpenables'; import { combatAchievementTripEffect } from '../../lib/combat_achievements/combatAchievements'; import { BitField } from '../../lib/constants'; -import { inventionBoosts, InventionID, inventionItemBoost } from '../../lib/invention/inventions'; -import { PatchTypes } from '../../lib/minions/farming'; -import { FarmingContract } from '../../lib/minions/farming/types'; -import { prisma } from '../../lib/settings/prisma'; +import { InventionID, inventionBoosts, inventionItemBoost } from '../../lib/invention/inventions'; +import type { PatchTypes } from '../../lib/minions/farming'; +import type { FarmingContract } from '../../lib/minions/farming/types'; + import { calcVariableYield } from '../../lib/skilling/functions/calcsFarming'; +import { getFarmingInfoFromUser } from '../../lib/skilling/functions/getFarmingInfo'; import Farming, { plants } from '../../lib/skilling/skills/farming'; -import { Plant, SkillsEnum } from '../../lib/skilling/types'; -import { FarmingActivityTaskOptions, MonsterActivityTaskOptions } from '../../lib/types/minions'; -import { - assert, - clAdjustedDroprate, - increaseBankQuantitesByPercent, - itemID, - itemNameFromID, - roll, - skillingPetDropRate -} from '../../lib/util'; +import { type Plant, SkillsEnum } from '../../lib/skilling/types'; +import type { FarmingActivityTaskOptions, MonsterActivityTaskOptions } from '../../lib/types/minions'; +import { assert, clAdjustedDroprate, itemNameFromID, roll, skillingPetDropRate } from '../../lib/util'; import chatHeadImage from '../../lib/util/chatHeadImage'; import { getFarmingKeyFromName } from '../../lib/util/farmingHelpers'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; +import itemID from '../../lib/util/itemID'; import { updateBankSetting } from '../../lib/util/updateBankSetting'; import { sendToChannelID } from '../../lib/util/webhook'; import { userStatsBankUpdate } from '../../mahoji/mahojiSettings'; @@ -334,7 +329,7 @@ export const farmingTask: MinionTask = { const uncleanedHerbLoot = new Bank().add(plantToHarvest.outputCrop, cropYield); await user.addItemsToCollectionLog(uncleanedHerbLoot); const cleanedHerbLoot = new Bank().add(plantToHarvest.cleanHerbCrop, cropYield); - await userStatsBankUpdate(user.id, 'herbs_cleaned_while_farming_bank', cleanedHerbLoot); + await userStatsBankUpdate(user, 'herbs_cleaned_while_farming_bank', cleanedHerbLoot); } if (plantToHarvest.name === 'Limpwurt') { @@ -459,8 +454,8 @@ export const farmingTask: MinionTask = { farmingLevel: currentFarmingLevel }); const fakeMonsterTaskOptions: MonsterActivityTaskOptions = { - monsterID: Monsters.Hespori.id, - quantity: patchType.lastQuantity, + mi: Monsters.Hespori.id, + q: patchType.lastQuantity, type: 'MonsterKilling', userID: user.id, duration: data.duration, @@ -594,7 +589,7 @@ export const farmingTask: MinionTask = { // Give boxes for planting when harvesting if (planting && plant.name === 'Mysterious tree') { for (let j = 0; j < quantity; j++) { - let upper = randInt(1, 2); + const upper = randInt(1, 2); for (let i = 0; i < upper; i++) { loot.add(MysteryBoxes.roll()); } @@ -603,7 +598,7 @@ export const farmingTask: MinionTask = { // Give the boxes for harvesting during a harvest if (alivePlants && plantToHarvest.name === 'Mysterious tree') { for (let j = 0; j < alivePlants; j++) { - let upper = randInt(1, 3); + const upper = randInt(1, 3); for (let i = 0; i < upper; i++) { loot.add(MysteryBoxes.roll()); } @@ -627,7 +622,7 @@ export const farmingTask: MinionTask = { collectionLog: true, itemsToAdd: loot }); - await userStatsBankUpdate(user.id, 'farming_harvest_loot_bank', loot); + await userStatsBankUpdate(user, 'farming_harvest_loot_bank', loot); if (pid) { await prisma.farmedCrop.update({ where: { @@ -641,6 +636,15 @@ export const farmingTask: MinionTask = { const seedPackCount = loot.amount('Seed pack'); + const hasFive = getFarmingInfoFromUser(user.user).patches.spirit.lastQuantity >= 5; + if (hasFive && !user.bitfield.includes(BitField.GrewFiveSpiritTrees)) { + await user.update({ + bitfield: { + push: BitField.GrewFiveSpiritTrees + } + }); + } + return handleTripFinish( user, channelID, @@ -655,7 +659,7 @@ export const farmingTask: MinionTask = { contractsCompleted + 1 } farming contracts.`, head: 'jane' - }) + }) : undefined, data, loot diff --git a/src/tasks/minions/firemakingActivity.ts b/src/tasks/minions/firemakingActivity.ts index 86394680d33..351a2e02bea 100644 --- a/src/tasks/minions/firemakingActivity.ts +++ b/src/tasks/minions/firemakingActivity.ts @@ -1,6 +1,6 @@ import Firemaking from '../../lib/skilling/skills/firemaking'; import { SkillsEnum } from '../../lib/skilling/types'; -import { FiremakingActivityTaskOptions } from '../../lib/types/minions'; +import type { FiremakingActivityTaskOptions } from '../../lib/types/minions'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; export const firemakingTask: MinionTask = { @@ -17,7 +17,7 @@ export const firemakingTask: MinionTask = { // If they have the entire pyromancer outfit, give an extra 0.5% xp bonus if ( user.hasEquippedOrInBank( - Object.keys(Firemaking.pyromancerItems).map(i => parseInt(i)), + Object.keys(Firemaking.pyromancerItems).map(i => Number.parseInt(i)), 'every' ) ) { @@ -27,7 +27,7 @@ export const firemakingTask: MinionTask = { } else { // For each pyromancer item, check if they have it, give its' XP boost if so. for (const [itemID, bonus] of Object.entries(Firemaking.pyromancerItems)) { - if (user.hasEquippedOrInBank(parseInt(itemID))) { + if (user.hasEquippedOrInBank(Number.parseInt(itemID))) { const amountToAdd = Math.floor(xpReceived * (bonus / 100)); xpReceived += amountToAdd; bonusXP += amountToAdd; diff --git a/src/tasks/minions/fishingActivity.ts b/src/tasks/minions/fishingActivity.ts index 871ca52df36..dee084f09c5 100644 --- a/src/tasks/minions/fishingActivity.ts +++ b/src/tasks/minions/fishingActivity.ts @@ -1,4 +1,4 @@ -import { calcPercentOfNum, percentChance, randInt } from 'e'; +import { calcPercentOfNum, percentChance, randInt, roll } from 'e'; import { Bank } from 'oldschooljs'; import { z } from 'zod'; @@ -8,8 +8,8 @@ import addSkillingClueToLoot from '../../lib/minions/functions/addSkillingClueTo import { Cookables } from '../../lib/skilling/skills/cooking/cooking'; import Fishing from '../../lib/skilling/skills/fishing'; import { SkillsEnum } from '../../lib/skilling/types'; -import { FishingActivityTaskOptions } from '../../lib/types/minions'; -import { clAdjustedDroprate, roll, skillingPetDropRate } from '../../lib/util'; +import type { FishingActivityTaskOptions } from '../../lib/types/minions'; +import { clAdjustedDroprate, skillingPetDropRate } from '../../lib/util'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; import itemID from '../../lib/util/itemID'; import { anglerBoostPercent } from '../../mahoji/mahojiSettings'; @@ -42,7 +42,7 @@ export const fishingTask: MinionTask = { quantity: z.number().min(1) }), async run(data: FishingActivityTaskOptions) { - let { fishID, quantity, userID, channelID, duration } = data; + const { fishID, quantity, userID, channelID, duration } = data; const user = await mUserFetch(userID); const { blessingEquipped, blessingChance } = radasBlessing(user); @@ -104,7 +104,7 @@ export const fishingTask: MinionTask = { skillName: SkillsEnum.Agility, amount: agilityXpReceived, duration - }) + }) : ''; xpRes += strengthXpReceived > 0 @@ -112,7 +112,7 @@ export const fishingTask: MinionTask = { skillName: SkillsEnum.Strength, amount: strengthXpReceived, duration - }) + }) : ''; let str = `${user}, ${user.minionName} finished fishing ${quantity} ${fish.name}. ${xpRes}`; @@ -121,7 +121,7 @@ export const fishingTask: MinionTask = { const baseKarambwanji = 1 + Math.floor(user.skillLevel(SkillsEnum.Fishing) / 5); let baseMinnow = [10, 10]; for (const [level, quantities] of Object.entries(minnowQuantity).reverse()) { - if (user.skillLevel(SkillsEnum.Fishing) >= parseInt(level)) { + if (user.skillLevel(SkillsEnum.Fishing) >= Number.parseInt(level)) { baseMinnow = quantities; break; } @@ -141,7 +141,7 @@ export const fishingTask: MinionTask = { } } - let loot = new Bank({ + const loot = new Bank({ [fish.id]: lootQuantity }); diff --git a/src/tasks/minions/fletchingActivity.ts b/src/tasks/minions/fletchingActivity.ts index 0d2230f5693..c10354ddef7 100644 --- a/src/tasks/minions/fletchingActivity.ts +++ b/src/tasks/minions/fletchingActivity.ts @@ -2,13 +2,13 @@ import { Bank } from 'oldschooljs'; import Fletching from '../../lib/skilling/skills/fletching/'; import { SkillsEnum } from '../../lib/skilling/types'; -import { FletchingActivityTaskOptions } from '../../lib/types/minions'; +import type { FletchingActivityTaskOptions } from '../../lib/types/minions'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; export const fletchingTask: MinionTask = { type: 'Fletching', async run(data: FletchingActivityTaskOptions) { - let { fletchableName, quantity, userID, channelID, duration } = data; + const { fletchableName, quantity, userID, channelID, duration } = data; const user = await mUserFetch(userID); const fletchableItem = Fletching.Fletchables.find(fletchable => fletchable.name === fletchableName)!; @@ -35,7 +35,7 @@ export const fletchingTask: MinionTask = { if (fletchableItem.outputMultiple) { sets = ' sets of'; } - let quantityToGive = fletchableItem.outputMultiple ? quantity * fletchableItem.outputMultiple : quantity; + const quantityToGive = fletchableItem.outputMultiple ? quantity * fletchableItem.outputMultiple : quantity; const loot = new Bank({ [fletchableItem.id]: quantityToGive }); await transactItems({ diff --git a/src/tasks/minions/gloryChargingActivity.ts b/src/tasks/minions/gloryChargingActivity.ts index 47b9093faf5..ec797ca95d4 100644 --- a/src/tasks/minions/gloryChargingActivity.ts +++ b/src/tasks/minions/gloryChargingActivity.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import { Events, gloriesInventorySize } from '../../lib/constants'; -import { ActivityTaskOptionsWithQuantity } from '../../lib/types/minions'; +import type { ActivityTaskOptionsWithQuantity } from '../../lib/types/minions'; import { roll } from '../../lib/util'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; @@ -11,7 +11,7 @@ export const gloryChargingTask: MinionTask = { const { quantity, userID, channelID } = data; const user = await mUserFetch(userID); let deaths = 0; - let loot = new Bank(); + const loot = new Bank(); for (let i = 0; i < quantity; i++) { if (roll(99)) { deaths++; diff --git a/src/tasks/minions/groupMonsterActivity.ts b/src/tasks/minions/groupMonsterActivity.ts index 54c6d7f1bf5..11882a6876e 100644 --- a/src/tasks/minions/groupMonsterActivity.ts +++ b/src/tasks/minions/groupMonsterActivity.ts @@ -1,4 +1,4 @@ -import { noOp, randArrItem, roll, Time } from 'e'; +import { Time, noOp, randArrItem, roll } from 'e'; import { Bank } from 'oldschooljs'; import { MysteryBoxes } from '../../lib/bsoOpenables'; @@ -7,20 +7,20 @@ import killableMonsters from '../../lib/minions/data/killableMonsters'; import { addMonsterXP } from '../../lib/minions/functions'; import announceLoot from '../../lib/minions/functions/announceLoot'; import isImportantItemForMonster from '../../lib/minions/functions/isImportantItemForMonster'; -import { GroupMonsterActivityTaskOptions } from '../../lib/types/minions'; +import type { GroupMonsterActivityTaskOptions } from '../../lib/types/minions'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; export const groupoMonsterTask: MinionTask = { type: 'GroupMonsterKilling', async run(data: GroupMonsterActivityTaskOptions) { - const { monsterID, channelID, quantity, users, leader, duration } = data; + const { mi: monsterID, channelID, q: quantity, users, leader, duration } = data; const monster = killableMonsters.find(mon => mon.id === monsterID)!; const teamsLoot: { [key: string]: Bank } = {}; const kcAmounts: { [key: string]: number } = {}; for (let i = 0; i < quantity; i++) { - let loot = monster.table.kill(1, {}); + const loot = monster.table.kill(1, {}); if (roll(10) && monster.id !== 696_969) { loot.multiply(4); loot.add(MysteryBoxes.roll()); @@ -28,7 +28,7 @@ export const groupoMonsterTask: MinionTask = { const userWhoGetsLoot = randArrItem(users); const currentLoot = teamsLoot[userWhoGetsLoot]; teamsLoot[userWhoGetsLoot] = loot.add(currentLoot); - kcAmounts[userWhoGetsLoot] = Boolean(kcAmounts[userWhoGetsLoot]) ? ++kcAmounts[userWhoGetsLoot] : 1; + kcAmounts[userWhoGetsLoot] = kcAmounts[userWhoGetsLoot] ? ++kcAmounts[userWhoGetsLoot] : 1; } const leaderUser = await mUserFetch(leader); @@ -60,7 +60,9 @@ export const groupoMonsterTask: MinionTask = { totalLoot.add(loot); if (kcToAdd) await user.incrementKC(monsterID, kcToAdd); - const purple = Object.keys(loot).some(itemID => isImportantItemForMonster(parseInt(itemID), monster)); + const purple = Object.keys(loot).some(itemID => + isImportantItemForMonster(Number.parseInt(itemID), monster) + ); resultStr += `${purple ? Emoji.Purple : ''} **${user} received:** ||${loot}||\n`; diff --git a/src/tasks/minions/herbloreActivity.ts b/src/tasks/minions/herbloreActivity.ts index d05b4c020eb..0fddadda6f4 100644 --- a/src/tasks/minions/herbloreActivity.ts +++ b/src/tasks/minions/herbloreActivity.ts @@ -1,11 +1,12 @@ -import { randInt, roll, Time } from 'e'; +import { Time, randInt, roll } from 'e'; import { Bank } from 'oldschooljs'; +import { SkillsEnum } from 'oldschooljs/dist/constants'; import { herbertDroprate } from '../../lib/constants'; -import { userhasDiaryTier, WildernessDiary } from '../../lib/diaries'; +import { WildernessDiary, userhasDiaryTier } from '../../lib/diaries'; import Herblore from '../../lib/skilling/skills/herblore/herblore'; -import { Mixable, SkillsEnum } from '../../lib/skilling/types'; -import { HerbloreActivityTaskOptions } from '../../lib/types/minions'; +import type { Mixable } from '../../lib/skilling/types'; +import type { HerbloreActivityTaskOptions } from '../../lib/types/minions'; import { percentChance } from '../../lib/util'; import getOSItem from '../../lib/util/getOSItem'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; @@ -59,11 +60,11 @@ export const herbloreTask: MinionTask = { const currentHerbLevel = user.skillLevel(SkillsEnum.Herblore); let scales = 0; // Having 99 herblore gives a 98% chance to recieve the max amount of shards - let maxShardChance = currentHerbLevel >= 99 ? 98 : 0; + const maxShardChance = currentHerbLevel >= 99 ? 98 : 0; // Completion of hard wilderness diary gives 50% more lava scale shards per lava scale, rounded down - let diaryMultiplier = hasWildyDiary ? 1.5 : 1; + const diaryMultiplier = hasWildyDiary ? 1.5 : 1; - if (Boolean(wesley)) { + if (wesley) { // Wesley always turns Lava scales into 3 lava scale shards scales = quantity * 3; } else { diff --git a/src/tasks/minions/mageArena2Activity.ts b/src/tasks/minions/mageArena2Activity.ts index e5cd5138c52..7cc092ecf65 100644 --- a/src/tasks/minions/mageArena2Activity.ts +++ b/src/tasks/minions/mageArena2Activity.ts @@ -1,25 +1,25 @@ import { percentChance, randArrItem } from 'e'; import { Bank } from 'oldschooljs'; -import { ActivityTaskOptionsWithNoChanges } from '../../lib/types/minions'; +import type { ActivityTaskOptionsWithNoChanges } from '../../lib/types/minions'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; export const mageArenaTwoTask: MinionTask = { type: 'MageArena2', async run(data: ActivityTaskOptionsWithNoChanges) { - let { userID, channelID } = data; + const { userID, channelID } = data; const user = await mUserFetch(userID); let str = ''; let loot: Bank | undefined = undefined; if (percentChance(70)) { const deathReason = randArrItem([ - 'Died to Porazdir.', - 'Killed by Derwen.', - 'Killed by Justiciar Zachariah.', - "PK'd by a clan.", - 'Killed by Chaos Elemental.', - 'Killed by a PKer.' + 'Died to Porazdir', + 'Killed by Derwen', + 'Killed by Justiciar Zachariah', + "PK'd by a clan", + 'Killed by Chaos Elemental', + 'Killed by a PKer' ]); str = `${user}, ${user.minionName} failed to complete the Mage Arena II: ${deathReason}. Try again.`; } else { diff --git a/src/tasks/minions/mageArenaActivity.ts b/src/tasks/minions/mageArenaActivity.ts index 8b8e3aeea60..42924010c72 100644 --- a/src/tasks/minions/mageArenaActivity.ts +++ b/src/tasks/minions/mageArenaActivity.ts @@ -1,12 +1,12 @@ import { Bank } from 'oldschooljs'; -import { ActivityTaskOptionsWithNoChanges } from '../../lib/types/minions'; +import type { ActivityTaskOptionsWithNoChanges } from '../../lib/types/minions'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; export const mageArenaTask: MinionTask = { type: 'MageArena', async run(data: ActivityTaskOptionsWithNoChanges) { - let { userID, channelID } = data; + const { userID, channelID } = data; const user = await mUserFetch(userID); const loot = new Bank().add('Saradomin cape').add('Zamorak cape').add('Guthix cape'); await transactItems({ diff --git a/src/tasks/minions/minigames/agilityArenaActivity.ts b/src/tasks/minions/minigames/agilityArenaActivity.ts index 22e835fd11a..e21f1a4d9c1 100644 --- a/src/tasks/minions/minigames/agilityArenaActivity.ts +++ b/src/tasks/minions/minigames/agilityArenaActivity.ts @@ -1,11 +1,11 @@ -import { calcWhatPercent, reduceNumByPercent, Time } from 'e'; +import { Time, calcWhatPercent, reduceNumByPercent } from 'e'; import { Bank } from 'oldschooljs'; import { KaramjaDiary, userhasDiaryTier } from '../../../lib/diaries'; import { userHasFlappy } from '../../../lib/invention/inventions'; import { incrementMinigameScore } from '../../../lib/settings/settings'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; +import type { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; import { formatDuration, randomVariation, roll, skillingPetDropRate } from '../../../lib/util'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { determineXPFromTickets } from '../../../mahoji/lib/abstracted_commands/agilityArenaCommand'; @@ -18,7 +18,7 @@ export const agilityArenaTask: MinionTask = { const currentLevel = user.skillLevel(SkillsEnum.Agility); // You get 1 ticket per minute at best without diary - let timePerTicket = Time.Minute; + const timePerTicket = Time.Minute; let ticketsReceived = Math.floor(duration / timePerTicket); // Approximately 25k xp/hr (416xp per min) from the obstacles @@ -66,7 +66,7 @@ export const agilityArenaTask: MinionTask = { str += `\nYou received ${bonusTickets} bonus tickets for the Karamja Elite Diary.`; } - let xpFromTickets = determineXPFromTickets(ticketsReceived, user, hasKaramjaElite); + const xpFromTickets = determineXPFromTickets(ticketsReceived, user, hasKaramjaElite); const xpFromTrip = xpFromTickets + agilityXP; str += `\n${( (xpFromTrip / (duration / Time.Minute)) * diff --git a/src/tasks/minions/minigames/barbarianAssaultActivity.ts b/src/tasks/minions/minigames/barbarianAssaultActivity.ts index b0b5bcb373e..8bd7af0991b 100644 --- a/src/tasks/minions/minigames/barbarianAssaultActivity.ts +++ b/src/tasks/minions/minigames/barbarianAssaultActivity.ts @@ -3,7 +3,7 @@ import { calcPercentOfNum, calcWhatPercent, randInt } from 'e'; import { KandarinDiary, userhasDiaryTier } from '../../../lib/diaries'; import { userHasFlappy } from '../../../lib/invention/inventions'; import { incrementMinigameScore } from '../../../lib/settings/settings'; -import { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; +import type { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { userStatsUpdate } from '../../../mahoji/mahojiSettings'; diff --git a/src/tasks/minions/minigames/castleWarsActivity.ts b/src/tasks/minions/minigames/castleWarsActivity.ts index 8ae8a414d1f..3d8fb66e230 100644 --- a/src/tasks/minions/minigames/castleWarsActivity.ts +++ b/src/tasks/minions/minigames/castleWarsActivity.ts @@ -3,7 +3,7 @@ import { Bank } from 'oldschooljs'; import { userHasFlappy } from '../../../lib/invention/inventions'; import { incrementMinigameScore } from '../../../lib/settings/settings'; -import { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; +import type { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; const ticketTable = new SimpleTable().add(1, 4).add(2, 4).add(3, 1); @@ -20,7 +20,7 @@ export const castleWarsTask: MinionTask = { for (let i = 0; i < quantity; i++) { loot.add('Castle wars ticket', ticketTable.rollOrThrow()); } - let boosts = []; + const boosts = []; const flappyRes = await userHasFlappy({ user, duration }); if (flappyRes.shouldGiveBoost) { diff --git a/src/tasks/minions/minigames/championsChallengeActivity.ts b/src/tasks/minions/minigames/championsChallengeActivity.ts index 163494c706e..66829140f78 100644 --- a/src/tasks/minions/minigames/championsChallengeActivity.ts +++ b/src/tasks/minions/minigames/championsChallengeActivity.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import { incrementMinigameScore } from '../../../lib/settings/settings'; -import { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; +import type { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; export const championsChallengeTask: MinionTask = { diff --git a/src/tasks/minions/minigames/chompyHuntActivity.ts b/src/tasks/minions/minigames/chompyHuntActivity.ts index 18f9d216fe3..7df8ad39123 100644 --- a/src/tasks/minions/minigames/chompyHuntActivity.ts +++ b/src/tasks/minions/minigames/chompyHuntActivity.ts @@ -2,9 +2,9 @@ import { roll } from 'e'; import { Bank } from 'oldschooljs'; import { chompyHats } from '../../../lib/constants'; -import { userhasDiaryTier, WesternProv } from '../../../lib/diaries'; +import { WesternProv, userhasDiaryTier } from '../../../lib/diaries'; import { getMinigameEntity, incrementMinigameScore } from '../../../lib/settings/settings'; -import { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; +import type { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; export const chompHuntTask: MinionTask = { diff --git a/src/tasks/minions/minigames/fightCavesActivity.ts b/src/tasks/minions/minigames/fightCavesActivity.ts index 7392118b70c..35035f2b513 100644 --- a/src/tasks/minions/minigames/fightCavesActivity.ts +++ b/src/tasks/minions/minigames/fightCavesActivity.ts @@ -4,10 +4,10 @@ import { Bank, Monsters } from 'oldschooljs'; import { Emoji, Events } from '../../../lib/constants'; import { userHasFlappy } from '../../../lib/invention/inventions'; -import { prisma } from '../../../lib/settings/prisma'; + import { SkillsEnum } from '../../../lib/skilling/types'; import { calculateSlayerPoints, getUsersCurrentSlayerInfo } from '../../../lib/slayer/slayerUtil'; -import { FightCavesActivityTaskOptions } from '../../../lib/types/minions'; +import type { FightCavesActivityTaskOptions } from '../../../lib/types/minions'; import { formatDuration, percentChance } from '../../../lib/util'; import chatHeadImage from '../../../lib/util/chatHeadImage'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; @@ -36,15 +36,15 @@ export const fightCavesTask: MinionTask = { { fight_caves_attempts: true } ); - const attemptsStr = `You have tried Fight caves ${newFightCavesAttempts}x times.`; + const attemptsStr = `You have tried Fight caves ${newFightCavesAttempts}x times`; // Add slayer const usersTask = await getUsersCurrentSlayerInfo(user.id); const isOnTask = usersTask.currentTask !== null && usersTask.currentTask !== undefined && - usersTask.currentTask!.monster_id === Monsters.TzHaarKet.id && - usersTask.currentTask!.quantity_remaining === usersTask.currentTask!.quantity; + usersTask.currentTask?.monster_id === Monsters.TzHaarKet.id && + usersTask.currentTask?.quantity_remaining === usersTask.currentTask?.quantity; if (preJadDeathTime) { let slayerMsg = ''; @@ -53,7 +53,7 @@ export const fightCavesTask: MinionTask = { await prisma.slayerTask.update({ where: { - id: usersTask.currentTask!.id + id: usersTask.currentTask?.id }, data: { quantity_remaining: 0, @@ -82,7 +82,7 @@ export const fightCavesTask: MinionTask = { preJadDeathTime )} into your attempt.${slayerMsg} The following supplies were refunded back into your bank: ${itemLootBank}.`, await chatHeadImage({ - content: `You die before you even reach TzTok-Jad...atleast you tried, I give you ${tokkulReward}x Tokkul. ${attemptsStr}`, + content: `You die before you even reach TzTok-Jad... At least you tried, I give you ${tokkulReward}x Tokkul. ${attemptsStr}.`, head: 'mejJal' }), data, @@ -104,7 +104,7 @@ export const fightCavesTask: MinionTask = { await prisma.slayerTask.update({ where: { - id: usersTask.currentTask!.id + id: usersTask.currentTask?.id }, data: { quantity_remaining: 0, @@ -118,7 +118,7 @@ export const fightCavesTask: MinionTask = { channelID, `${user} ${msg}`, await chatHeadImage({ - content: `TzTok-Jad stomp you to death...nice try though JalYt, for your effort I give you ${tokkulReward}x Tokkul. ${attemptsStr}.`, + content: `TzTok-Jad stomp you to death... Nice try though JalYt, for your effort I give you ${tokkulReward}x Tokkul. ${attemptsStr}.`, head: 'mejJal' }), data, @@ -158,8 +158,8 @@ export const fightCavesTask: MinionTask = { itemsToAdd: loot }); - const rangeXP = await user.addXP({ skillName: SkillsEnum.Ranged, amount: 47_580, duration }); - const hpXP = await user.addXP({ skillName: SkillsEnum.Hitpoints, amount: 15_860, duration }); + const rangeXP = await user.addXP({ skillName: SkillsEnum.Ranged, amount: 47_580, duration, minimal: true }); + const hpXP = await user.addXP({ skillName: SkillsEnum.Hitpoints, amount: 15_860, duration, minimal: true }); let msg = `${rangeXP}. ${hpXP}.`; if (isOnTask) { @@ -185,14 +185,20 @@ export const fightCavesTask: MinionTask = { await prisma.slayerTask.update({ where: { - id: usersTask.currentTask!.id + id: usersTask.currentTask?.id }, data: { quantity_remaining: 0 } }); - const slayXP = await user.addXP({ skillName: SkillsEnum.Slayer, amount: slayerXP, duration }); + const slayXP = await user.addXP({ + skillName: SkillsEnum.Slayer, + amount: slayerXP, + duration, + minimal: true + }); + const xpMessage = `${msg} ${slayXP}`; msg = `Jad task completed. ${xpMessage}. \n**You've completed ${currentStreak} tasks and received ${points} points; giving you a total of ${secondNewUser.newUser.slayer_points}; return to a Slayer master.**`; diff --git a/src/tasks/minions/minigames/gauntletActivity.ts b/src/tasks/minions/minigames/gauntletActivity.ts index 2f1710bc805..e029701a08c 100644 --- a/src/tasks/minions/minigames/gauntletActivity.ts +++ b/src/tasks/minions/minigames/gauntletActivity.ts @@ -1,9 +1,9 @@ import { calcWhatPercent, percentChance } from 'e'; import { Bank } from 'oldschooljs'; -import { getMinigameScore, incrementMinigameScore, MinigameName } from '../../../lib/settings/settings'; +import { type MinigameName, getMinigameScore, incrementMinigameScore } from '../../../lib/settings/minigames'; import { gauntlet } from '../../../lib/simulation/gauntlet'; -import { GauntletOptions } from '../../../lib/types/minions'; +import type { GauntletOptions } from '../../../lib/types/minions'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { makeBankImage } from '../../../lib/util/makeBankImage'; import { updateBankSetting } from '../../../lib/util/updateBankSetting'; diff --git a/src/tasks/minions/minigames/giantsFoundryActivity.ts b/src/tasks/minions/minigames/giantsFoundryActivity.ts index 635730f967e..059bcc09747 100644 --- a/src/tasks/minions/minigames/giantsFoundryActivity.ts +++ b/src/tasks/minions/minigames/giantsFoundryActivity.ts @@ -1,16 +1,12 @@ import { deepClone } from 'e'; import { Bank } from 'oldschooljs'; -import { - encodeGiantWeapons, - generateRandomGiantWeapon, - GiantsFoundryBank, - giantWeaponName -} from '../../../lib/giantsFoundry'; +import type { GiantsFoundryBank } from '../../../lib/giantsFoundry'; +import { encodeGiantWeapons, generateRandomGiantWeapon, giantWeaponName } from '../../../lib/giantsFoundry'; import { trackLoot } from '../../../lib/lootTrack'; import { incrementMinigameScore } from '../../../lib/settings/settings'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { GiantsFoundryActivityTaskOptions } from '../../../lib/types/minions'; +import type { GiantsFoundryActivityTaskOptions } from '../../../lib/types/minions'; import { randomVariation } from '../../../lib/util'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { updateBankSetting } from '../../../lib/util/updateBankSetting'; @@ -37,7 +33,7 @@ export const giantsFoundryTask: MinionTask = { const newWeapons = deepClone(currentStats.gf_weapons_made) as GiantsFoundryBank; for (let i = 0; i < quantity; i++) { - let quality = Math.min(Math.floor(randomVariation(metalScore - 5 + avgMouldBonus, 10)), 199); + const quality = Math.min(Math.floor(randomVariation(metalScore - 5 + avgMouldBonus, 10)), 199); xpReceived += (Math.pow(quality, 2) / 73 + 1.5 * quality + 1) * 30; reputationReceived += quality; @@ -75,7 +71,7 @@ export const giantsFoundryTask: MinionTask = { const loot = new Bank().add('Coins', 2 * xpReceived); - let str = `${user}, ${ + const str = `${user}, ${ user.minionName } finished creating ${quantity}x giant weapons in the Giants' Foundry minigame. ${ boosts.length > 0 ? `**Boosts:** ${boosts.join(', ')}.` : '' @@ -104,7 +100,7 @@ export const giantsFoundryTask: MinionTask = { } ] }); - await userStatsBankUpdate(user.id, 'gf_loot', loot); + await userStatsBankUpdate(user, 'gf_loot', loot); handleTripFinish(user, channelID, str, undefined, data, itemsAdded); } diff --git a/src/tasks/minions/minigames/gnomeRestaurantActivity.ts b/src/tasks/minions/minigames/gnomeRestaurantActivity.ts index 4260f03ec78..94c91cd1a42 100644 --- a/src/tasks/minions/minigames/gnomeRestaurantActivity.ts +++ b/src/tasks/minions/minigames/gnomeRestaurantActivity.ts @@ -4,7 +4,7 @@ import LootTable from 'oldschooljs/dist/structures/LootTable'; import { userHasFlappy } from '../../../lib/invention/inventions'; import { incrementMinigameScore } from '../../../lib/settings/settings'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { GnomeRestaurantActivityTaskOptions } from '../../../lib/types/minions'; +import type { GnomeRestaurantActivityTaskOptions } from '../../../lib/types/minions'; import { roll } from '../../../lib/util'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { updateBankSetting } from '../../../lib/util/updateBankSetting'; @@ -96,7 +96,7 @@ export const gnomeResTask: MinionTask = { duration }); - let str = `<@${userID}>, ${user.minionName} finished completing ${quantity}x Gnome Restaurant deliveries. You received **${loot}**. ${xpRes} ${flappyRes.userMsg}`; + const str = `<@${userID}>, ${user.minionName} finished completing ${quantity}x Gnome Restaurant deliveries. You received **${loot}**. ${xpRes} ${flappyRes.userMsg}`; updateBankSetting('gnome_res_loot', loot); diff --git a/src/tasks/minions/minigames/guardiansOfTheRiftActivity.ts b/src/tasks/minions/minigames/guardiansOfTheRiftActivity.ts index a25cc2d8225..fb857e4b105 100644 --- a/src/tasks/minions/minigames/guardiansOfTheRiftActivity.ts +++ b/src/tasks/minions/minigames/guardiansOfTheRiftActivity.ts @@ -13,7 +13,7 @@ import { makeBankImage } from '../../../lib/util/makeBankImage'; import { updateBankSetting } from '../../../lib/util/updateBankSetting'; import { calcMaxRCQuantity, userStatsUpdate } from '../../../mahoji/mahojiSettings'; import { rewardsGuardianTable } from './../../../lib/simulation/rewardsGuardian'; -import { GuardiansOfTheRiftActivityTaskOptions } from './../../../lib/types/minions'; +import type { GuardiansOfTheRiftActivityTaskOptions } from './../../../lib/types/minions'; const catalyticRunesArray: string[] = [ 'Mind rune', @@ -71,7 +71,7 @@ export const guardiansOfTheRiftTask: MinionTask = { }) ]); - let runesLoot = new Bank(); + const runesLoot = new Bank(); let inventorySize = 28; const { bank } = user; // For each pouch the user has, increase their inventory size. @@ -85,7 +85,7 @@ export const guardiansOfTheRiftTask: MinionTask = { let setBonus = 1; if ( user.hasEquippedOrInBank( - Object.keys(Runecraft.raimentsOfTheEyeItems).map(i => parseInt(i)), + Object.keys(Runecraft.raimentsOfTheEyeItems).map(i => Number.parseInt(i)), 'every' ) ) { @@ -93,7 +93,7 @@ export const guardiansOfTheRiftTask: MinionTask = { } else { // For each Raiments of the Eye item, check if they have it, give its' quantity boost if so (NO bonus XP). for (const [itemID, bonus] of Object.entries(Runecraft.raimentsOfTheEyeItems)) { - if (user.hasEquippedOrInBank(parseInt(itemID))) { + if (user.hasEquippedOrInBank(Number.parseInt(itemID))) { setBonus += bonus / 100; } } @@ -121,7 +121,7 @@ export const guardiansOfTheRiftTask: MinionTask = { runesLoot.add(rune, Math.floor(quantityPerEssence * inventorySize * setBonus)); } - let rewardsGuardianLoot = new Bank(); + const rewardsGuardianLoot = new Bank(); let rewardsQty = 0; for (let i = 0; i < quantity; i++) { rewardsQty += randInt(rolls - 1, rolls); diff --git a/src/tasks/minions/minigames/infernoActivity.ts b/src/tasks/minions/minigames/infernoActivity.ts index 5c7756d4a4c..5196ffd1717 100644 --- a/src/tasks/minions/minigames/infernoActivity.ts +++ b/src/tasks/minions/minigames/infernoActivity.ts @@ -2,14 +2,14 @@ import { formatOrdinal } from '@oldschoolgg/toolkit'; import { calcPercentOfNum, calcWhatPercent, roll } from 'e'; import { Bank, Monsters } from 'oldschooljs'; +import type { ItemBank } from 'oldschooljs/dist/meta/types'; import { Events } from '../../../lib/constants'; import { diariesObject, userhasDiaryTier } from '../../../lib/diaries'; -import { countUsersWithItemInCl, prisma } from '../../../lib/settings/prisma'; +import { countUsersWithItemInCl } from '../../../lib/settings/prisma'; import { getMinigameScore, incrementMinigameScore } from '../../../lib/settings/settings'; import { SkillsEnum } from '../../../lib/skilling/types'; import { calculateSlayerPoints, getUsersCurrentSlayerInfo } from '../../../lib/slayer/slayerUtil'; -import { ItemBank } from '../../../lib/types'; -import { InfernoOptions } from '../../../lib/types/minions'; +import type { InfernoOptions } from '../../../lib/types/minions'; import { formatDuration } from '../../../lib/util'; import chatHeadImage from '../../../lib/util/chatHeadImage'; import { mahojiClientSettingsFetch, mahojiClientSettingsUpdate } from '../../../lib/util/clientSettings'; @@ -50,9 +50,9 @@ export const infernoTask: MinionTask = { const isOnTask = usersTask.currentTask !== null && usersTask.currentTask !== undefined && - usersTask.currentTask!.monster_id === Monsters.TzHaarKet.id && + usersTask.currentTask?.monster_id === Monsters.TzHaarKet.id && score > 0 && - usersTask.currentTask!.quantity_remaining === usersTask.currentTask!.quantity; + usersTask.currentTask?.quantity_remaining === usersTask.currentTask?.quantity; const { inferno_attempts: newInfernoAttempts } = await userStatsUpdate( user.id, @@ -126,7 +126,7 @@ export const infernoTask: MinionTask = { await prisma.slayerTask.update({ where: { - id: usersTask.currentTask!.id + id: usersTask.currentTask?.id }, data: { quantity_remaining: 0, @@ -157,7 +157,7 @@ export const infernoTask: MinionTask = { await prisma.slayerTask.update({ where: { - id: usersTask.currentTask!.id + id: usersTask.currentTask?.id }, data: { quantity_remaining: 0, @@ -241,7 +241,7 @@ export const infernoTask: MinionTask = { const emergedKC = await getMinigameScore(user.id, 'emerged_inferno'); // If first successfull emerged zuk kill if (baseBank.has('Infernal cape') && isEmergedZuk && !diedEmergedZuk && emergedKC === 1) { - const usersDefeatedEmergedZuk = parseInt( + const usersDefeatedEmergedZuk = Number.parseInt( ( await prisma.$queryRawUnsafe( `SELECT COUNT(user_id) @@ -274,7 +274,7 @@ You made it through ${percentMadeItThrough.toFixed(2)}% of the Inferno${ unusedItems.length ? `, you didn't use ${percSuppliesRefunded.toFixed( 2 - )}% of your supplies, ${unusedItems} was returned to your bank` + )}% of your supplies, ${unusedItems} was returned to your bank` : '.' } `, diff --git a/src/tasks/minions/minigames/lmsActivity.ts b/src/tasks/minions/minigames/lmsActivity.ts index e8e4e768213..0d29ba9a02e 100644 --- a/src/tasks/minions/minigames/lmsActivity.ts +++ b/src/tasks/minions/minigames/lmsActivity.ts @@ -1,10 +1,10 @@ -import { formatOrdinal, SimpleTable } from '@oldschoolgg/toolkit'; +import { SimpleTable, formatOrdinal } from '@oldschoolgg/toolkit'; import { clamp, percentChance, sumArr } from 'e'; import { Emoji } from '../../../lib/constants'; -import { prisma } from '../../../lib/settings/prisma'; + import { incrementMinigameScore } from '../../../lib/settings/settings'; -import { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; +import type { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; import { calcPerHour, gaussianRandom } from '../../../lib/util'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; @@ -76,9 +76,9 @@ export function calculateResultOfLMSGames(qty: number, lmsStats: Awaited !notPurple.includes(i)); +const coxPurpleItems = chambersOfXericCL.filter(i => !notPurple.includes(i)); async function handleCoxXP(user: MUser, qty: number, isCm: boolean) { let hitpointsXP = 12_000 * qty; @@ -170,7 +170,7 @@ export const raidsTask: MinionTask = { } finished. The total amount of points your team got is ${totalPoints.toLocaleString()}.\n`; await Promise.all(allUsers.map(u => incrementMinigameScore(u.id, minigameID, quantity))); - for (let [userID, userData] of raidResults) { + for (const [userID, userData] of raidResults) { const { personalPoints, deaths, deathChance, loot, mUser: user, naturalDouble, flappyMsg } = userData; if (!user) continue; if (naturalDouble) loot.add(MysteryBoxes.roll()); @@ -180,12 +180,12 @@ export const raidsTask: MinionTask = { cc ? userStatsBankUpdate(user.id, 'chincannon_destroyed_loot_bank', loot).then(() => ({ itemsAdded: new Bank() - })) + })) : transactItems({ userID, itemsToAdd: loot, collectionLog: true - }), + }), userStatsUpdate( user.id, { @@ -218,7 +218,7 @@ export const raidsTask: MinionTask = { } if (cc) { - let msg = randArrItem(CHINCANNON_MESSAGES); + const msg = randArrItem(CHINCANNON_MESSAGES); resultMessage += `\n\n**${msg}**`; } @@ -256,7 +256,7 @@ export const raidsTask: MinionTask = { } ], type: 'Chambers of Xerician' - }) + }) : undefined, data, totalLoot @@ -276,7 +276,7 @@ export const raidsTask: MinionTask = { customTexts: [] })), type: 'Chambers of Xerician' - }) + }) : undefined, data, null diff --git a/src/tasks/minions/minigames/roguesDenMazeActivity.ts b/src/tasks/minions/minigames/roguesDenMazeActivity.ts index 4d50986e884..d42484ac2e6 100644 --- a/src/tasks/minions/minigames/roguesDenMazeActivity.ts +++ b/src/tasks/minions/minigames/roguesDenMazeActivity.ts @@ -3,7 +3,7 @@ import { Bank } from 'oldschooljs'; import { roguesDenOutfit } from '../../../lib/data/CollectionsExport'; import { incrementMinigameScore } from '../../../lib/settings/settings'; -import { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; +import type { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { makeBankImage } from '../../../lib/util/makeBankImage'; diff --git a/src/tasks/minions/minigames/sepulchreActivity.ts b/src/tasks/minions/minigames/sepulchreActivity.ts index 20ad2ba9ee6..cc55a7874fc 100644 --- a/src/tasks/minions/minigames/sepulchreActivity.ts +++ b/src/tasks/minions/minigames/sepulchreActivity.ts @@ -6,7 +6,7 @@ import { trackLoot } from '../../../lib/lootTrack'; import { openCoffin, sepulchreFloors } from '../../../lib/minions/data/sepulchre'; import { incrementMinigameScore } from '../../../lib/settings/settings'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { SepulchreActivityTaskOptions } from '../../../lib/types/minions'; +import type { SepulchreActivityTaskOptions } from '../../../lib/types/minions'; import { roll } from '../../../lib/util'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { makeBankImage } from '../../../lib/util/makeBankImage'; @@ -55,13 +55,13 @@ export const sepulchreTask: MinionTask = { itemsToAdd: loot }); - let xpRes = await user.addXP({ + const xpRes = await user.addXP({ skillName: SkillsEnum.Agility, amount: agilityXP, duration }); - let thievingXpRes = await user.addXP({ + const thievingXpRes = await user.addXP({ skillName: SkillsEnum.Thieving, amount: thievingXP, duration diff --git a/src/tasks/minions/minigames/shadesOfMortonActivity.ts b/src/tasks/minions/minigames/shadesOfMortonActivity.ts index 260cbf3434d..6de9f38914a 100644 --- a/src/tasks/minions/minigames/shadesOfMortonActivity.ts +++ b/src/tasks/minions/minigames/shadesOfMortonActivity.ts @@ -3,12 +3,13 @@ import { bold } from 'discord.js'; import { increaseNumByPercent, randInt, roll } from 'e'; import { Bank, LootTable } from 'oldschooljs'; +import assert from 'node:assert'; import { Events } from '../../../lib/constants'; import { MorytaniaDiary, userhasDiaryTier } from '../../../lib/diaries'; import { incrementMinigameScore } from '../../../lib/settings/minigames'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { ShadesOfMortonOptions } from '../../../lib/types/minions'; -import { assert, clAdjustedDroprate } from '../../../lib/util'; +import type { ShadesOfMortonOptions } from '../../../lib/types/minions'; +import { clAdjustedDroprate } from '../../../lib/util'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { shades, shadesLogs } from '../../../mahoji/lib/abstracted_commands/shadesOfMortonCommand'; @@ -23,29 +24,29 @@ export const shadesOfMortonTask: MinionTask = { const log = shadesLogs.find(i => i.normalLog.id === logID)!; const shade = shades.find(i => i.shadeName === shadeID)!; - let loot = new Bank(); + const loot = new Bank(); const multiplier = 100; - let table = new LootTable().add('Coins', 0.21 * multiplier); + const table = new LootTable().add('Coins', 0.21 * multiplier); if (shade.lowMetalKeys) { - let subTable = new LootTable(); + const subTable = new LootTable(); for (const key of shade.lowMetalKeys.items) subTable.add(key); table.add(subTable, 1, shade.lowMetalKeys.fraction * multiplier); } if (shade.highMetalKeys) { - let subTable = new LootTable(); + const subTable = new LootTable(); for (const key of shade.highMetalKeys.items) subTable.add(key); table.add(subTable, 1, shade.highMetalKeys.fraction * multiplier); } - let messages: string[] = []; + const messages: string[] = []; // Pet droprate gets rarer if using lower tier shades let gotPet = false; - let remains = ['Urium', 'Fiyr', 'Asyn', 'Riyl', 'Phrin', 'Loar']; + const remains = ['Urium', 'Fiyr', 'Asyn', 'Riyl', 'Phrin', 'Loar']; assert(remains.includes(shadeID), `Invalid shadeID: ${shadeID}`); - let baseGaryRate = (remains.indexOf(shadeID) + 1) * 1200; - let garyDroprate = clAdjustedDroprate(user, 'Gary', baseGaryRate, 1.4); + const baseGaryRate = (remains.indexOf(shadeID) + 1) * 1200; + const garyDroprate = clAdjustedDroprate(user, 'Gary', baseGaryRate, 1.4); for (let i = 0; i < quantity; i++) { loot.add(table.roll()); @@ -65,7 +66,7 @@ export const shadesOfMortonTask: MinionTask = { if (loot.has('Gary')) { await user.sync(); - let kcGot = randInt(scoreUpdate.newScore - quantity + 1, scoreUpdate.newScore); + const kcGot = randInt(scoreUpdate.newScore - quantity + 1, scoreUpdate.newScore); globalClient.emit( Events.ServerNotification, `**${user.badgedUsername}'s** minion, ${user.minionName}, just received their ${formatOrdinal( @@ -102,7 +103,7 @@ export const shadesOfMortonTask: MinionTask = { source: 'ShadesOfMorton' }); - let str = `${user}, You received ${loot}. ${xpStr}.`; + const str = `${user}, You received ${loot}. ${xpStr}.`; handleTripFinish(user, channelID, str, undefined, data, itemsAdded, messages); } diff --git a/src/tasks/minions/minigames/soulWarsActivity.ts b/src/tasks/minions/minigames/soulWarsActivity.ts index dea645f01fb..96bceb6e644 100644 --- a/src/tasks/minions/minigames/soulWarsActivity.ts +++ b/src/tasks/minions/minigames/soulWarsActivity.ts @@ -2,7 +2,7 @@ import { increaseNumByPercent, reduceNumByPercent } from 'e'; import { userHasFlappy } from '../../../lib/invention/inventions'; import { incrementMinigameScore } from '../../../lib/settings/settings'; -import { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; +import type { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; import { roll } from '../../../lib/util'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; diff --git a/src/tasks/minions/minigames/tearsOfGuthixActivity.ts b/src/tasks/minions/minigames/tearsOfGuthixActivity.ts index 4311e04f63a..754d04650ec 100644 --- a/src/tasks/minions/minigames/tearsOfGuthixActivity.ts +++ b/src/tasks/minions/minigames/tearsOfGuthixActivity.ts @@ -1,11 +1,11 @@ import { increaseNumByPercent, randInt } from 'e'; -import { chargePortentIfHasCharges, PortentID } from '../../../lib/bso/divination'; +import { PortentID, chargePortentIfHasCharges } from '../../../lib/bso/divination'; import { BitField } from '../../../lib/constants'; import { LumbridgeDraynorDiary, userhasDiaryTier } from '../../../lib/diaries'; import { incrementMinigameScore } from '../../../lib/settings/settings'; -import { SkillsEnum } from '../../../lib/skilling/types'; -import { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; +import type { SkillsEnum } from '../../../lib/skilling/types'; +import type { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { userStatsUpdate } from '../../../mahoji/mahojiSettings'; diff --git a/src/tasks/minions/minigames/templeTrekkingActivity.ts b/src/tasks/minions/minigames/templeTrekkingActivity.ts index 2b25d3e2ec1..65a45229951 100644 --- a/src/tasks/minions/minigames/templeTrekkingActivity.ts +++ b/src/tasks/minions/minigames/templeTrekkingActivity.ts @@ -10,7 +10,7 @@ import { rewardTokens } from '../../../lib/minions/data/templeTrekking'; import { incrementMinigameScore } from '../../../lib/settings/settings'; -import { TempleTrekkingActivityTaskOptions } from '../../../lib/types/minions'; +import type { TempleTrekkingActivityTaskOptions } from '../../../lib/types/minions'; import { percentChance, stringMatches } from '../../../lib/util'; import getOSItem from '../../../lib/util/getOSItem'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; @@ -45,21 +45,21 @@ export const templeTrekkingTask: MinionTask = { const user = await mUserFetch(userID); await incrementMinigameScore(user.id, 'temple_trekking', quantity); const userBank = user.bank.clone(); - let loot = new Bank(); + const loot = new Bank(); const rewardToken = stringMatches(difficulty, 'hard') ? getOSItem(rewardTokens.hard) : stringMatches(difficulty, 'medium') - ? getOSItem(rewardTokens.medium) - : getOSItem(rewardTokens.easy); + ? getOSItem(rewardTokens.medium) + : getOSItem(rewardTokens.easy); let totalEncounters = 0; for (let trip = 0; trip < quantity; trip++) { const encounters = stringMatches(difficulty, 'hard') ? randInt(0, 7) : stringMatches(difficulty, 'medium') - ? randInt(0, 4) - : randInt(0, 5); + ? randInt(0, 4) + : randInt(0, 5); for (let i = 0; i < encounters; i++) { // 2 out of 12 encounters drop loot, 16% @@ -95,7 +95,7 @@ export const templeTrekkingTask: MinionTask = { itemsToAdd: loot }); - let str = `${user}, ${user.minionName} finished Temple Trekking ${quantity}x times. ${totalEncounters}x encounters were defeated. ${flappyRes.userMsg}`; + const str = `${user}, ${user.minionName} finished Temple Trekking ${quantity}x times. ${totalEncounters}x encounters were defeated. ${flappyRes.userMsg}`; const image = await makeBankImage({ bank: itemsAdded, diff --git a/src/tasks/minions/minigames/temporossActivity.ts b/src/tasks/minions/minigames/temporossActivity.ts index 2f6b4373739..0ebb7358ae0 100644 --- a/src/tasks/minions/minigames/temporossActivity.ts +++ b/src/tasks/minions/minigames/temporossActivity.ts @@ -4,7 +4,7 @@ import { incrementMinigameScore } from '../../../lib/settings/settings'; import { getTemporossLoot } from '../../../lib/simulation/tempoross'; import Fishing from '../../../lib/skilling/skills/fishing'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { TemporossActivityTaskOptions } from '../../../lib/types/minions'; +import type { TemporossActivityTaskOptions } from '../../../lib/types/minions'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { makeBankImage } from '../../../lib/util/makeBankImage'; @@ -28,7 +28,7 @@ export const temporossTask: MinionTask = { // If they have the entire angler outfit, give an extra 0.5% xp bonus if ( user.hasEquippedOrInBank( - Object.keys(Fishing.anglerItems).map(i => parseInt(i)), + Object.keys(Fishing.anglerItems).map(i => Number.parseInt(i)), 'every' ) ) { @@ -38,7 +38,7 @@ export const temporossTask: MinionTask = { } else { // For each angler item, check if they have it, give its' XP boost if so. for (const [itemID, bonus] of Object.entries(Fishing.anglerItems)) { - if (user.hasEquippedOrInBank(parseInt(itemID))) { + if (user.hasEquippedOrInBank(Number.parseInt(itemID))) { const amountToAdd = Math.floor(fXPtoGive * (bonus / 100)); fXPtoGive += amountToAdd; fBonusXP += amountToAdd; diff --git a/src/tasks/minions/minigames/titheFarmActivity.ts b/src/tasks/minions/minigames/titheFarmActivity.ts index 0721013d477..2ab82293f3a 100644 --- a/src/tasks/minions/minigames/titheFarmActivity.ts +++ b/src/tasks/minions/minigames/titheFarmActivity.ts @@ -3,7 +3,7 @@ import { Bank } from 'oldschooljs'; import { Emoji } from '../../../lib/constants'; import { userHasFlappy } from '../../../lib/invention/inventions'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { TitheFarmActivityTaskOptions } from '../../../lib/types/minions'; +import type { TitheFarmActivityTaskOptions } from '../../../lib/types/minions'; import { roll, skillingPetDropRate } from '../../../lib/util'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { userStatsUpdate } from '../../../mahoji/mahojiSettings'; diff --git a/src/tasks/minions/minigames/toaActivity.ts b/src/tasks/minions/minigames/toaActivity.ts index 0039cb86c8a..4635702a65f 100644 --- a/src/tasks/minions/minigames/toaActivity.ts +++ b/src/tasks/minions/minigames/toaActivity.ts @@ -1,26 +1,21 @@ import { formatOrdinal } from '@oldschoolgg/toolkit'; import { bold } from 'discord.js'; -import { isObject, Time, uniqueArr } from 'e'; +import { Time, isObject, uniqueArr } from 'e'; import { Bank } from 'oldschooljs'; -import { ItemBank } from 'oldschooljs/dist/meta/types'; +import type { ItemBank } from 'oldschooljs/dist/meta/types'; +import { resolveItems } from 'oldschooljs/dist/util/util'; import { drawChestLootImage } from '../../../lib/bankImage'; import { Emoji, Events, toaPurpleItems } from '../../../lib/constants'; import { toaCL } from '../../../lib/data/CollectionsExport'; import { trackLoot } from '../../../lib/lootTrack'; import { getMinigameScore, incrementMinigameScore } from '../../../lib/settings/settings'; import { TeamLoot } from '../../../lib/simulation/TeamLoot'; -import { - calcTOALoot, - calculateXPFromRaid, - normalizeTOAUsers, - toaOrnamentKits, - toaPetTransmogItems -} from '../../../lib/simulation/toa'; -import { TOAOptions } from '../../../lib/types/minions'; +import { calcTOALoot, calculateXPFromRaid, toaOrnamentKits, toaPetTransmogItems } from '../../../lib/simulation/toa'; +import type { TOAOptions } from '../../../lib/types/minions'; +import { normalizeTOAUsers } from '../../../lib/util'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { assert } from '../../../lib/util/logError'; -import resolveItems from '../../../lib/util/resolveItems'; import { updateBankSetting } from '../../../lib/util/updateBankSetting'; import { userStatsUpdate } from '../../../mahoji/mahojiSettings'; @@ -130,12 +125,12 @@ export const toaTask: MinionTask = { let resultMessage = isSolo ? `${leaderSoloUser}, your minion finished ${quantity === 1 ? 'a' : `${quantity}x`} Tombs of Amascut raid${ quantity > 1 ? 's' : '' - }! Your KC is now ${minigameIncrementResult[0].newScore}.\n` + }! Your KC is now ${minigameIncrementResult[0].newScore}.\n` : `<@${leader}> Your Raid${quantity > 1 ? 's have' : ' has'} finished.\n`; const shouldShowImage = allUsers.length <= 3 && totalLoot.entries().every(i => i[1].length <= 6); - for (let [userID, userData] of raidResults.entries()) { + for (const [userID, userData] of raidResults.entries()) { const { points, deaths, mUser: user } = userData; if (!chincannonUser) { await userStatsUpdate( @@ -158,7 +153,7 @@ export const toaTask: MinionTask = { } } - let str: string = 'Nothing'; + let str = 'Nothing'; if (!chincannonUser) { const { itemsAdded } = await transactItems({ userID, @@ -185,23 +180,18 @@ export const toaTask: MinionTask = { str = isPurple ? `${Emoji.Purple} ||${itemsAdded}||` : itemsAdded.toString(); } - userStatsUpdate( - user.id, - u => { - return { - toa_raid_levels_bank: new Bank() - .add(u.toa_raid_levels_bank as ItemBank) - .add(raidLevel, quantity).bank, - total_toa_duration_minutes: { - increment: Math.floor(duration / Time.Minute) - }, - toa_loot: !chincannonUser - ? new Bank(u.toa_loot as ItemBank).add(totalLoot.get(userID)).bank - : undefined - }; + const currentStats = await user.fetchStats({ toa_raid_levels_bank: true, toa_loot: true }); + userStatsUpdate(user.id, { + toa_raid_levels_bank: new Bank() + .add(currentStats.toa_raid_levels_bank as ItemBank) + .add(raidLevel, quantity).bank, + total_toa_duration_minutes: { + increment: Math.floor(duration / Time.Minute) }, - {} - ); + toa_loot: !chincannonUser + ? new Bank(currentStats.toa_loot as ItemBank).add(totalLoot.get(userID)).bank + : undefined + }); const deathStr = deaths === 0 ? '' : new Array(deaths).fill(Emoji.Skull).join(' '); if (!chincannonUser) { @@ -279,7 +269,7 @@ export const toaTask: MinionTask = { } ], type: 'Tombs of Amascut' - }) + }) : undefined, data, itemsAddedTeamLoot.totalLoot() @@ -299,7 +289,7 @@ export const toaTask: MinionTask = { customTexts: makeCustomTexts(u.id) })), type: 'Tombs of Amascut' - }) + }) : undefined, data, null diff --git a/src/tasks/minions/minigames/tobActivity.ts b/src/tasks/minions/minigames/tobActivity.ts index ff448bee80a..519eab09f0e 100644 --- a/src/tasks/minions/minigames/tobActivity.ts +++ b/src/tasks/minions/minigames/tobActivity.ts @@ -12,7 +12,7 @@ import { incrementMinigameScore } from '../../../lib/settings/settings'; import { TeamLoot } from '../../../lib/simulation/TeamLoot'; import { TheatreOfBlood } from '../../../lib/simulation/tob'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { TheatreOfBloodTaskOptions } from '../../../lib/types/minions'; +import type { TheatreOfBloodTaskOptions } from '../../../lib/types/minions'; import { convertPercentChance } from '../../../lib/util'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { updateBankSetting } from '../../../lib/util/updateBankSetting'; @@ -132,7 +132,7 @@ export const tobTask: MinionTask = { ); } - for (let [userID, _userLoot] of Object.entries(result.loot)) { + for (const [userID, _userLoot] of Object.entries(result.loot)) { if (data.solo && userID !== leader) continue; const user = allUsers.find(i => i.id === userID); if (!user) continue; @@ -240,7 +240,7 @@ export const tobTask: MinionTask = { }); if (chincannonUser) { - let msg = randArrItem(CHINCANNON_MESSAGES); + const msg = randArrItem(CHINCANNON_MESSAGES); resultMessage += `\n\n**${msg}**`; } const shouldShowImage = @@ -262,7 +262,7 @@ export const tobTask: MinionTask = { } ], type: 'Theatre of Blood' - }) + }) : undefined, data, totalLoot @@ -282,7 +282,7 @@ export const tobTask: MinionTask = { customTexts: [] })), type: 'Theatre of Blood' - }) + }) : undefined, data, null diff --git a/src/tasks/minions/minigames/trawlerActivity.ts b/src/tasks/minions/minigames/trawlerActivity.ts index 8bc3d67ac7b..6d914976e73 100644 --- a/src/tasks/minions/minigames/trawlerActivity.ts +++ b/src/tasks/minions/minigames/trawlerActivity.ts @@ -6,7 +6,7 @@ import { ArdougneDiary, userhasDiaryTier } from '../../../lib/diaries'; import { incrementMinigameScore } from '../../../lib/settings/settings'; import { fishingTrawlerLoot } from '../../../lib/simulation/fishingTrawler'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; +import type { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { makeBankImage } from '../../../lib/util/makeBankImage'; import { anglerBoostPercent } from '../../../mahoji/mahojiSettings'; diff --git a/src/tasks/minions/minigames/troubleBrewingActivity.ts b/src/tasks/minions/minigames/troubleBrewingActivity.ts index e3857dc831b..85c17bfcf3a 100644 --- a/src/tasks/minions/minigames/troubleBrewingActivity.ts +++ b/src/tasks/minions/minigames/troubleBrewingActivity.ts @@ -2,7 +2,7 @@ import { Bank } from 'oldschooljs'; import { userHasFlappy } from '../../../lib/invention/inventions'; import { incrementMinigameScore } from '../../../lib/settings/minigames'; -import { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; +import type { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; export const brewingTask: MinionTask = { @@ -11,7 +11,7 @@ export const brewingTask: MinionTask = { const { channelID, quantity, userID, duration } = data; const user = await mUserFetch(userID); await incrementMinigameScore(user.id, 'trouble_brewing', quantity); - let loot = new Bank().add('Pieces of eight', quantity * 100); + const loot = new Bank().add('Pieces of eight', quantity * 100); const flappyRes = await userHasFlappy({ user, duration }); if (flappyRes.shouldGiveBoost) loot.multiply(2); diff --git a/src/tasks/minions/minigames/warriorsGuild/animatedArmourActivity.ts b/src/tasks/minions/minigames/warriorsGuild/animatedArmourActivity.ts index db34d2c95fc..e5f75ab9534 100644 --- a/src/tasks/minions/minigames/warriorsGuild/animatedArmourActivity.ts +++ b/src/tasks/minions/minigames/warriorsGuild/animatedArmourActivity.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import { Emoji } from '../../../../lib/constants'; -import { AnimatedArmourActivityTaskOptions } from '../../../../lib/types/minions'; +import type { AnimatedArmourActivityTaskOptions } from '../../../../lib/types/minions'; import { handleTripFinish } from '../../../../lib/util/handleTripFinish'; import { Armours } from '../../../../mahoji/lib/abstracted_commands/warriorsGuildCommand'; @@ -12,7 +12,7 @@ export const animatedArmorTask: MinionTask = { const user = await mUserFetch(userID); const armour = Armours.find(armour => armour.name === armourID)!; - let baseQuantity = quantity * armour.tokens; + const baseQuantity = quantity * armour.tokens; const loot = new Bank().add('Warrior guild token', baseQuantity); const messages: string[] = []; diff --git a/src/tasks/minions/minigames/warriorsGuild/cyclopsActivity.ts b/src/tasks/minions/minigames/warriorsGuild/cyclopsActivity.ts index 7a9f05f2323..f1f2d77ca0e 100644 --- a/src/tasks/minions/minigames/warriorsGuild/cyclopsActivity.ts +++ b/src/tasks/minions/minigames/warriorsGuild/cyclopsActivity.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import { CyclopsTable } from '../../../../lib/simulation/cyclops'; -import { ActivityTaskOptionsWithQuantity } from '../../../../lib/types/minions'; +import type { ActivityTaskOptionsWithQuantity } from '../../../../lib/types/minions'; import { roll } from '../../../../lib/util'; import { handleTripFinish } from '../../../../lib/util/handleTripFinish'; import itemID from '../../../../lib/util/itemID'; @@ -51,7 +51,7 @@ export const cyclopsTask: MinionTask = { const user = await mUserFetch(userID); const userBank = user.bank; - let loot = new Bank(); + const loot = new Bank(); for (let i = 0; i < quantity; i++) { const highestDefenderOwned = defenders.find( @@ -77,7 +77,7 @@ export const cyclopsTask: MinionTask = { }); const { newKC } = await user.incrementKC(cyclopsID, quantity); - let str = `${user}, ${user.minionName} finished killing ${quantity} Cyclops. Your Cyclops KC is now ${newKC}.`; + const str = `${user}, ${user.minionName} finished killing ${quantity} Cyclops. Your Cyclops KC is now ${newKC}.`; const image = await makeBankImage({ bank: itemsAdded, diff --git a/src/tasks/minions/minigames/wintertodtActivity.ts b/src/tasks/minions/minigames/wintertodtActivity.ts index ed43f6ac0ce..b878db37f69 100644 --- a/src/tasks/minions/minigames/wintertodtActivity.ts +++ b/src/tasks/minions/minigames/wintertodtActivity.ts @@ -1,13 +1,12 @@ -import { randInt, roll, Time } from 'e'; +import { Time, randInt, roll } from 'e'; import { Bank } from 'oldschooljs'; - import { userHasFlappy } from '../../../lib/invention/inventions'; import { trackLoot } from '../../../lib/lootTrack'; import { incrementMinigameScore } from '../../../lib/settings/settings'; import { WintertodtCrate } from '../../../lib/simulation/wintertodt'; import Firemaking from '../../../lib/skilling/skills/firemaking'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; +import type { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; import { clAdjustedDroprate } from '../../../lib/util'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { makeBankImage } from '../../../lib/util/makeBankImage'; @@ -18,8 +17,9 @@ export const wintertodtTask: MinionTask = { async run(data: ActivityTaskOptionsWithQuantity) { const { userID, channelID, quantity, duration } = data; const user = await mUserFetch(userID); + const hasMasterCape = user.hasEquippedOrInBank('Firemaking master cape'); - let loot = new Bank(); + const loot = new Bank(); let totalPoints = 0; @@ -71,7 +71,7 @@ export const wintertodtTask: MinionTask = { // If they have the entire pyromancer outfit, give an extra 0.5% xp bonus if ( user.hasEquippedOrInBank( - Object.keys(Firemaking.pyromancerItems).map(i => parseInt(i)), + Object.keys(Firemaking.pyromancerItems).map(i => Number.parseInt(i)), 'every' ) ) { @@ -81,7 +81,7 @@ export const wintertodtTask: MinionTask = { } else { // For each pyromancer item, check if they have it, give its' XP boost if so. for (const [itemID, bonus] of Object.entries(Firemaking.pyromancerItems)) { - if (user.hasEquippedOrInBank(parseInt(itemID))) { + if (user.hasEquippedOrInBank(Number.parseInt(itemID))) { const amountToAdd = Math.floor(fmXpToGive * (bonus / 100)); fmXpToGive += amountToAdd; fmBonusXP += amountToAdd; @@ -105,7 +105,7 @@ export const wintertodtTask: MinionTask = { if (flappyRes.shouldGiveBoost) { loot.multiply(2); } - if (user.hasEquippedOrInBank('Firemaking master cape')) { + if (hasMasterCape) { loot.multiply(2); } @@ -114,15 +114,20 @@ export const wintertodtTask: MinionTask = { collectionLog: true, itemsToAdd: loot }); - incrementMinigameScore(user.id, 'wintertodt', quantity); + await incrementMinigameScore(user.id, 'wintertodt', quantity); const image = await makeBankImage({ + title: `Loot From ${quantity}x Wintertodt`, bank: itemsAdded, user, previousCL }); - let output = `${user}, ${user.minionName} finished subduing Wintertodt ${quantity}x times. ${xpStr}, you cut ${numberOfRoots}x Bruma roots.`; + let output = `${user}, ${ + user.minionName + } finished subduing Wintertodt ${quantity}x times. ${xpStr}, you cut ${numberOfRoots}x Bruma roots${ + hasMasterCape ? ', 2x loot for Firemaking master cape.' : '.' + }`; if (fmBonusXP > 0) { output += `\n\n**Firemaking Bonus XP:** ${fmBonusXP.toLocaleString()}`; @@ -149,6 +154,6 @@ export const wintertodtTask: MinionTask = { ] }); - handleTripFinish(user, channelID, output, image.file.attachment, data, itemsAdded); + return handleTripFinish(user, channelID, output, image.file.attachment, data, itemsAdded); } }; diff --git a/src/tasks/minions/minigames/zalcanoActivity.ts b/src/tasks/minions/minigames/zalcanoActivity.ts index e871d39982e..993aeb751d7 100644 --- a/src/tasks/minions/minigames/zalcanoActivity.ts +++ b/src/tasks/minions/minigames/zalcanoActivity.ts @@ -3,7 +3,7 @@ import { Bank, Misc } from 'oldschooljs'; import { ZALCANO_ID } from '../../../lib/constants'; import { SkillsEnum } from '../../../lib/skilling/types'; -import { ZalcanoActivityTaskOptions } from '../../../lib/types/minions'; +import type { ZalcanoActivityTaskOptions } from '../../../lib/types/minions'; import { ashSanctifierEffect } from '../../../lib/util/ashSanctifier'; import { handleTripFinish } from '../../../lib/util/handleTripFinish'; import { makeBankImage } from '../../../lib/util/makeBankImage'; diff --git a/src/tasks/minions/miningActivity.ts b/src/tasks/minions/miningActivity.ts index 14f51a379c5..4b5c42d16e5 100644 --- a/src/tasks/minions/miningActivity.ts +++ b/src/tasks/minions/miningActivity.ts @@ -1,19 +1,21 @@ -import { increaseNumByPercent, randInt, roll, Time } from 'e'; import { Bank } from 'oldschooljs'; -import { chargePortentIfHasCharges, PortentID } from '../../lib/bso/divination'; +import { Time, increaseNumByPercent, randInt, roll } from 'e'; +import { SkillsEnum } from 'oldschooljs/dist/constants'; +import { itemID, toKMB } from 'oldschooljs/dist/util'; +import { PortentID, chargePortentIfHasCharges } from '../../lib/bso/divination'; import { GLOBAL_BSO_XP_MULTIPLIER, MIN_LENGTH_FOR_PET } from '../../lib/constants'; import { upgradedDragonstoneOutfit } from '../../lib/data/CollectionsExport'; import { globalDroprates } from '../../lib/data/globalDroprates'; -import { UserFullGearSetup } from '../../lib/gear'; +import type { UserFullGearSetup } from '../../lib/gear'; import { InventionID } from '../../lib/invention/inventions'; -import { StoneSpirit, stoneSpirits } from '../../lib/minions/data/stoneSpirits'; +import { type StoneSpirit, stoneSpirits } from '../../lib/minions/data/stoneSpirits'; import addSkillingClueToLoot from '../../lib/minions/functions/addSkillingClueToLoot'; import Mining from '../../lib/skilling/skills/mining'; import Smithing from '../../lib/skilling/skills/smithing'; -import { Ore, SkillsEnum } from '../../lib/skilling/types'; -import { MiningActivityTaskOptions } from '../../lib/types/minions'; -import { clAdjustedDroprate, itemID, skillingPetDropRate, toKMB } from '../../lib/util'; +import type { Ore } from '../../lib/skilling/types'; +import type { MiningActivityTaskOptions } from '../../lib/types/minions'; +import { clAdjustedDroprate, skillingPetDropRate } from '../../lib/util'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; import resolveItems from '../../lib/util/resolveItems'; import { mahojiUsersSettingsFetch, userStatsBankUpdate, userStatsUpdate } from '../../mahoji/mahojiSettings'; @@ -65,7 +67,7 @@ export function calculateMiningResult({ // Prospector outfit if ( allGear.skilling.hasEquipped( - Object.keys(Mining.prospectorItems).map(i => parseInt(i)), + Object.keys(Mining.prospectorItems).map(i => Number.parseInt(i)), true ) ) { @@ -248,7 +250,8 @@ export const miningTask: MinionTask = { type: 'Mining', async run(data: MiningActivityTaskOptions) { const { oreID, userID, channelID, duration, powermine } = data; - let { quantity } = data; + const { quantity } = data; + const minutes = Math.round(duration / Time.Minute); const user = await mUserFetch(userID); const ore = Mining.Ores.find(ore => ore.id === oreID)!; @@ -262,8 +265,8 @@ export const miningTask: MinionTask = { ? await chargePortentIfHasCharges({ user, portentID: PortentID.MiningPortent, - charges: amountOfSpiritsToUse - }) + charges: minutes + }) : null; const { totalMiningXPToAdd, diff --git a/src/tasks/minions/monsterActivity.ts b/src/tasks/minions/monsterActivity.ts index 6f5dd0be3d8..82c68ef0f8b 100644 --- a/src/tasks/minions/monsterActivity.ts +++ b/src/tasks/minions/monsterActivity.ts @@ -1,17 +1,19 @@ -import { Prisma } from '@prisma/client'; +import type { Prisma } from '@prisma/client'; import { + Time, calcWhatPercent, deepClone, increaseNumByPercent, percentChance, randArrItem, reduceNumByPercent, - sumArr, - Time + roll, + sumArr } from 'e'; -import { Bank, MonsterKillOptions, Monsters } from 'oldschooljs'; +import type { MonsterKillOptions } from 'oldschooljs'; +import { Bank, Monsters } from 'oldschooljs'; import { MonsterAttribute } from 'oldschooljs/dist/meta/monsterData'; -import { ItemBank } from 'oldschooljs/dist/meta/types'; +import type { ItemBank } from 'oldschooljs/dist/meta/types'; import { MysteryBoxes } from '../../lib/bsoOpenables'; import { ClueTiers } from '../../lib/clues/clueTiers'; @@ -19,19 +21,19 @@ import { BitField, Emoji } from '../../lib/constants'; import { slayerMaskHelms } from '../../lib/data/slayerMaskHelms'; import { KourendKebosDiary, userhasDiaryTier } from '../../lib/diaries'; import { isDoubleLootActive } from '../../lib/doubleLoot'; -import { inventionBoosts, InventionID, inventionItemBoost } from '../../lib/invention/inventions'; +import { InventionID, inventionBoosts, inventionItemBoost } from '../../lib/invention/inventions'; import { trackLoot } from '../../lib/lootTrack'; import killableMonsters from '../../lib/minions/data/killableMonsters'; import { addMonsterXP } from '../../lib/minions/functions'; import announceLoot from '../../lib/minions/functions/announceLoot'; -import { KillableMonster } from '../../lib/minions/types'; -import { prisma } from '../../lib/settings/prisma'; +import type { KillableMonster } from '../../lib/minions/types'; + import { bones } from '../../lib/skilling/skills/prayer'; import { SkillsEnum } from '../../lib/skilling/types'; import { SlayerTaskUnlocksEnum } from '../../lib/slayer/slayerUnlocks'; import { calculateSlayerPoints, isOnSlayerTask } from '../../lib/slayer/slayerUtil'; -import { MonsterActivityTaskOptions } from '../../lib/types/minions'; -import { assert, calculateSimpleMonsterDeathChance, clAdjustedDroprate, hasSkillReqs, roll } from '../../lib/util'; +import type { MonsterActivityTaskOptions } from '../../lib/types/minions'; +import { assert, calculateSimpleMonsterDeathChance, clAdjustedDroprate, hasSkillReqs } from '../../lib/util'; import { ashSanctifierEffect } from '../../lib/util/ashSanctifier'; import calculateGearLostOnDeathWilderness from '../../lib/util/calculateGearLostOnDeathWilderness'; import getOSItem from '../../lib/util/getOSItem'; @@ -54,7 +56,7 @@ async function bonecrusherEffect(user: MUser, loot: Bank, duration: number, mess } } - let durationForCost = totalXP * 16.8; + const durationForCost = totalXP * 16.8; let boostMsg: string | null = null; if (hasSuperior && durationForCost > Time.Minute) { const t = await inventionItemBoost({ @@ -71,11 +73,11 @@ async function bonecrusherEffect(user: MUser, loot: Bank, duration: number, mess } totalXP *= 5; - userStatsUpdate(user.id, () => ({ + userStatsUpdate(user.id, { bonecrusher_prayer_xp: { increment: Math.floor(totalXP) } - })); + }); const xpStr = await user.addXP({ skillName: SkillsEnum.Prayer, amount: totalXP, @@ -88,7 +90,7 @@ async function bonecrusherEffect(user: MUser, loot: Bank, duration: number, mess hasSuperior ? `+${inventionBoosts.superiorBonecrusher.xpBoostPercent}% more from Superior bonecrusher${ boostMsg ? ` (${boostMsg})` : '' - }` + }` : ' from Gorajan bonecrusher' }` ); @@ -111,9 +113,9 @@ async function portableTannerEffect(user: MUser, loot: Bank, duration: number, m }); if (!boostRes.success) return; let triggered = false; - let toAdd = new Bank(); + const toAdd = new Bank(); for (const [hide, leather] of hideLeatherMap) { - let qty = loot.amount(hide.id); + const qty = loot.amount(hide.id); if (qty > 0) { triggered = true; loot.remove(hide.id, qty); @@ -157,7 +159,7 @@ export async function clueUpgraderEffect(user: MUser, loot: Bank, messages: stri loot.add(upgradedClues); assert(loot.has(removeBank)); loot.remove(removeBank); - let totalCluesUpgraded = sumArr(upgradedClues.items().map(i => i[1])); + const totalCluesUpgraded = sumArr(upgradedClues.items().map(i => i[1])); messages.push(`<:Clue_upgrader:986830303001722880> Upgraded ${totalCluesUpgraded} clues (${boostRes.messages})`); } @@ -165,17 +167,18 @@ export const monsterTask: MinionTask = { type: 'MonsterKilling', async run(data: MonsterActivityTaskOptions) { let { - monsterID, + mi: monsterID, userID, channelID, - quantity, + q: quantity, duration, usingCannon, cannonMulti, - burstOrBarrage, + bob: burstOrBarrage, died, pkEncounters, - hasWildySupplies + hasWildySupplies, + isInWilderness } = data; const monster = killableMonsters.find(mon => mon.id === monsterID)!; @@ -253,7 +256,7 @@ export const monsterTask: MinionTask = { gear: userGear, smited: hasPrayerLevel && !protectItem, protectItem: hasPrayerLevel, - after20wilderness: monster.pkBaseDeathChance && monster.pkBaseDeathChance >= 5 ? true : false, + after20wilderness: !!(monster.pkBaseDeathChance && monster.pkBaseDeathChance >= 5), skulled }); @@ -369,7 +372,12 @@ export const monsterTask: MinionTask = { const isOnTaskResult = await isOnSlayerTask({ user, monsterID, quantityKilled: quantity }); const superiorTable = isOnTaskResult.hasSuperiorsUnlocked && monster.superior ? monster.superior : undefined; - const isInCatacombs = !usingCannon ? monster.existsInCatacombs ?? undefined : undefined; + const isInCatacombs = (!usingCannon ? monster.existsInCatacombs ?? undefined : undefined) && !isInWilderness; + + const hasRingOfWealthI = user.gear.wildy.hasEquipped('Ring of wealth (i)') && isInWilderness; + if (hasRingOfWealthI) { + messages.push('\nYour clue scroll chance is doubled due to wearing a Ring of Wealth (i).'); + } const killOptions: MonsterKillOptions = { onSlayerTask: isOnTaskResult.isOnTask, @@ -377,7 +385,11 @@ export const monsterTask: MinionTask = { hasSuperiors: superiorTable, inCatacombs: isInCatacombs, lootTableOptions: { - tertiaryItemPercentageChanges: user.buildCATertiaryItemChanges() + tertiaryItemPercentageChanges: user.buildTertiaryItemChanges( + hasRingOfWealthI, + isInWilderness, + isOnTaskResult.isOnTask + ) } }; @@ -387,18 +399,28 @@ export const monsterTask: MinionTask = { // Calculate superiors and assign loot. let newSuperiorCount = 0; + if (superiorTable && isOnTaskResult.isOnTask) { - let superiorDroprate = 200; - if (user.hasCompletedCATier('elite')) { - superiorDroprate = 150; - messages.push(`${Emoji.CombatAchievements} 25% more common superiors due to Elite CA tier`); - } - for (let i = 0; i < quantity; i++) { - if (roll(superiorDroprate)) newSuperiorCount++; + if (!(isInWilderness && monster.name === 'Bloodveld')) { + let superiorDroprate = 200; + if (isInWilderness) { + superiorDroprate *= 0.9; + messages.push('\n10% more common superiors due to Wilderness Slayer.'); + } + if (user.hasCompletedCATier('elite')) { + superiorDroprate *= 0.75; + messages.push(`\n${Emoji.CombatAchievements} 25% more common superiors due to Elite CA tier.`); + } + + for (let i = 0; i < quantity; i++) { + if (roll(superiorDroprate)) { + newSuperiorCount++; + } + } } } - let masterCapeRolls = user.hasEquippedOrInBank('Slayer master cape') ? newSuperiorCount : 0; + const masterCapeRolls = user.hasEquippedOrInBank('Slayer master cape') ? newSuperiorCount : 0; newSuperiorCount += masterCapeRolls; // Regular loot @@ -408,8 +430,18 @@ export const monsterTask: MinionTask = { if (newSuperiorCount) { // Superior loot and totems if in catacombs - loot.add(superiorTable!.kill(newSuperiorCount)); + loot.add(superiorTable?.kill(newSuperiorCount)); if (isInCatacombs) loot.add('Dark totem base', newSuperiorCount); + if (isInWilderness) loot.add("Larran's key", newSuperiorCount); + } + + // Hill giant key wildy buff + if (isInWilderness && monster.name === 'Hill giant') { + for (let i = 0; i < quantity; i++) { + if (roll(128)) { + loot.add('Giant key'); + } + } } const xpRes: string[] = []; @@ -443,9 +475,9 @@ export const monsterTask: MinionTask = { quantity === 0 ? `${user}, ${user.minionName} died in ALL their kill attempts!${ roll(10) ? ` ${randArrItem(sorryMessages)}` : '' - }` + }` : `${user}, ${user.minionName} finished killing ${quantity} ${monster.name}${superiorMessage}.` + - ` Your ${monster.name} KC is now ${newKC}.\n${xpRes}\n`; + ` Your ${monster.name} KC is now ${newKC}.\n${xpRes}\n`; if (masterCapeRolls > 0) { messages.push(`${Emoji.SlayerMasterCape} You received ${masterCapeRolls}x bonus superior rolls`); @@ -540,15 +572,15 @@ export const monsterTask: MinionTask = { const { quantitySlayed } = isOnTaskResult; const effectiveSlayed = monsterID === Monsters.KrilTsutsaroth.id && - isOnTaskResult.currentTask!.monster_id !== Monsters.KrilTsutsaroth.id + isOnTaskResult.currentTask?.monster_id !== Monsters.KrilTsutsaroth.id ? quantitySlayed! * 2 : monsterID === Monsters.Kreearra.id && - isOnTaskResult.currentTask.monster_id !== Monsters.Kreearra.id - ? quantitySlayed * 4 - : monsterID === Monsters.GrotesqueGuardians.id && - user.user.slayer_unlocks.includes(SlayerTaskUnlocksEnum.DoubleTrouble) - ? quantitySlayed * 2 - : quantitySlayed; + isOnTaskResult.currentTask.monster_id !== Monsters.Kreearra.id + ? quantitySlayed * 4 + : monsterID === Monsters.GrotesqueGuardians.id && + user.user.slayer_unlocks.includes(SlayerTaskUnlocksEnum.DoubleTrouble) + ? quantitySlayed * 2 + : quantitySlayed; /** * Slayer masks/helms @@ -574,18 +606,41 @@ export const monsterTask: MinionTask = { } } - await userStatsUpdate(user.id, u => { - return { - on_task_monster_scores: new Bank(u.on_task_monster_scores as ItemBank).add(bankToAdd).bank, - on_task_with_mask_monster_scores: matchingMaskOrHelm ? newMaskScores.bank : undefined - }; + const currentStats = await user.fetchStats({ on_task_monster_scores: true }); + await userStatsUpdate(user.id, { + on_task_monster_scores: new Bank(currentStats.on_task_monster_scores as ItemBank).add(bankToAdd) + .bank, + on_task_with_mask_monster_scores: matchingMaskOrHelm ? newMaskScores.bank : undefined }); } const quantityLeft = Math.max(0, isOnTaskResult.currentTask!.quantity_remaining - effectiveSlayed); + const isUsingKrystilia = isOnTaskResult.slayerMaster.id === 8; thisTripFinishesTask = quantityLeft === 0; - if (thisTripFinishesTask) { + if (thisTripFinishesTask && isUsingKrystilia) { + const newStats = await userStatsUpdate( + user.id, + { + slayer_wildy_task_streak: { + increment: 1 + } + }, + { slayer_wildy_task_streak: true } + ); + const currentStreak = newStats.slayer_wildy_task_streak; + const points = await calculateSlayerPoints(currentStreak, isOnTaskResult.slayerMaster, user); + const secondNewUser = await user.update({ + slayer_points: { + increment: points + } + }); + str += `\n**You've completed ${currentStreak} wilderness tasks and received ${points} points; giving you a total of ${secondNewUser.newUser.slayer_points}; return to a Slayer master.**`; + if (isOnTaskResult.assignedTask.isBoss) { + str += ` ${await user.addXP({ skillName: SkillsEnum.Slayer, amount: 5000, minimal: true })}`; + str += ' for completing your boss task.'; + } + } else if (thisTripFinishesTask) { const newStats = await userStatsUpdate( user.id, { @@ -609,7 +664,7 @@ export const monsterTask: MinionTask = { } } else { str += `\nYou killed ${effectiveSlayed}x of your ${ - isOnTaskResult.currentTask!.quantity_remaining + isOnTaskResult.currentTask?.quantity_remaining } remaining kills, you now have ${quantityLeft} kills remaining.`; } @@ -634,7 +689,7 @@ export const monsterTask: MinionTask = { await prisma.slayerTask.update({ where: { - id: isOnTaskResult.currentTask!.id + id: isOnTaskResult.currentTask?.id }, data: { quantity_remaining: quantityLeft @@ -685,7 +740,7 @@ export const monsterTask: MinionTask = { title: `Loot From ${quantity} ${monster.name}:`, user, previousCL - }); + }); return handleTripFinish(user, channelID, str, image?.file.attachment, data, itemsAdded, messages); } diff --git a/src/tasks/minions/motherlodeMineActivity.ts b/src/tasks/minions/motherlodeMineActivity.ts index 7781c071aa5..6c25924ca27 100644 --- a/src/tasks/minions/motherlodeMineActivity.ts +++ b/src/tasks/minions/motherlodeMineActivity.ts @@ -1,4 +1,4 @@ -import { roll, Time } from 'e'; +import { Time, roll } from 'e'; import { Bank, LootTable } from 'oldschooljs'; import { MIN_LENGTH_FOR_PET } from '../../lib/constants'; @@ -6,7 +6,7 @@ import { globalDroprates } from '../../lib/data/globalDroprates'; import { FaladorDiary, userhasDiaryTier } from '../../lib/diaries'; import Mining from '../../lib/skilling/skills/mining'; import { SkillsEnum } from '../../lib/skilling/types'; -import { MotherlodeMiningActivityTaskOptions } from '../../lib/types/minions'; +import type { MotherlodeMiningActivityTaskOptions } from '../../lib/types/minions'; import { clAdjustedDroprate, skillingPetDropRate } from '../../lib/util'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; @@ -14,7 +14,7 @@ export const motherlodeMiningTask: MinionTask = { type: 'MotherlodeMining', async run(data: MotherlodeMiningActivityTaskOptions) { const { userID, channelID, duration } = data; - let { quantity } = data; + const { quantity } = data; const user = await mUserFetch(userID); const motherlode = Mining.MotherlodeMine; @@ -25,7 +25,7 @@ export const motherlodeMiningTask: MinionTask = { // If they have the entire prospector outfit, give an extra 0.5% xp bonus if ( user.hasEquippedOrInBank( - Object.keys(Mining.prospectorItems).map(i => parseInt(i)), + Object.keys(Mining.prospectorItems).map(i => Number.parseInt(i)), 'every' ) ) { @@ -35,7 +35,7 @@ export const motherlodeMiningTask: MinionTask = { } else { // For each prospector item, check if they have it, give its' XP boost if so. for (const [itemID, bonus] of Object.entries(Mining.prospectorItems)) { - if (user.hasEquippedOrInBank(parseInt(itemID))) { + if (user.hasEquippedOrInBank(Number.parseInt(itemID))) { const amountToAdd = Math.floor(xpReceived * (bonus / 100)); xpReceived += amountToAdd; bonusXP += amountToAdd; diff --git a/src/tasks/minions/pickpocketActivity.ts b/src/tasks/minions/pickpocketActivity.ts index 6e59db77eae..199fbabe14e 100644 --- a/src/tasks/minions/pickpocketActivity.ts +++ b/src/tasks/minions/pickpocketActivity.ts @@ -1,12 +1,12 @@ -import { percentChance, randInt, roll, Time } from 'e'; +import { Time, percentChance, randInt, roll } from 'e'; import { Bank } from 'oldschooljs'; +import { SkillsEnum } from 'oldschooljs/dist/constants'; -import { chargePortentIfHasCharges, PortentID } from '../../lib/bso/divination'; +import { PortentID, chargePortentIfHasCharges } from '../../lib/bso/divination'; import { ClueTiers } from '../../lib/clues/clueTiers'; import { MIN_LENGTH_FOR_PET } from '../../lib/constants'; -import { Stealable, stealables } from '../../lib/skilling/skills/thieving/stealables'; -import { SkillsEnum } from '../../lib/skilling/types'; -import { PickpocketActivityTaskOptions } from '../../lib/types/minions'; +import { type Stealable, stealables } from '../../lib/skilling/skills/thieving/stealables'; +import type { PickpocketActivityTaskOptions } from '../../lib/types/minions'; import { perHourChance, skillingPetDropRate } from '../../lib/util'; import { forcefullyUnequipItem } from '../../lib/util/forcefullyUnequipItem'; import getOSItem from '../../lib/util/getOSItem'; @@ -77,7 +77,7 @@ export const pickpocketTask: MinionTask = { if (obj.type === 'pickpockable') { for (let i = 0; i < successfulQuantity; i++) { const lootItems = obj.table.roll(1, { - tertiaryItemPercentageChanges: user.buildCATertiaryItemChanges() + tertiaryItemPercentageChanges: user.buildTertiaryItemChanges() }); // TODO: Remove Rocky from loot tables in oldschoolJS if (lootItems.has('Rocky')) lootItems.remove('Rocky'); @@ -103,7 +103,7 @@ export const pickpocketTask: MinionTask = { } } - let boosts: string[] = []; + const boosts: string[] = []; await clueUpgraderEffect(user, loot, boosts, 'pickpocketing'); if (user.hasEquipped("Thieves' armband")) { boosts.push('3x loot for Thieves armband'); @@ -123,7 +123,7 @@ export const pickpocketTask: MinionTask = { const before = loot.clone(); loot.multiply(3, notMultiplied); const after = loot.clone(); - await userStatsBankUpdate(user.id, 'loot_from_rogues_portent', after.difference(before)); + await userStatsBankUpdate(user, 'loot_from_rogues_portent', after.difference(before)); } } @@ -140,7 +140,7 @@ export const pickpocketTask: MinionTask = { updateClientGPTrackSetting('gp_pickpocket', loot.amount('Coins')); } - await userStatsBankUpdate(user.id, 'steal_loot_bank', loot); + await userStatsBankUpdate(user, 'steal_loot_bank', loot); const { previousCL, itemsAdded } = await transactItems({ userID: user.id, @@ -165,7 +165,7 @@ export const pickpocketTask: MinionTask = { ? '' : `${ 100 - obj.lootPercent! - }% of the loot was dropped in favour of enhancing amount of stalls stolen from.` + }% of the loot was dropped in favour of enhancing amount of stalls stolen from.` }`; if (rogueOutfitBoostActivated) { @@ -187,7 +187,7 @@ export const pickpocketTask: MinionTask = { title: `Loot From ${successfulQuantity} ${obj.name}:`, user, previousCL - }); + }); handleTripFinish(user, channelID, str, image?.file.attachment, data, loot); } diff --git a/src/tasks/minions/questingActivity.ts b/src/tasks/minions/questingActivity.ts index 566fa3020f0..29242e5d9bc 100644 --- a/src/tasks/minions/questingActivity.ts +++ b/src/tasks/minions/questingActivity.ts @@ -3,11 +3,11 @@ import { Bank } from 'oldschooljs'; import { Emoji } from '../../lib/constants'; import { globalDroprates } from '../../lib/data/globalDroprates'; +import { MAX_QP } from '../../lib/minions/data/quests'; import { SkillsEnum } from '../../lib/skilling/types'; import type { ActivityTaskOptionsWithQuantity } from '../../lib/types/minions'; import { roll } from '../../lib/util'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; -import { MAX_QP } from '../../mahoji/lib/abstracted_commands/questCommand'; export const questingTask: MinionTask = { type: 'Questing', diff --git a/src/tasks/minions/runecraftActivity.ts b/src/tasks/minions/runecraftActivity.ts index 7d78f308b96..9fe5c1861f4 100644 --- a/src/tasks/minions/runecraftActivity.ts +++ b/src/tasks/minions/runecraftActivity.ts @@ -1,4 +1,4 @@ -import { increaseNumByPercent, Time } from 'e'; +import { Time, increaseNumByPercent } from 'e'; import { Bank } from 'oldschooljs'; import { MIN_LENGTH_FOR_PET } from '../../lib/constants'; diff --git a/src/tasks/minions/smeltingActivity.ts b/src/tasks/minions/smeltingActivity.ts index e6de2ca5103..612d94344b6 100644 --- a/src/tasks/minions/smeltingActivity.ts +++ b/src/tasks/minions/smeltingActivity.ts @@ -1,4 +1,4 @@ -import { percentChance, Time } from 'e'; +import { Time, percentChance } from 'e'; import { Bank } from 'oldschooljs'; import { BlacksmithOutfit } from '../../lib/bsoOpenables'; @@ -24,7 +24,7 @@ export const smeltingTask: MinionTask = { const hasBS = user.hasEquippedOrInBank(BlacksmithOutfit, 'every'); const oldQuantity = quantity; if ((bar.chanceOfFail > 0 && bar.name !== 'Iron bar') || (!blastf && bar.name === 'Iron bar')) { - let chance = masterCapeInEffect ? bar.chanceOfFail / 2 : bar.chanceOfFail; + const chance = masterCapeInEffect ? bar.chanceOfFail / 2 : bar.chanceOfFail; let newQuantity = 0; for (let i = 0; i < quantity; i++) { if (!percentChance(chance)) { diff --git a/src/tasks/minions/specificQuestActivity.ts b/src/tasks/minions/specificQuestActivity.ts index 445bdf798fb..1c45125a7bd 100644 --- a/src/tasks/minions/specificQuestActivity.ts +++ b/src/tasks/minions/specificQuestActivity.ts @@ -1,9 +1,9 @@ import { bold } from 'discord.js'; +import { quests } from '../../lib/minions/data/quests'; import { SkillsEnum } from '../../lib/skilling/types'; import type { SpecificQuestOptions } from '../../lib/types/minions'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; -import { quests } from '../../mahoji/lib/abstracted_commands/questCommand'; export const specificQuestTask: MinionTask = { type: 'SpecificQuest', diff --git a/src/tasks/minions/strongholdOfSecurityActivity.ts b/src/tasks/minions/strongholdOfSecurityActivity.ts index 86bb6551a2b..70d2e30d6ba 100644 --- a/src/tasks/minions/strongholdOfSecurityActivity.ts +++ b/src/tasks/minions/strongholdOfSecurityActivity.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { ActivityTaskOptionsWithNoChanges } from '../../lib/types/minions'; +import type { ActivityTaskOptionsWithNoChanges } from '../../lib/types/minions'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; export const strongholdTask: MinionTask = { diff --git a/src/tasks/minions/tiaraRunecraftActivity.ts b/src/tasks/minions/tiaraRunecraftActivity.ts index 26f9fba1864..4851080ecd2 100644 --- a/src/tasks/minions/tiaraRunecraftActivity.ts +++ b/src/tasks/minions/tiaraRunecraftActivity.ts @@ -2,7 +2,7 @@ import { Bank } from 'oldschooljs'; import Runecraft from '../../lib/skilling/skills/runecraft'; import { SkillsEnum } from '../../lib/skilling/types'; -import { TiaraRunecraftActivityTaskOptions } from '../../lib/types/minions'; +import type { TiaraRunecraftActivityTaskOptions } from '../../lib/types/minions'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; export const tiaraRunecraftTask: MinionTask = { @@ -14,7 +14,7 @@ export const tiaraRunecraftTask: MinionTask = { const tiara = Runecraft.Tiaras.find(_tiara => _tiara.id === tiaraID)!; const xpReceived = tiaraQuantity * tiara.xp; - let xpRes = `\n${await user.addXP({ + const xpRes = `\n${await user.addXP({ skillName: SkillsEnum.Runecraft, amount: xpReceived, duration diff --git a/src/tasks/minions/tokkulShopActivity.ts b/src/tasks/minions/tokkulShopActivity.ts index d9e19207d5b..72b71438693 100644 --- a/src/tasks/minions/tokkulShopActivity.ts +++ b/src/tasks/minions/tokkulShopActivity.ts @@ -1,6 +1,6 @@ import { Bank } from 'oldschooljs'; -import { TokkulShopOptions } from '../../lib/types/minions'; +import type { TokkulShopOptions } from '../../lib/types/minions'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; import { updateBankSetting } from '../../lib/util/updateBankSetting'; diff --git a/src/tasks/minions/underwaterActivity.ts b/src/tasks/minions/underwaterActivity.ts index 6f5e5f583b6..e898ad5c156 100644 --- a/src/tasks/minions/underwaterActivity.ts +++ b/src/tasks/minions/underwaterActivity.ts @@ -2,7 +2,7 @@ import { percentChance } from 'e'; import { Bank, LootTable } from 'oldschooljs'; import { SkillsEnum } from '../../lib/skilling/types'; -import { UnderwaterAgilityThievingTaskOptions } from '../../lib/types/minions'; +import type { UnderwaterAgilityThievingTaskOptions } from '../../lib/types/minions'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; // Bonus loot from clams and chests, TODO: check wiki in future for more accurate rates @@ -16,14 +16,14 @@ const clamChestTable = new LootTable() export const underwaterAgilityThievingTask: MinionTask = { type: 'UnderwaterAgilityThieving', async run(data: UnderwaterAgilityThievingTaskOptions) { - let { quantity, userID, channelID, duration, trainingSkill } = data; + const { quantity, userID, channelID, duration, trainingSkill } = data; const user = await mUserFetch(userID); const currentThievingLevel = user.skillLevel(SkillsEnum.Thieving); const currentAgilityLevel = user.skillLevel(SkillsEnum.Agility); let successful = 0; // Search clam/chest until it becomes inactive chance - let chanceOfSuccess = 0.043_88 * currentThievingLevel + 11.68; + const chanceOfSuccess = 0.043_88 * currentThievingLevel + 11.68; for (let i = 0; i < quantity; i++) { while (percentChance(chanceOfSuccess)) { diff --git a/src/tasks/minions/volcanicMineActivity.ts b/src/tasks/minions/volcanicMineActivity.ts index 496ec2f783c..e4126b17ba1 100644 --- a/src/tasks/minions/volcanicMineActivity.ts +++ b/src/tasks/minions/volcanicMineActivity.ts @@ -1,10 +1,10 @@ -import { randFloat, randInt, roll, Time } from 'e'; +import { Time, randFloat, randInt, roll } from 'e'; import { Bank, LootTable } from 'oldschooljs'; import { userHasFlappy } from '../../lib/invention/inventions'; import { incrementMinigameScore } from '../../lib/settings/settings'; import { SkillsEnum } from '../../lib/skilling/types'; -import { ActivityTaskOptionsWithQuantity } from '../../lib/types/minions'; +import type { ActivityTaskOptionsWithQuantity } from '../../lib/types/minions'; import { skillingPetDropRate } from '../../lib/util'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; import { VolcanicMineGameTime } from '../../mahoji/lib/abstracted_commands/volcanicMineCommand'; diff --git a/src/tasks/minions/wealthChargingActivity.ts b/src/tasks/minions/wealthChargingActivity.ts index 876b4cc6d29..1c37745e737 100644 --- a/src/tasks/minions/wealthChargingActivity.ts +++ b/src/tasks/minions/wealthChargingActivity.ts @@ -1,7 +1,7 @@ import { Bank } from 'oldschooljs'; import { wealthInventorySize } from '../../lib/constants'; -import { ActivityTaskOptionsWithQuantity } from '../../lib/types/minions'; +import type { ActivityTaskOptionsWithQuantity } from '../../lib/types/minions'; import { roll } from '../../lib/util'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; @@ -11,7 +11,7 @@ export const wealthChargeTask: MinionTask = { const { quantity, userID, channelID } = data; const user = await mUserFetch(userID); let deaths = 0; - let loot = new Bank(); + const loot = new Bank(); for (let i = 0; i < quantity; i++) { if (roll(9)) { deaths++; diff --git a/src/tasks/minions/woodcuttingActivity.ts b/src/tasks/minions/woodcuttingActivity.ts index 5616128fe1e..66011eaeb17 100644 --- a/src/tasks/minions/woodcuttingActivity.ts +++ b/src/tasks/minions/woodcuttingActivity.ts @@ -1,7 +1,8 @@ -import { percentChance, randInt, Time } from 'e'; +import { Time, objectEntries, percentChance, randInt, roll } from 'e'; import { Bank } from 'oldschooljs'; -import { MAX_LEVEL, MIN_LENGTH_FOR_PET, TwitcherGloves } from '../../lib/constants'; +import { itemID } from 'oldschooljs/dist/util'; +import { MAX_LEVEL, MIN_LENGTH_FOR_PET, type TwitcherGloves } from '../../lib/constants'; import { MediumSeedPackTable } from '../../lib/data/seedPackTables'; import addSkillingClueToLoot from '../../lib/minions/functions/addSkillingClueToLoot'; import { eggNest } from '../../lib/simulation/birdsNest'; @@ -10,15 +11,15 @@ import Firemaking from '../../lib/skilling/skills/firemaking'; import { ForestryEvents, LeafTable } from '../../lib/skilling/skills/woodcutting/forestry'; import Woodcutting from '../../lib/skilling/skills/woodcutting/woodcutting'; import { SkillsEnum } from '../../lib/skilling/types'; -import { WoodcuttingActivityTaskOptions } from '../../lib/types/minions'; -import { clAdjustedDroprate, itemID, perTimeUnitChance, roll, skillingPetDropRate } from '../../lib/util'; +import type { WoodcuttingActivityTaskOptions } from '../../lib/types/minions'; +import { clAdjustedDroprate, perTimeUnitChance, skillingPetDropRate } from '../../lib/util'; import { handleTripFinish } from '../../lib/util/handleTripFinish'; import resolveItems from '../../lib/util/resolveItems'; import { userStatsBankUpdate } from '../../mahoji/mahojiSettings'; async function handleForestry({ user, duration, loot }: { user: MUser; duration: number; loot: Bank }) { - let eventCounts: { [key: number]: number } = {}; - let eventXP = {} as { [key in SkillsEnum]: number }; + const eventCounts: { [key: number]: number } = {}; + const eventXP = {} as { [key in SkillsEnum]: number }; ForestryEvents.forEach(event => { eventCounts[event.id] = 0; eventXP[event.uniqueXP] = 0; @@ -27,8 +28,11 @@ async function handleForestry({ user, duration, loot }: { user: MUser; duration: let strForestry = ''; const userWcLevel = user.skillLevel(SkillsEnum.Woodcutting); const chanceWcLevel = Math.min(userWcLevel, 99); - const eggChance = Math.ceil(2700 - ((chanceWcLevel - 1) * (2700 - 1350)) / 98); - const whistleChance = Math.ceil(90 - ((chanceWcLevel - 1) * (90 - 45)) / 98); + + const pekyBoost = user.usingPet('Peky') ? 5 : 1; + const eggChance = Math.ceil((2700 - ((chanceWcLevel - 1) * (2700 - 1350)) / 98) / pekyBoost); + const whistleChance = Math.ceil((90 - ((chanceWcLevel - 1) * (90 - 45)) / 98) / pekyBoost); + const garlandChance = Math.ceil(50 / pekyBoost); perTimeUnitChance(duration, 20, Time.Minute, async () => { const eventIndex = randInt(0, ForestryEvents.length - 1); @@ -98,7 +102,7 @@ async function handleForestry({ user, duration, loot }: { user: MUser; duration: break; case 8: // Enchantment Ritual eventInteraction = randInt(6, 8); // ritual circles - if (roll(50)) { + if (roll(garlandChance)) { loot.add('Petal garland'); } eventCounts[event.id]++; @@ -124,25 +128,20 @@ async function handleForestry({ user, duration, loot }: { user: MUser; duration: }); let totalEvents = 0; - for (const event in eventCounts) { - if (eventCounts.hasOwnProperty(event)) { - const count = eventCounts[event]; + for (const [event, count] of objectEntries(eventCounts)) { + if (event && count && count > 0) { totalEvents += count; - await userStatsBankUpdate( - user.id, - 'forestry_event_completions_bank', - new Bank().add(parseInt(event), count) - ); + await userStatsBankUpdate(user, 'forestry_event_completions_bank', new Bank().add(Number(event), count)); } } // Give user xp from events let xpRes = ''; - for (const skill in eventXP) { - if (eventXP.hasOwnProperty(skill)) { + for (const [key, val] of objectEntries(eventXP)) { + if (key && val && val > 0) { xpRes += await user.addXP({ - skillName: skill as SkillsEnum, - amount: Math.ceil(eventXP[skill as SkillsEnum]), + skillName: key, + amount: Math.ceil(val), minimal: true, source: 'ForestryEvents' }); @@ -152,8 +151,8 @@ async function handleForestry({ user, duration, loot }: { user: MUser; duration: // Generate forestry message const completedEvents = Object.entries(eventCounts) .map(([eventId, count]) => { - const event = ForestryEvents.find(e => e.id === parseInt(eventId)); - return count > 0 ? `${count} ${event!.name}` : null; + const event = ForestryEvents.find(e => e.id === Number.parseInt(eventId)); + return count > 0 ? `${count} ${event?.name}` : null; }) .filter(Boolean) .join(' & '); @@ -192,16 +191,16 @@ export const woodcuttingTask: MinionTask = { user.bank.has(['Woodcutting cape', 'Cape pouch']) && userWcLevel >= 99); - let strungRabbitFoot = user.hasEquipped('Strung rabbit foot'); - let twitchersEquipped = user.hasEquipped("twitcher's gloves"); + const strungRabbitFoot = user.hasEquipped('Strung rabbit foot'); + const twitchersEquipped = user.hasEquipped("twitcher's gloves"); let twitcherSetting: TwitcherGloves | undefined = 'egg'; let xpReceived = quantity * log.xp; let bonusXP = 0; let rationUsed = 0; let lostLogs = 0; - let loot = new Bank(); - let itemsToRemove = new Bank(); - let priffUnlocked = user.hasSkillReqs(soteSkillRequirements) && user.QP >= 150; + const loot = new Bank(); + const itemsToRemove = new Bank(); + const priffUnlocked = user.hasSkillReqs(soteSkillRequirements) && user.QP >= 150; // GMC for elder logs let clueChance = log.clueScrollChance; @@ -227,7 +226,7 @@ export const woodcuttingTask: MinionTask = { // If they have the entire lumberjack outfit, give an extra 0.5% xp bonus if ( user.hasEquippedOrInBank( - Object.keys(Woodcutting.lumberjackItems).map(i => parseInt(i)), + Object.keys(Woodcutting.lumberjackItems).map(i => Number.parseInt(i)), 'every' ) ) { @@ -237,7 +236,7 @@ export const woodcuttingTask: MinionTask = { } else { // For each lumberjack item, check if they have it, give its XP boost if so for (const [itemID, bonus] of Object.entries(Woodcutting.lumberjackItems)) { - if (user.hasEquippedOrInBank(parseInt(itemID))) { + if (user.hasEquippedOrInBank(Number.parseInt(itemID))) { const amountToAdd = Math.floor(xpReceived * (bonus / 100)); xpReceived += amountToAdd; bonusXP += amountToAdd; @@ -370,7 +369,7 @@ export const woodcuttingTask: MinionTask = { lostLogs > 0 && !powerchopping ? `You lost ${ log.lootTable ? `${lostLogs}x ${log.name} loot rolls` : `${lostLogs}x ${log.name}` - } due to using a felling axe.` + } due to using a felling axe.` : '' }`; diff --git a/src/tasks/tames/tameTasks.ts b/src/tasks/tames/tameTasks.ts index 661ee6e54f4..b7d247d4c93 100644 --- a/src/tasks/tames/tameTasks.ts +++ b/src/tasks/tames/tameTasks.ts @@ -1,68 +1,116 @@ -import { Tame, TameActivity } from '@prisma/client'; +import type { TameActivity } from '@prisma/client'; import { + type APIInteractionGuildMember, ActionRowBuilder, - APIInteractionGuildMember, AttachmentBuilder, ButtonBuilder, - ButtonInteraction, + type ButtonInteraction, ButtonStyle, - ChatInputCommandInteraction, - GuildMember, + type ChatInputCommandInteraction, + type GuildMember, userMention } from 'discord.js'; -import { increaseNumByPercent, isFunction, percentChance, randArrItem, randInt, roll, Time } from 'e'; +import { Time, increaseNumByPercent, isFunction, percentChance, randArrItem, randInt, roll } from 'e'; import { isEmpty } from 'lodash'; import { Bank } from 'oldschooljs'; -import { ItemBank } from 'oldschooljs/dist/meta/types'; +import type { ItemBank } from 'oldschooljs/dist/meta/types'; import { ClueTiers } from '../../lib/clues/clueTiers'; import { BitField } from '../../lib/constants'; import { handlePassiveImplings } from '../../lib/implings'; import { trackLoot } from '../../lib/lootTrack'; import { allOpenables } from '../../lib/openables'; -import { prisma } from '../../lib/settings/prisma'; + import { runCommand } from '../../lib/settings/settings'; import { getTemporossLoot } from '../../lib/simulation/tempoross'; import { WintertodtCrate } from '../../lib/simulation/wintertodt'; -import { MTame } from '../../lib/structures/MTame'; +import type { MTame } from '../../lib/structures/MTame'; import { - addDurationToTame, - ArbitraryTameActivity, - seaMonkeySpells, - tameKillableMonsters, + type ArbitraryTameActivity, TameSpeciesID, - TameTaskOptions, - TameType + type TameTaskOptions, + TameType, + seaMonkeySpells, + tameKillableMonsters } from '../../lib/tames'; -import { ActivityTaskData } from '../../lib/types/minions'; +import type { ActivityTaskData } from '../../lib/types/minions'; import { assert, calcPerHour, formatDuration, itemNameFromID } from '../../lib/util'; import getOSItem from '../../lib/util/getOSItem'; -import { getUsersTamesCollectionLog } from '../../lib/util/getUsersTameCL'; import { handleCrateSpawns } from '../../lib/util/handleCrateSpawns'; import { makeBankImage } from '../../lib/util/makeBankImage'; -import { tameHasBeenFed, tameLastFinishedActivity, tameName } from '../../lib/util/tameUtil'; +import { tameLastFinishedActivity } from '../../lib/util/tameUtil'; import { sendToChannelID } from '../../lib/util/webhook'; -import { collectables } from '../../mahoji/lib/abstracted_commands/collectCommand'; +import { collectables } from '../../mahoji/lib/collectables'; + +export async function handleFinish({ + lootToAdd, + user, + activity, + tame, + message +}: { lootToAdd: Bank; message: string; user: MUser; tame: MTame; activity: TameActivity }) { + const results: string[] = [message]; + const previousTameCl = tame.totalLoot.clone(); + const crateRes = handleCrateSpawns(user, activity.duration); + if (crateRes !== null) lootToAdd.add(crateRes); + + await prisma.tame.update({ + where: { + id: tame.id + }, + data: { + max_total_loot: previousTameCl.clone().add(lootToAdd).bank, + last_activity_date: new Date() + } + }); + const { itemsAdded } = await user.addItemsToBank({ items: lootToAdd, collectionLog: false }); + results.push(`Received ${itemsAdded}`); + + const addRes = await tame.addDuration(activity.duration); + if (addRes) results.push(addRes); + + return sendToChannelID(activity.channel_id, { + content: results.join(', '), + components: [ + new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId('REPEAT_TAME_TRIP') + .setLabel('Repeat Trip') + .setStyle(ButtonStyle.Secondary) + ) + ], + files: + itemsAdded.length > 0 + ? [ + new AttachmentBuilder( + ( + await makeBankImage({ + bank: lootToAdd, + title: `${tame}'s Loot`, + user: user, + previousCL: previousTameCl + }) + ).file.attachment + ) + ] + : undefined + }); +} export const arbitraryTameActivities: ArbitraryTameActivity[] = [ { name: 'Tempoross', id: 'Tempoross', allowedTames: [TameSpeciesID.Monkey], - run: async ({ handleFinish, user, duration, tame }) => { + run: async ({ handleFinish, user, duration, tame, previousTameCL, activity }) => { const quantity = Math.ceil(duration / (Time.Minute * 5)); - const loot = getTemporossLoot( - quantity, - tame.max_gatherer_level + 15, - await getUsersTamesCollectionLog(user.id) - ); - const { itemsAdded } = await user.addItemsToBank({ items: loot, collectionLog: false }); + const loot = getTemporossLoot(quantity, tame.maxGathererLevel + 15, previousTameCL); handleFinish({ - loot: itemsAdded, - message: `${user}, ${tameName( - tame - )} finished defeating Tempoross ${quantity}x times, and received ${loot}.`, - user + lootToAdd: loot, + message: `${user}, ${tame} finished defeating Tempoross ${quantity}x times.`, + user, + tame, + activity }); } }, @@ -70,7 +118,7 @@ export const arbitraryTameActivities: ArbitraryTameActivity[] = [ name: 'Wintertodt', id: 'Wintertodt', allowedTames: [TameSpeciesID.Igne], - run: async ({ handleFinish, user, duration, tame }) => { + run: async ({ handleFinish, user, duration, tame, activity }) => { const quantity = Math.ceil(duration / (Time.Minute * 5)); const loot = new Bank(); for (let i = 0; i < quantity; i++) { @@ -83,29 +131,17 @@ export const arbitraryTameActivities: ArbitraryTameActivity[] = [ }) ); } - const { itemsAdded } = await user.addItemsToBank({ items: loot, collectionLog: false }); handleFinish({ - loot: itemsAdded, - message: `${user}, ${tameName( - tame - )} finished defeating Wintertodt ${quantity}x times, and received ${loot}.`, - user + lootToAdd: loot, + message: `${user}, ${tame} finished defeating Wintertodt ${quantity}x times.`, + user, + activity, + tame }); } } ]; -function doubleLootCheck(tame: Tame, loot: Bank) { - const hasMrE = tameHasBeenFed(tame, 'Mr. E'); - let doubleLootMsg = ''; - if (hasMrE && roll(12)) { - loot.multiply(2); - doubleLootMsg = '\n**2x Loot from Mr. E**'; - } - - return { loot, doubleLootMsg }; -} - async function handleImplingLocator(user: MUser, tame: MTame, duration: number, loot: Bank, messages: string[]) { if (tame.hasBeenFed('Impling locator')) { const result = await handlePassiveImplings( @@ -131,7 +167,7 @@ async function handleImplingLocator(user: MUser, tame: MTame, duration: number, self: openable, totalLeaguesPoints: 0 }) - ).bank + ).bank : openable.output.roll(qty) ); } @@ -142,56 +178,9 @@ async function handleImplingLocator(user: MUser, tame: MTame, duration: number, } } -export async function runTameTask(activity: TameActivity, tame: Tame) { +export async function runTameTask(activity: TameActivity, tame: MTame) { const user = await mUserFetch(activity.user_id); - async function handleFinish(res: { loot: Bank | null; message: string; user: MUser }) { - const previousTameCl = new Bank({ ...(tame.max_total_loot as ItemBank) }); - - if (res.loot) { - await prisma.tame.update({ - where: { - id: tame.id - }, - data: { - max_total_loot: previousTameCl.clone().add(res.loot.bank).bank, - last_activity_date: new Date() - } - }); - } - const addRes = await addDurationToTame(tame, activity.duration); - if (addRes) res.message += `\n${addRes}`; - - const crateRes = await handleCrateSpawns(user, activity.duration); - if (crateRes !== null) res.message += `\n${crateRes}`; - - sendToChannelID(activity.channel_id, { - content: res.message, - components: [ - new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId('REPEAT_TAME_TRIP') - .setLabel('Repeat Trip') - .setStyle(ButtonStyle.Secondary) - ) - ], - files: res.loot - ? [ - new AttachmentBuilder( - ( - await makeBankImage({ - bank: res.loot, - title: `${tameName(tame)}'s Loot`, - user: res.user, - previousCL: previousTameCl - }) - ).file.attachment - ) - ] - : undefined - }); - } - const activityData = activity.data as any as TameTaskOptions; switch (activityData.type) { case 'pvm': { @@ -201,15 +190,17 @@ export async function runTameTask(activity: TameActivity, tame: Tame) { let killQty = quantity - activity.deaths; if (killQty < 1) { handleFinish({ - loot: null, + lootToAdd: new Bank(), message: `${userMention(user.id)}, Your tame died in all their attempts to kill ${ mon.name }. Get them some better armor!`, - user + user, + activity, + tame }); return; } - const hasOri = tameHasBeenFed(tame, 'Ori'); + const hasOri = tame.hasBeenFed('Ori'); const oriIsApplying = hasOri && mon.oriWorks !== false; // If less than 8 kills, roll 25% chance per kill @@ -225,24 +216,21 @@ export async function runTameTask(activity: TameActivity, tame: Tame) { const loot = mon.loot({ quantity: killQty, tame }); const messages: string[] = []; - let str = `${user}, ${tameName(tame)} finished killing ${quantity}x ${mon.name}.${ - activity.deaths > 0 ? ` ${tameName(tame)} died ${activity.deaths}x times.` : '' + let str = `${user}, ${tame} finished killing ${quantity}x ${mon.name}.${ + activity.deaths > 0 ? ` ${tame} died ${activity.deaths}x times.` : '' }`; if (oriIsApplying) { messages.push('25% extra loot (ate an Ori)'); } - const { doubleLootMsg } = doubleLootCheck(tame, loot); + const { doubleLootMsg } = tame.doubleLootCheck(loot); str += doubleLootMsg; - - const mTame = new MTame(tame); - await handleImplingLocator(user, mTame, activity.duration, loot, messages); + await handleImplingLocator(user, tame, activity.duration, loot, messages); if (messages.length > 0) { str += `\n\n**Messages:** ${messages.join(', ')}.`; } - const { itemsAdded } = await user.addItemsToBank({ items: loot, collectionLog: false }); await trackLoot({ duration: activity.duration, kc: activityData.quantity, @@ -254,15 +242,17 @@ export async function runTameTask(activity: TameActivity, tame: Tame) { users: [ { id: user.id, - loot: itemsAdded, + loot, duration: activity.duration } ] }); handleFinish({ - loot: itemsAdded, + lootToAdd: loot, message: str, - user + user, + activity, + tame }); break; } @@ -271,45 +261,46 @@ export async function runTameTask(activity: TameActivity, tame: Tame) { const collectable = collectables.find(c => c.item.id === itemID)!; const totalQuantity = quantity * collectable.quantity; const loot = new Bank().add(collectable.item.id, totalQuantity); - let str = `${user}, ${tameName(tame)} finished collecting ${totalQuantity}x ${ + let str = `${user}, ${tame} finished collecting ${totalQuantity}x ${ collectable.item.name }. (${Math.round((totalQuantity / (activity.duration / Time.Minute)) * 60).toLocaleString()}/hr)`; - const { doubleLootMsg } = doubleLootCheck(tame, loot); + const { doubleLootMsg } = tame.doubleLootCheck(loot); str += doubleLootMsg; - const { itemsAdded } = await user.addItemsToBank({ items: loot, collectionLog: false }); handleFinish({ - loot: itemsAdded, + lootToAdd: loot, message: str, - user + user, + tame, + activity }); break; } case 'SpellCasting': { const spell = seaMonkeySpells.find(s => s.id === activityData.spellID)!; const loot = new Bank(activityData.loot); - let str = `${user}, ${tameName(tame)} finished casting the ${spell.name} spell for ${formatDuration( + let str = `${user}, ${tame} finished casting the ${spell.name} spell for ${formatDuration( activity.duration )}. ${loot .items() .map(([item, qty]) => `${Math.floor(calcPerHour(qty, activity.duration)).toFixed(1)}/hr ${item.name}`) .join(', ')}`; - const { doubleLootMsg } = doubleLootCheck(tame, loot); + const { doubleLootMsg } = tame.doubleLootCheck(loot); str += doubleLootMsg; - const { itemsAdded } = await user.addItemsToBank({ items: loot, collectionLog: false }); handleFinish({ - loot: itemsAdded, + lootToAdd: loot, message: str, - user + user, + activity, + tame }); break; } case 'Clues': { - const mTame = new MTame(tame); const clueTier = ClueTiers.find(c => c.scrollID === activityData.clueID)!; const messages: string[] = []; const loot = new Bank(); - await handleImplingLocator(user, mTame, activity.duration, loot, messages); + await handleImplingLocator(user, tame, activity.duration, loot, messages); let actualOpenQuantityWithBonus = 0; for (let i = 0; i < activityData.quantity; i++) { @@ -317,7 +308,7 @@ export async function runTameTask(activity: TameActivity, tame: Tame) { } if (clueTier.name === 'Master' && !user.bitfield.includes(BitField.DisabledTameClueOpening)) { - const hasDivineRing = mTame.hasEquipped('Divine ring'); + const hasDivineRing = tame.hasEquipped('Divine ring'); const percentChanceOfGMC = hasDivineRing ? 3.5 : 1.5; for (let i = 0; i < activityData.quantity; i++) { if (percentChance(percentChanceOfGMC)) { @@ -332,7 +323,7 @@ export async function runTameTask(activity: TameActivity, tame: Tame) { } else { const openingLoot = clueTier.table.open(actualOpenQuantityWithBonus, user); - if (mTame.hasEquipped('Abyssal jibwings') && clueTier !== ClueTiers[0]) { + if (tame.hasEquipped('Abyssal jibwings') && clueTier !== ClueTiers[0]) { const lowerTier = ClueTiers[ClueTiers.indexOf(clueTier) - 1]; const abysJwLoot = new Bank(); let bonusClues = 0; @@ -344,28 +335,28 @@ export async function runTameTask(activity: TameActivity, tame: Tame) { } if (abysJwLoot.length > 0) { loot.add(abysJwLoot); - await mTame.addToStatsBank('abyssal_jibwings_loot', abysJwLoot); + await tame.addToStatsBank('abyssal_jibwings_loot', abysJwLoot); messages.push( `You received the loot from ${bonusClues}x ${lowerTier.name} caskets from your Abyssal jibwings.` ); } - } else if (mTame.hasEquipped('3rd age jibwings') && openingLoot.has('Coins')) { + } else if (tame.hasEquipped('3rd age jibwings') && openingLoot.has('Coins')) { const thirdAgeJwLoot = new Bank().add('Coins', openingLoot.amount('Coins')); loot.add(thirdAgeJwLoot); messages.push(`You received ${thirdAgeJwLoot} from your 3rd age jibwings.`); - await mTame.addToStatsBank('third_age_jibwings_loot', thirdAgeJwLoot); + await tame.addToStatsBank('third_age_jibwings_loot', thirdAgeJwLoot); } loot.add(openingLoot); } - if (mTame.hasBeenFed('Elder knowledge') && roll(15)) { - await mTame.addToStatsBank('elder_knowledge_loot_bank', loot); + if (tame.hasBeenFed('Elder knowledge') && roll(15)) { + await tame.addToStatsBank('elder_knowledge_loot_bank', loot); loot.multiply(2); messages.push('2x loot from Elder knowledge (1/15 chance)'); } - let str = `${user}, ${mTame} finished completing ${activityData.quantity}x ${itemNameFromID( + let str = `${user}, ${tame} finished completing ${activityData.quantity}x ${itemNameFromID( clueTier.scrollID )}. (${Math.floor(calcPerHour(activityData.quantity, activity.duration)).toFixed(1)} clues/hr)`; @@ -373,18 +364,34 @@ export async function runTameTask(activity: TameActivity, tame: Tame) { str += `\n\n${messages.join('\n')}`; } - const { itemsAdded } = await user.addItemsToBank({ items: loot, collectionLog: false }); handleFinish({ - loot: itemsAdded, + lootToAdd: loot, message: str, - user + user, + activity, + tame }); break; } case 'Tempoross': case 'Wintertodt': { + const previousTameCL = await prisma.userStats.findFirst({ + where: { + user_id: BigInt(user.id) + }, + select: { + tame_cl_bank: true + } + }); const act = arbitraryTameActivities.find(i => i.id === activityData.type)!; - await act.run({ handleFinish, user, tame, duration: activity.duration }); + await act.run({ + handleFinish, + user, + tame, + duration: activity.duration, + previousTameCL: new Bank(previousTameCL?.tame_cl_bank as ItemBank), + activity + }); break; } default: { diff --git a/src/tsconfig.json b/src/tsconfig.json index fd54d746458..c6921b073fc 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -3,10 +3,9 @@ "compilerOptions": { "outDir": "../dist", "rootDir": ".", - "tsBuildInfoFile": "../dist/.tsbuildinfo", "composite": true, - "declaration": true, - "incremental": true + "tsBuildInfoFile": "../dist/.tsbuildinfo" }, - "include": [".", "./**/*.json"] + "include": [".", "./**/*.json"], + "exclude": ["./scripts", "**/node_modules"] } diff --git a/tests/globalSetup.ts b/tests/globalSetup.ts index 72c831572d8..fa419be8420 100644 --- a/tests/globalSetup.ts +++ b/tests/globalSetup.ts @@ -1,14 +1,23 @@ -import '../src/index'; -import '../src/lib/roboChimp'; +import '../src/lib/safeglobals'; +import { Decimal } from '@prisma/client/runtime/library'; import { Collection } from 'discord.js'; import { vi } from 'vitest'; +import { MUserStats } from '../src/lib/structures/MUserStats'; vi.mock('@oldschoolgg/toolkit', async () => { - const actualToolkit = await vi.importActual('@oldschoolgg/toolkit'); // Import all actual exports + const actual: any = await vi.importActual('@oldschoolgg/toolkit'); return { - ...actualToolkit, // Include all actual exports in the mock - mentionCommand: vi.fn().mockReturnValue('') // Mock mentionCommand to return a blank string + ...actual, + mentionCommand: async (_args: any) => 'hi' + }; +}); + +vi.mock('../node_modules/@oldschoolgg/toolkit/src/util/discord.ts', async () => { + const actualToolkit = await vi.importActual('../node_modules/@oldschoolgg/toolkit/src/util/discord.ts'); + return { + ...actualToolkit, + mentionCommand: vi.fn().mockReturnValue('') }; }); @@ -18,21 +27,159 @@ global.globalClient = { guilds: { cache: new Collection() }, mahojiClient: { commands: { - values: [ - { - name: 'test', + values: () => + ['test'].map(n => ({ + name: n, description: 'test description', attributes: { description: 'test description' }, - options: [] - } - ] + options: [{ name: 'claim' }] + })) } }, users: { cache: new Collection() }, channels: { - cache: new Collection() + cache: new Collection().set('1', { id: '1' }) }, busyCounterCache: new Map() } as any; + +if (!process.env.TEST) { + throw new Error('This file should only be imported in tests.'); +} + +MUserStats.fromID = async (id: string) => { + return new MUserStats({ + user_id: BigInt(id), + sell_gp: 0n, + items_sold_bank: {}, + puropuro_implings_bank: {}, + passive_implings_bank: {}, + create_cost_bank: {}, + create_loot_bank: {}, + bird_eggs_offered_bank: {}, + scattered_ashes_bank: {}, + gf_weapons_made: {}, + gf_cost: {}, + gf_loot: {}, + ash_sanctifier_prayer_xp: 0n, + gotr_rift_searches: 0, + farming_plant_cost_bank: {}, + farming_harvest_loot_bank: {}, + cl_array: [ + 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 313, 436, 526, 554, 557, 558, 562, 877, 882, 995, 1139, + 1203, 1440, 1965, 2485, 2677, 23182 + ], + cl_array_length: 27, + bars_from_adze_bank: {}, + ores_from_spirits_bank: {}, + bars_from_klik_bank: {}, + portable_tanner_bank: {}, + clue_upgrader_bank: {}, + ic_cost_bank: {}, + ic_loot_bank: {}, + loot_from_zippy_bank: {}, + peky_loot_bank: {}, + obis_loot_bank: {}, + brock_loot_bank: {}, + wilvus_loot_bank: {}, + doug_loot_bank: {}, + harry_loot_bank: {}, + smokey_loot_bank: {}, + doubled_loot_bank: {}, + silverhawk_boots_passive_xp: 0n, + bonecrusher_prayer_xp: 0n, + ic_donations_given_bank: {}, + ic_donations_received_bank: {}, + lamped_xp: {}, + tame_cl_bank: {}, + tinker_workshop_mats_bank: {}, + buy_cost_bank: {}, + buy_loot_bank: {}, + tworkshop_material_cost_bank: {}, + tworkshop_xp_gained: 0, + shades_of_morton_cost_bank: {}, + gp_from_agil_pyramid: 0, + random_event_completions_bank: {}, + death_touched_darts_used: 0, + toa_attempts: 0, + toa_cost: {}, + toa_loot: {}, + total_toa_points: 0, + toa_raid_levels_bank: {}, + total_toa_duration_minutes: 0, + on_task_monster_scores: {}, + on_task_with_mask_monster_scores: {}, + deaths: 0, + pk_evasion_exp: 0, + dice_wins: 0, + dice_losses: 0, + duel_losses: 0, + duel_wins: 0, + fight_caves_attempts: 0, + firecapes_sacrificed: 0, + tithe_farms_completed: 0, + tithe_farm_points: 0, + pest_control_points: 0, + inferno_attempts: 0, + infernal_cape_sacrifices: 0, + tob_attempts: 0, + foundry_reputation: 0, + tob_hard_attempts: 0, + total_cox_points: 0, + honour_level: 1, + high_gambles: 0, + honour_points: 0, + slayer_task_streak: 0, + slayer_wildy_task_streak: 0, + slayer_superior_count: 0, + slayer_unsired_offered: 0, + slayer_chewed_offered: 0, + tob_cost: {}, + tob_loot: {}, + creature_scores: {}, + monster_scores: { '1118': 514 }, + laps_scores: {}, + sacrificed_bank: {}, + openable_scores: {}, + gp_luckypick: 0n, + gp_dice: 0n, + gp_slots: 0n, + gp_hotcold: 0n, + total_gp_traded: 0n, + last_daily_timestamp: 0n, + last_tears_of_guthix_timestamp: 0n, + herbs_cleaned_while_farming_bank: {}, + forestry_event_completions_bank: {}, + main_server_challenges_won: 0, + doa_attempts: 0, + doa_cost: {}, + doa_loot: {}, + doa_room_attempts_bank: {}, + doa_total_minutes_raided: 0, + chincannon_destroyed_loot_bank: {}, + comp_cape_percent: new Decimal(0), + untrimmed_comp_cape_percent: new Decimal(0), + god_favour_bank: null, + god_items_sacrificed_bank: {}, + steal_cost_bank: {}, + steal_loot_bank: {}, + xp_from_graceful_portent: 0n, + xp_from_dungeon_portent: 0n, + xp_from_mining_portent: 0n, + xp_from_hunter_portent: 0n, + loot_from_rogues_portent: {}, + loot_from_lucky_portent: {}, + loot_destroyed_by_hunter_portent: {}, + divination_loot: {}, + octo_loot_bank: {}, + turaels_trials_cost_bank: {}, + turaels_trials_loot_bank: {}, + colo_cost: {}, + colo_loot: {}, + colo_kc_bank: {}, + colo_max_glory: null, + quivers_sacrificed: 0 + }); +}; diff --git a/tests/integration/MUser.test.ts b/tests/integration/MUser.test.ts index 7723af28c1e..582a117a931 100644 --- a/tests/integration/MUser.test.ts +++ b/tests/integration/MUser.test.ts @@ -1,13 +1,12 @@ import { activity_type_enum } from '@prisma/client'; -import { objectEntries, randArrItem, randInt, Time } from 'e'; +import { Time, objectEntries, randArrItem, randInt } from 'e'; import { Bank } from 'oldschooljs'; -import { ItemBank } from 'oldschooljs/dist/meta/types'; +import type { ItemBank } from 'oldschooljs/dist/meta/types'; import { convertLVLtoXP } from 'oldschooljs/dist/util'; import { describe, expect, test } from 'vitest'; import { ClueTiers } from '../../src/lib/clues/clueTiers'; import { GLOBAL_BSO_XP_MULTIPLIER } from '../../src/lib/constants'; -import { prisma } from '../../src/lib/settings/prisma'; import { SkillsEnum } from '../../src/lib/skilling/types'; import { assert } from '../../src/lib/util/logError'; import { mahojiUsersSettingsFetch } from '../../src/mahoji/mahojiSettings'; @@ -112,11 +111,11 @@ describe('MUser', () => { test('skillsAsLevels/skillsAsXP', async () => { const user = await createTestUser(); for (const [key, val] of objectEntries(user.skillsAsLevels)) { - let expectedVal = key === 'hitpoints' ? 10 : 1; + const expectedVal = key === 'hitpoints' ? 10 : 1; expect(val).toEqual(expectedVal); } for (const [key, val] of objectEntries(user.skillsAsXP)) { - let expectedVal = key === 'hitpoints' ? convertLVLtoXP(10) : convertLVLtoXP(1); + const expectedVal = key === 'hitpoints' ? convertLVLtoXP(10) : convertLVLtoXP(1); expect(val).toEqual(expectedVal); } expect(user.skillsAsLevels.dungeoneering).toEqual(1); @@ -166,8 +165,8 @@ describe('MUser', () => { group_activity: false, data: { userID: user.id, - clueID: tier.id, - quantity: randInt(1, 10) + ci: tier.id, + q: randInt(1, 10) } }); } diff --git a/tests/integration/allCommandsBase.test.ts b/tests/integration/allCommandsBase.test.ts index 59800bd64a8..83ab9129acd 100644 --- a/tests/integration/allCommandsBase.test.ts +++ b/tests/integration/allCommandsBase.test.ts @@ -1,98 +1,101 @@ -import { describe, test, vi } from 'vitest'; +import { generateCommandInputs, generateRandomBank } from '@oldschoolgg/toolkit'; +import { Time, shuffleArr } from 'e'; +import { expect, test, vi } from 'vitest'; -import { activitiesCommand } from '../../src/mahoji/commands/activities'; -import { askCommand } from '../../src/mahoji/commands/ask'; -import { bankCommand } from '../../src/mahoji/commands/bank'; -import { bsCommand } from '../../src/mahoji/commands/bs'; -import { buildCommand } from '../../src/mahoji/commands/build'; -import { buyCommand } from '../../src/mahoji/commands/buy'; -import { chooseCommand } from '../../src/mahoji/commands/choose'; -import { chopCommand } from '../../src/mahoji/commands/chop'; -import { claimCommand } from '../../src/mahoji/commands/claim'; -import { clueCommand } from '../../src/mahoji/commands/clue'; -import { createCommand } from '../../src/mahoji/commands/create'; -import { farmingCommand } from '../../src/mahoji/commands/farming'; -import { fishCommand } from '../../src/mahoji/commands/fish'; -import { fletchCommand } from '../../src/mahoji/commands/fletch'; -import { gpCommand } from '../../src/mahoji/commands/gp'; -import { huntCommand } from '../../src/mahoji/commands/hunt'; -import { lapsCommand } from '../../src/mahoji/commands/laps'; -import { leaderboardCommand } from '../../src/mahoji/commands/leaderboard'; -import { lightCommand } from '../../src/mahoji/commands/light'; -import { lootCommand } from '../../src/mahoji/commands/loot'; -import { minigamesCommand } from '../../src/mahoji/commands/minigames'; -import { minionCommand } from '../../src/mahoji/commands/minion'; -import { openCommand } from '../../src/mahoji/commands/open'; -import { patreonCommand } from '../../src/mahoji/commands/patreon'; -import { payCommand } from '../../src/mahoji/commands/pay'; -import { pohCommand } from '../../src/mahoji/commands/poh'; -import { priceCommand } from '../../src/mahoji/commands/price'; -import { raidCommand } from '../../src/mahoji/commands/raid'; -import { rollCommand } from '../../src/mahoji/commands/roll'; -import { runecraftCommand } from '../../src/mahoji/commands/runecraft'; -import { slayerCommand } from '../../src/mahoji/commands/slayer'; -import { smeltingCommand } from '../../src/mahoji/commands/smelt'; -import { stealCommand } from '../../src/mahoji/commands/steal'; -import { toolsCommand } from '../../src/mahoji/commands/tools'; -import { OSBMahojiCommand } from '../../src/mahoji/lib/util'; +import { BitField, minionActivityCache } from '../../src/lib/constants'; +import { mahojiClientSettingsFetch } from '../../src/lib/util/clientSettings'; +import { handleMahojiConfirmation } from '../../src/lib/util/handleMahojiConfirmation'; +import { allCommands } from '../../src/mahoji/commands/allCommands'; import { randomMock } from './setup'; -import { createTestUser } from './util'; +import { createTestUser, mockClient } from './util'; -const commands: [OSBMahojiCommand, null | object][] = [ - [activitiesCommand, null], - [askCommand, null], - [bankCommand, null], - [bsCommand, null], - [clueCommand, null], - [claimCommand, null], - [farmingCommand, null], - [gpCommand, null], - [lapsCommand, null], - [leaderboardCommand, null], - [fletchCommand, null], - [fishCommand, null], - [createCommand, { item: 'asdf' }], - [chopCommand, null], - [chooseCommand, { list: 'a,a,a' }], - [buildCommand, null], - [buyCommand, null], - [huntCommand, null], - [lightCommand, null], - [lootCommand, null], - [minionCommand, null], - [minigamesCommand, null], - [runecraftCommand, { rune: 'blood rune' }], - [stealCommand, null], - [rollCommand, null], - [raidCommand, null], - [priceCommand, null], - [openCommand, null], - [patreonCommand, null], - [payCommand, { user: { user: { id: '2' } } }], - [pohCommand, null], - [slayerCommand, null], - [toolsCommand, null], - [stealCommand, null], - [smeltingCommand, null] -]; +test( + 'All Commands Base Test', + async () => { + const bank = generateRandomBank(500, 100_000); + expect(vi.isMockFunction(handleMahojiConfirmation)).toBe(true); + const client = await mockClient(); + process.env.CLIENT_ID = client.data.id; + randomMock(); + const maxUser = await createTestUser(bank, { GP: 100_000_000_000 }); + await maxUser.max(); + await maxUser.update({ bitfield: [BitField.isModerator] }); + await mahojiClientSettingsFetch({ construction_cost_bank: true }); + await prisma.activity.deleteMany({ + where: { + user_id: BigInt(maxUser.id) + } + }); + + const ignoredCommands = [ + 'leagues', + 'bank', + 'bingo', + 'bossrecords', + 'stats', + 'clues', + 'kc', + 'simulate', + 'lvl', + 'testpotato', + 'xp', + 'wiki', + 'casket', + 'finish', + 'kill', + 'trivia', + 'ge', + 'rp', + 'cl', + 'gearpresets' + ]; + const cmds = allCommands; -// Don't let any of these commands create an activity -vi.mock('../../src/lib/util/addSubTaskToActivityTask', async () => { - const actual: any = await vi.importActual('../../src/lib/util/addSubTaskToActivityTask'); - return { - ...actual, - default: async (args: any) => { - console.log(`Sending ${args}`); + for (const command of cmds) { + if (ignoredCommands.includes(command.name)) continue; + if (cmds.some(c => c.name === command.name)) continue; + throw new Error( + `If you added a new command (${command.name}), you need to put it in the allCommandsBase.test.ts file.` + ); } - }; -}); -describe('All Commands Base Test', async () => { - randomMock(); - const user = await createTestUser(); - for (const [command, options] of commands) { - test(`Run ${command.name} command`, async () => { - await user.runCommand(command, options ?? {}); - }); + const ignoredSubCommands = [ + ['tools', 'patron', 'cl_bank'], + ['loot', 'view'], + ['minion', 'bankbg'] + ]; + + const promises = []; + + for (const command of cmds) { + if (ignoredCommands.includes(command.name)) continue; + const options = shuffleArr(await generateCommandInputs(command.options!)).slice(0, 5); + outer: for (const option of options) { + for (const [parent, sub, subCommand] of ignoredSubCommands) { + if (command.name === parent && option[sub] && (subCommand ? option[sub][subCommand] : true)) { + continue outer; + } + } + + promises.push(async () => { + try { + await maxUser.runCommand(command, option); + minionActivityCache.clear(); + } catch (err) { + console.error( + `Failed to run command ${command.name} with options ${JSON.stringify(option)}: ${err}` + ); + throw err; + } + }); + } + } + + await Promise.all(promises); + + await client.processActivities(); + }, + { + timeout: Time.Minute * 10 } -}); +); diff --git a/tests/integration/clArrayUpdate.test.ts b/tests/integration/clArrayUpdate.test.ts new file mode 100644 index 00000000000..15f0917f16c --- /dev/null +++ b/tests/integration/clArrayUpdate.test.ts @@ -0,0 +1,37 @@ +import { expect, test } from 'vitest'; + +import { Bank } from 'oldschooljs'; +import { itemID } from 'oldschooljs/dist/util'; +import { roboChimpSyncData } from '../../src/lib/roboChimp'; +import { createTestUser } from './util'; + +test('CL Updates', async () => { + const user = await createTestUser(); + await user.addItemsToBank({ items: new Bank().add('Coal', 100) }); + await roboChimpSyncData(user); + expect(await user.fetchStats({ cl_array: true, cl_array_length: true })).toMatchObject({ + cl_array: [], + cl_array_length: 0 + }); + + await user.addItemsToBank({ items: new Bank().add('Egg', 100), collectionLog: true }); + await roboChimpSyncData(user); + expect(await user.fetchStats({ cl_array: true, cl_array_length: true })).toMatchObject({ + cl_array: [itemID('Egg')], + cl_array_length: 1 + }); + + await user.addItemsToBank({ items: new Bank().add('Egg', 100), collectionLog: true }); + await roboChimpSyncData(user); + expect(await user.fetchStats({ cl_array: true, cl_array_length: true })).toMatchObject({ + cl_array: [itemID('Egg')], + cl_array_length: 1 + }); + + await user.addItemsToBank({ items: new Bank().add('Trout', 100), collectionLog: true }); + await roboChimpSyncData(user); + expect(await user.fetchStats({ cl_array: true, cl_array_length: true })).toMatchObject({ + cl_array: [itemID('Trout'), itemID('Egg')], + cl_array_length: 2 + }); +}); diff --git a/tests/integration/commands/dice.test.ts b/tests/integration/commands/dice.test.ts index 47a09086e53..fe6d0c90468 100644 --- a/tests/integration/commands/dice.test.ts +++ b/tests/integration/commands/dice.test.ts @@ -3,8 +3,7 @@ import { Bank } from 'oldschooljs'; import { beforeEach, describe, expect, test, vi } from 'vitest'; import { gambleCommand } from '../../../src/mahoji/commands/gamble'; -import { randomMock } from '../setup'; -import { createTestUser, mockClient } from '../util'; +import { createTestUser, mockClient, mockMathRandom } from '../util'; vi.mock('../../../src/lib/util', async () => { const actual: any = await vi.importActual('../../../src/lib/util'); @@ -26,24 +25,26 @@ describe('Dice Command', async () => { }); test('Lose dice', async () => { - randomMock(); await user.gpMatch(100_000_000); + const unmock = mockMathRandom(0.1); const result = await user.runCommand(gambleCommand, { dice: { amount: '100m' } }); - expect(result).toMatchObject(`<@${user.id}> rolled **11** on the percentile dice, and you lost -100m GP.`); + expect(result).toMatchObject('Unknown rolled **11** on the percentile dice, and you lost -100m GP.'); await user.gpMatch(0); await user.statsMatch('dice_losses', 1); await user.statsMatch('gp_dice', BigInt(-100_000_000)); await client.expectValueMatch('gp_dice', BigInt(-100_000_000)); + unmock(); }); test('Won dice', async () => { - randomMock(0.9); + const unmock = mockMathRandom(0.9); await user.gpMatch(100_000_000); const result = await user.runCommand(gambleCommand, { dice: { amount: '100m' } }); - expect(result).toMatchObject(`<@${user.id}> rolled **91** on the percentile dice, and you won 100m GP.`); + expect(result).toMatchObject('Unknown rolled **91** on the percentile dice, and you won 100m GP.'); await user.gpMatch(200_000_000); await user.statsMatch('dice_wins', 1); await user.statsMatch('gp_dice', BigInt(100_000_000)); await client.expectValueMatch('gp_dice', BigInt(100_000_000)); + unmock(); }); }); diff --git a/tests/integration/commands/ironman.test.ts b/tests/integration/commands/ironman.test.ts index 5ef80a99500..f777d831b01 100644 --- a/tests/integration/commands/ironman.test.ts +++ b/tests/integration/commands/ironman.test.ts @@ -1,10 +1,9 @@ import { miniID } from '@oldschoolgg/toolkit'; -import { Prisma } from '@prisma/client'; +import type { Prisma } from '@prisma/client'; import { Time } from 'e'; import { Bank } from 'oldschooljs'; import { describe, expect, test } from 'vitest'; -import { prisma } from '../../../src/lib/settings/prisma'; import { ironmanCommand } from '../../../src/mahoji/lib/abstracted_commands/ironmanCommand'; import { mockedId } from '../util'; diff --git a/tests/integration/commands/open.test.ts b/tests/integration/commands/open.test.ts index 8608ea04d04..f26f9fb7cf6 100644 --- a/tests/integration/commands/open.test.ts +++ b/tests/integration/commands/open.test.ts @@ -1,5 +1,5 @@ import { Bank } from 'oldschooljs'; -import { beforeEach, describe, expect, test } from 'vitest'; +import { describe, expect, test } from 'vitest'; import { openCommand } from '../../../src/mahoji/commands/open'; import { randomMock } from '../setup'; @@ -7,15 +7,10 @@ import { createTestUser, mockClient } from '../util'; describe('Open Command', async () => { await mockClient(); - const user = await createTestUser(); - - beforeEach(async () => { + test.concurrent('Open with no quantity', async () => { randomMock(); - await user.reset(); + const user = await createTestUser(); await user.addItemsToBank({ items: new Bank().add('Reward casket (beginner)', 100) }); - }); - - test('Open with no quantity', async () => { const result = await user.runCommand(openCommand, { name: 'reward casket (beginner)' }); expect(result).toMatchObject({ content: `You have now opened a total of 1x Reward casket (beginner) @@ -31,7 +26,10 @@ describe('Open Command', async () => { await user.clMatch(new Bank().add('Fire rune', 34 * 2)); }); - test('Open with quantity', async () => { + test.concurrent('Open with quantity', async () => { + randomMock(); + const user = await createTestUser(); + await user.addItemsToBank({ items: new Bank().add('Reward casket (beginner)', 100) }); await user.runCommand(openCommand, { name: 'reward casket (beginner)', quantity: 10 }); await user.bankAmountMatch('Reward casket (beginner)', 90); await user.openedBankMatch(new Bank().add('Reward casket (beginner)', 10)); diff --git a/tests/integration/commands/sacrifice.test.ts b/tests/integration/commands/sacrifice.test.ts index f32ac4464a2..0abd4fc483f 100644 --- a/tests/integration/commands/sacrifice.test.ts +++ b/tests/integration/commands/sacrifice.test.ts @@ -1,5 +1,5 @@ import { Bank } from 'oldschooljs'; -import { ItemBank } from 'oldschooljs/dist/meta/types'; +import type { ItemBank } from 'oldschooljs/dist/meta/types'; import { describe, expect, test } from 'vitest'; import { Emoji } from '../../../src/lib/constants'; @@ -16,28 +16,26 @@ describe('Sacrifice Command', async () => { await user.runCommand(sacrificeCommand, { items: '1 trout, 10 coal' }); const result = await user.runCommand(sacrificeCommand, {}); expect(result).toEqual( - `${Emoji.Incinerator} **Your Sacrifice Stats** ${Emoji.Incinerator}\n\n` + - `**Current Minion Icon:** ${Emoji.Minion}\n` + - '**Sacrificed Value:** 1,590 GP\n' + - '**Unique Items Sacrificed:** 2 items' + `${Emoji.Incinerator} **Your Sacrifice Stats** ${Emoji.Incinerator}\n\n**Current Minion Icon:** ${Emoji.Minion}\n**Sacrificed Value:** 1,590 GP\n**Unique Items Sacrificed:** 2 items` ); }); test('No items provided', async () => { const result = await user.runCommand(sacrificeCommand, { items: 'aaaa' }); - expect(result).toEqual('No items were provided.\nYour current sacrificed value is: 1,590 (1.59k)'); + expect(result).toEqual('No items were provided.\nYour current sacrificed value is: 1,590 (1.6k)'); }); test('Successful', async () => { await user.addItemsToBank({ items: new Bank().add('Trout').add('Coal', 10) }); const result = await user.runCommand(sacrificeCommand, { items: '1 trout, 10 coal' }); + await user.sync(); expect(result).toEqual( - 'You sacrificed 10x Coal, 1x Trout, with a value of 1,590gp (1.59k). Your total amount sacrificed is now: 3,180. ' + 'You sacrificed 10x Coal, 1x Trout, with a value of 1,590gp (1.6k). Your total amount sacrificed is now: 3,180. ' ); const stats = await user.fetchStats({ sacrificed_bank: true }); - expect(user.bank.equals(new Bank())).toBe(true); - expect(new Bank(stats.sacrificed_bank as ItemBank).equals(new Bank().add('Coal', 20).add('Trout', 2))).toBe( - true + expect(user.bank.toString()).toBe(new Bank().toString()); + expect(new Bank(stats.sacrificed_bank as ItemBank).toString()).toEqual( + new Bank().add('Coal', 20).add('Trout', 2).toString() ); expect(user.user.sacrificedValue).toEqual(BigInt(3180)); const clientSettings = await mahojiClientSettingsFetch({ economyStats_sacrificedBank: true }); @@ -52,7 +50,7 @@ describe('Sacrifice Command', async () => { 'You sacrificed 1x Trout, 1x Cake, with a value of 257gp (257). Your total amount sacrificed is now: 3,437. ' ); await user.sync(); - expect(user.bank.equals(new Bank())).toBe(true); + expect(user.bank.toString()).toBe(new Bank().toString()); const stats2 = await user.fetchStats({ sacrificed_bank: true }); expect( new Bank(stats2.sacrificed_bank as ItemBank).equals(new Bank().add('Coal', 20).add('Trout', 3).add('Cake')) diff --git a/tests/integration/dryStreak.test.ts b/tests/integration/dryStreak.test.ts index f4d566837cc..54a135e3b3d 100644 --- a/tests/integration/dryStreak.test.ts +++ b/tests/integration/dryStreak.test.ts @@ -13,7 +13,7 @@ describe('Drystreak', async () => { } catch (err) { throw new Error(`Error running ${a.name}: ${err}`); } - await Promise.all(promises); } + await Promise.all(promises); }); }); diff --git a/tests/integration/gearFixing.test.ts b/tests/integration/gearFixing.test.ts index 9534ef0a4b2..cea0975c4a4 100644 --- a/tests/integration/gearFixing.test.ts +++ b/tests/integration/gearFixing.test.ts @@ -1,8 +1,8 @@ -import deepEqual from 'deep-equal'; +import deepEqual from 'fast-deep-equal'; import { Bank } from 'oldschooljs'; import { describe, test } from 'vitest'; -import { defaultGear, Gear } from '../../src/lib/structures/Gear'; +import { Gear, defaultGear } from '../../src/lib/structures/Gear'; import { assert } from '../../src/lib/util'; import itemID from '../../src/lib/util/itemID'; import { createTestUser, mockClient } from './util'; diff --git a/tests/integration/grandExchange.test.ts b/tests/integration/grandExchange.test.ts index 5f09a583c53..add00c8921b 100644 --- a/tests/integration/grandExchange.test.ts +++ b/tests/integration/grandExchange.test.ts @@ -1,33 +1,27 @@ -import { calcPercentOfNum, randArrItem, randInt, shuffleArr, Time } from 'e'; +import { Time, calcPercentOfNum, randArrItem, randInt, shuffleArr } from 'e'; import { Bank } from 'oldschooljs'; -import PQueue from 'p-queue'; +import { resolveItems } from 'oldschooljs/dist/util/util'; import { describe, expect, test } from 'vitest'; -import { usernameCache } from '../../src/lib/constants'; import { GrandExchange } from '../../src/lib/grandExchange'; -import { assert } from '../../src/lib/util'; -import resolveItems from '../../src/lib/util/resolveItems'; + +import PQueue from 'p-queue'; +import { assert, Stopwatch } from '../../src/lib/util'; import { geCommand } from '../../src/mahoji/commands/ge'; import { cancelUsersListings } from '../../src/mahoji/lib/abstracted_commands/cancelGEListingCommand'; -import { createTestUser, mockClient, TestUser } from './util'; - -const quantities = [-1, 0, 100_000_000_000_000_000, 1, 2, 38, 1_000_000_000_000, 500, '5*5']; -const prices = [ - -1, - 0, - 100_000_000_000_000_000, - 1, - 2, - 1_000_000_000_000, - 99, - 100, - 101, - 1005, - 4005, - 5005, - 100_000, - '5*9999999' -]; +import type { TestUser } from './util'; +import { createTestUser, mockClient } from './util'; + +const TICKS_TO_RUN = 50; +const AMOUNT_USERS = 10; +const COMMANDS_PER_USER = 3; +const TICKS_PER_EXTENSIVE_VERIFICATION = 20; +const itemPool = resolveItems(['Egg', 'Trout', 'Coal']); + +console.log(`G.E test will make ${itemPool.length * COMMANDS_PER_USER * AMOUNT_USERS} listings.`); + +const quantities = [1, 2, 38, 500, '5*5']; +const prices = [1, 30, 33, 55]; const sampleBank = new Bank() .add('Coins', 1_000_000_000) @@ -37,18 +31,21 @@ const sampleBank = new Bank() .freeze(); describe('Grand Exchange', async () => { - const itemPool = resolveItems(['Egg', 'Trout', 'Coal']); - GrandExchange.calculateSlotsOfUser = async () => ({ slots: 500 } as any); + GrandExchange.calculateSlotsOfUser = async () => ({ slots: 500 }) as any; await mockClient(); async function waitForGEToBeEmpty() { await GrandExchange.queue.onEmpty(); assert(!GrandExchange.locked, 'G.E should not be locked'); + assert(GrandExchange.queue.size === 0 && !GrandExchange.isTicking, 'Queue should be empty'); } test( 'Fuzz', async () => { + const stopwatch = new Stopwatch(); + stopwatch.start(); + // biome-ignore lint/suspicious/noSelfCompare: assert(randInt(1, 100_000) !== randInt(1, 100_000)); await GrandExchange.totalReset(); @@ -56,51 +53,57 @@ describe('Grand Exchange', async () => { const currentOwnedBank = await GrandExchange.fetchOwnedBank(); expect(currentOwnedBank.toString()).toEqual(new Bank().toString()); - let amountOfUsers = randInt(100, 200); - const totalExpectedBank = sampleBank.clone().multiply(amountOfUsers); + const totalExpectedBank = sampleBank.clone().multiply(AMOUNT_USERS); let users: TestUser[] = []; - for (let i = 0; i < amountOfUsers; i++) { - const user = await createTestUser(); - await user.addItemsToBank({ items: sampleBank }); - users.push(user); + for (let i = 0; i < AMOUNT_USERS; i++) { + users.push( + (async () => { + const user = await createTestUser(sampleBank); + return user; + })() as any + ); } - console.log(`Finished initializing ${amountOfUsers} users`); + users = await Promise.all(users); + stopwatch.check(`Finished initializing ${AMOUNT_USERS} users`); // Run a bunch of commands to buy/sell const commandPromises = new PQueue({ concurrency: 10 }); for (const user of shuffleArr(users)) { - const method = randArrItem(['buy', 'sell']); - let quantity = randArrItem(quantities); - let price = randArrItem(prices); - commandPromises.add(async () => { + for (let i = 0; i < COMMANDS_PER_USER; i++) { + const method = randArrItem(['buy', 'sell']); + const quantity = randArrItem(quantities); + const price = randArrItem(prices); for (const item of itemPool) { - await user.runCommand(geCommand, { - [method]: { - item, - quantity, - price - } - }); + commandPromises.add(() => + user.runCommand(geCommand, { + [method]: { + item, + quantity, + price + } + }) + ); } - }); + } } + stopwatch.check('Finished initiaing commands'); await commandPromises.onEmpty(); await waitForGEToBeEmpty(); - console.log('Finished running all commands'); + stopwatch.check('Finished running all commands'); // Tick the g.e to make some transactions - for (let i = 0; i < 100; i++) { + for (let i = 0; i < TICKS_TO_RUN; i++) { await GrandExchange.tick(); - await waitForGEToBeEmpty(); - await Promise.all([ - GrandExchange.checkGECanFullFilAllListings(), - GrandExchange.extensiveVerification() - ]); + if (i % TICKS_PER_EXTENSIVE_VERIFICATION === 0) { + await GrandExchange.extensiveVerification(); + } } + await waitForGEToBeEmpty(); - console.log('Finished ticking 100 times'); + const count = await prisma.gETransaction.count(); + stopwatch.check(`Finished ticking ${TICKS_TO_RUN} times, made ${count} transactions`); // Cancel all remaining listings const cancelPromises = []; @@ -117,22 +120,22 @@ describe('Grand Exchange', async () => { if (newCurrentOwnedBank.length !== 0) { throw new Error('There should be no items in the G.E bank!'); } - console.log('Finished cancelling'); + stopwatch.check('Finished cancelling'); await Promise.all(users.map(u => u.sync())); + stopwatch.check('Finished syncing all users'); const testBank = new Bank(); for (const user of users) { testBank.add(user.bankWithGP); } - await GrandExchange.checkGECanFullFilAllListings(); + assert(GrandExchange.queue.size === 0 && !GrandExchange.isTicking, 'Queue should be empty'); await GrandExchange.extensiveVerification(); + assert(GrandExchange.queue.size === 0 && !GrandExchange.isTicking, 'Queue should be empty'); const data = await GrandExchange.fetchData(); expect(data.isLocked).toEqual(false); - expect(data.taxBank, '1MS').toBeGreaterThan(0); - expect(data.totalTax, 'L1M').toBeGreaterThan(0); const totalTaxed = await global.prisma!.gETransaction.aggregate({ _sum: { @@ -152,10 +155,10 @@ Based on G.E data, we should have received ${data.totalTax} tax`; await GrandExchange.queue.onEmpty(); assert(GrandExchange.queue.size === 0, 'Queue should be empty'); + stopwatch.check('Finished final checks'); }, { - repeats: 1, - timeout: Time.Minute * 5 + timeout: Time.Minute * 10 } ); @@ -169,9 +172,6 @@ Based on G.E data, we should have received ${data.totalTax} tax`; const wes = await createTestUser(); const magnaboy = await createTestUser(); - usernameCache.set(wes.id, 'Wes'); - usernameCache.set(magnaboy.id, 'Magnaboy'); - await magnaboy.addItemsToBank({ items: sampleBank }); await wes.addItemsToBank({ items: sampleBank }); assert(magnaboy.bankWithGP.equals(sampleBank), 'Test users bank should match sample bank'); diff --git a/tests/integration/killSimulator.test.ts b/tests/integration/killSimulator.test.ts new file mode 100644 index 00000000000..c12b3893091 --- /dev/null +++ b/tests/integration/killSimulator.test.ts @@ -0,0 +1,18 @@ +import { expect, test } from 'vitest'; + +import { Time } from 'e'; +import { killCommand } from '../../src/mahoji/commands/kill'; +import { createTestUser } from './util'; + +test( + 'killSimulator.test', + async () => { + const user = await createTestUser(); + await bankImageGenerator.init(); + await bankImageGenerator.ready; + expect(async () => await user.runCommand(killCommand, { name: 'man', quantity: 100 })).to.not.throw(); + }, + { + timeout: Time.Second * 5 + } +); diff --git a/tests/integration/leaderboard.test.ts b/tests/integration/leaderboard.test.ts new file mode 100644 index 00000000000..d4445932acb --- /dev/null +++ b/tests/integration/leaderboard.test.ts @@ -0,0 +1,15 @@ +import { describe, test } from 'vitest'; + +import { leaderboardCommand } from '../../src/mahoji/commands/leaderboard'; +import { createTestUser } from './util'; + +describe('Leaderboard', async () => { + test('KC Leaderboard', async () => { + const user = await createTestUser(); + await user.runCommand(leaderboardCommand, { + kc: { + monster: 'man' + } + }); + }); +}); diff --git a/tests/integration/memoryHarvesting.bso.test.ts b/tests/integration/memoryHarvesting.bso.test.ts index 751ac6d9708..f55783e9ecf 100644 --- a/tests/integration/memoryHarvesting.bso.test.ts +++ b/tests/integration/memoryHarvesting.bso.test.ts @@ -1,10 +1,12 @@ import { Bank } from 'oldschooljs'; -import { ItemBank } from 'oldschooljs/dist/meta/types'; +import type { ItemBank } from 'oldschooljs/dist/meta/types'; import { afterEach, beforeEach, describe, expect, test } from 'vitest'; +import { processPendingActivities } from '../../src/lib/Task'; import { MemoryHarvestType } from '../../src/lib/bso/divination'; +import { convertStoredActivityToFlatActivity } from '../../src/lib/settings/prisma'; import { Gear } from '../../src/lib/structures/Gear'; -import { MemoryHarvestOptions } from '../../src/lib/types/minions'; +import type { MemoryHarvestOptions } from '../../src/lib/types/minions'; import itemID from '../../src/lib/util/itemID'; import { divinationCommand } from '../../src/mahoji/commands/divination'; import { createTestUser, mockClient } from './util'; @@ -32,8 +34,15 @@ describe('Divination', async () => { energy: 'Pale' } }); - const activity = await user.runActivity(); + await processPendingActivities(); await user.sync(); + const _activity = await prisma.activity.findFirst({ + where: { + user_id: BigInt(user.id), + type: 'MemoryHarvest' + } + }); + const activity = convertStoredActivityToFlatActivity(_activity!) as MemoryHarvestOptions; expect(user.skillsAsXP.divination).toBeGreaterThan(1); expect(user.skillsAsLevels.divination).toEqual(36); expect(activity.dp).toEqual(false); @@ -55,8 +64,15 @@ describe('Divination', async () => { type: MemoryHarvestType.ConvertToEnergy } }); - const activity = await user.runActivity(); + await processPendingActivities(); await user.sync(); + const _activity = await prisma.activity.findFirst({ + where: { + user_id: BigInt(user.id), + type: 'MemoryHarvest' + } + }); + const activity = convertStoredActivityToFlatActivity(_activity!) as MemoryHarvestOptions; expect(user.skillsAsXP.divination).toBeGreaterThan(1); expect(user.skillsAsLevels.divination).toEqual(32); expect(activity.dp).toEqual(false); diff --git a/tests/integration/migrateUser.test.ts b/tests/integration/migrateUser.test.ts index 8de51877d0b..62abb33ac8d 100644 --- a/tests/integration/migrateUser.test.ts +++ b/tests/integration/migrateUser.test.ts @@ -1,49 +1,47 @@ import { - Activity, - activity_type_enum, - Bingo, - BingoParticipant, - BuyCommandTransaction, - CommandUsage, - EconomyTransaction, - FarmedCrop, - GearPreset, - Giveaway, - HistoricalData, - LastManStandingGame, - LootTrack, - Minigame, - MortimerTricks, - PinnedTrip, - PlayerOwnedHouse, - Prisma, - ReclaimableItem, - SlayerTask, - UserStats, - XPGain + type Activity, + type Bingo, + type BingoParticipant, + type BuyCommandTransaction, + type CommandUsage, + type EconomyTransaction, + type FarmedCrop, + type GearPreset, + type Giveaway, + type HistoricalData, + type LastManStandingGame, + type LootTrack, + type Minigame, + type PinnedTrip, + type PlayerOwnedHouse, + type Prisma, + type ReclaimableItem, + type SlayerTask, + type UserStats, + type XPGain, + type activity_type_enum, + command_name_enum } from '@prisma/client'; -import { deepClone, randArrItem, randInt, shuffleArr, sumArr, Time } from 'e'; +import { Time, deepClone, randArrItem, randInt, shuffleArr, sumArr } from 'e'; import { Bank } from 'oldschooljs'; -import { ItemBank } from 'oldschooljs/dist/meta/types'; -import { describe, expect, test, vi } from 'vitest'; +import type { ItemBank } from 'oldschooljs/dist/meta/types'; +import { beforeAll, expect, test, vi } from 'vitest'; +import { processPendingActivities } from '../../src/lib/Task'; import { BitField } from '../../src/lib/constants'; -import { GearSetupType, UserFullGearSetup } from '../../src/lib/gear/types'; -import { GrandExchange } from '../../src/lib/grandExchange'; +import type { GearSetupType, UserFullGearSetup } from '../../src/lib/gear/types'; import { trackLoot } from '../../src/lib/lootTrack'; -import { incrementMinigameScore, MinigameName } from '../../src/lib/settings/minigames'; -import { prisma } from '../../src/lib/settings/prisma'; -import { SkillsEnum } from '../../src/lib/skilling/types'; +import type { MinigameName } from '../../src/lib/settings/minigames'; +import { incrementMinigameScore } from '../../src/lib/settings/minigames'; +import type { SkillsEnum } from '../../src/lib/skilling/types'; import { slayerMasters } from '../../src/lib/slayer/slayerMasters'; import { assignNewSlayerTask } from '../../src/lib/slayer/slayerUtil'; -import { processPendingActivities } from '../../src/lib/Task'; -import { Skills } from '../../src/lib/types'; -import { isGroupActivity } from '../../src/lib/util'; +import type { Skills } from '../../src/lib/types'; +import { isGroupActivity, resolveItems } from '../../src/lib/util'; import { gearEquipMultiImpl } from '../../src/lib/util/equipMulti'; import { findPlant } from '../../src/lib/util/farmingHelpers'; import getOSItem from '../../src/lib/util/getOSItem'; import { migrateUser } from '../../src/lib/util/migrateUser'; -import resolveItems from '../../src/lib/util/resolveItems'; import { tradePlayerItems } from '../../src/lib/util/tradePlayerItems'; import { updateBankSetting } from '../../src/lib/util/updateBankSetting'; import { pinTripCommand } from '../../src/mahoji/commands/config'; @@ -55,22 +53,22 @@ import { stashUnitBuildAllCommand, stashUnitFillAllCommand } from '../../src/mahoji/lib/abstracted_commands/stashUnitsCommand'; -import { syncNewUserUsername } from '../../src/mahoji/lib/preCommand'; -import { OSBMahojiCommand } from '../../src/mahoji/lib/util'; +import type { OSBMahojiCommand } from '../../src/mahoji/lib/util'; import { updateClientGPTrackSetting, userStatsUpdate } from '../../src/mahoji/mahojiSettings'; import { calculateResultOfLMSGames, getUsersLMSStats } from '../../src/tasks/minions/minigames/lmsActivity'; -import { createTestUser, mockClient, mockedId, TestUser } from './util'; -import { BotItemSell, FishingContestCatch, GEListing, StashUnit, Tame, TameActivity } from '.prisma/client'; +import type { TestUser } from './util'; +import { createTestUser, mockClient, mockedId } from './util'; +import type { BotItemSell, GEListing, StashUnit } from '.prisma/client'; interface TestCommand { name: string; - cmd: [OSBMahojiCommand, Object] | ((user: TestUser) => Promise); + cmd: [OSBMahojiCommand, object] | ((user: TestUser) => Promise); activity?: boolean; priority?: boolean; } class UserData { // Class Data - private loaded: boolean = false; + private loaded = false; private mUser: MUser | null = null; // Robochimp: @@ -108,12 +106,6 @@ class UserData { bingos?: Bingo[]; commandUsage?: CommandUsage[]; geListings?: GEListing[]; - // BSO Data - tames?: Tame[]; - tameActivity?: TameActivity[]; - fishingContestCatches?: FishingContestCatch[]; - // BSO Event Data - mortimerTricks?: MortimerTricks[]; constructor(_user: string | MUser) { this.id = typeof _user === 'string' ? _user : _user.id; @@ -265,37 +257,10 @@ class UserData { }); if (geListings.length > 0) this.geListings = geListings; - // BSO Data: - const tames = await prisma.tame.findMany({ - where: { user_id: this.id }, - orderBy: { id: 'asc' } - }); - if (tames.length > 0) this.tames = tames; - - const tameActivity = await prisma.tameActivity.findMany({ - where: { user_id: this.id }, - orderBy: { id: 'asc' } - }); - if (tameActivity.length > 0) this.tameActivity = tameActivity; - - const fishingContestCatches = await prisma.fishingContestCatch.findMany({ - where: { user_id: BigInt(this.id) }, - orderBy: { id: 'asc' } - }); - if (fishingContestCatches.length > 0) this.fishingContestCatches = fishingContestCatches; - - // BSO Limited Event Data: - const mortimerTricks = await prisma.mortimerTricks.findMany({ - where: { OR: [{ trickster_id: this.id }, { target_id: this.id }] }, - orderBy: { date: 'asc' } - }); - - if (mortimerTricks.length > 0) this.mortimerTricks = mortimerTricks; - this.loaded = true; } - equals(target: UserData, ignoreRoboChimp: boolean = false): { result: boolean; errors: string[] } { + equals(target: UserData, ignoreRoboChimp = false): { result: boolean; errors: string[] } { const errors: string[] = []; if (!this.loaded || !target.loaded) { errors.push('Both UserData object must be loaded. Try .sync()'); @@ -637,73 +602,6 @@ class UserData { } } - // BSO Data - // Tames - if (this.tames !== target.tames) { - const srcCt = this.tames?.length ?? 0; - const dstCt = target.tames?.length ?? 0; - if (srcCt !== dstCt) { - errors.push(`Wrong number of Tames. ${srcCt} vs ${dstCt}`); - } else if ( - !this.tames!.every(s => - target.tames!.some( - t => s.nickname === t.nickname && s.max_combat_level === t.max_combat_level && s.id === t.id - ) - ) - ) { - errors.push("One or more Tames don't match."); - } - } - // Tame Activity - if (this.tameActivity !== target.tameActivity) { - const srcCt = this.tameActivity?.length ?? 0; - const dstCt = target.tameActivity?.length ?? 0; - if (srcCt !== dstCt) { - errors.push(`Wrong number of Tame Activity rows. ${srcCt} vs ${dstCt}`); - } else if ( - !this.tameActivity!.every(s => - target.tameActivity!.some(t => JSON.stringify(s.data) === JSON.stringify(t.data) && s.id === t.id) - ) - ) { - errors.push("One or more Tame Activities don't match."); - } - } - // Fishing Contest Catches - if (this.fishingContestCatches !== target.fishingContestCatches) { - const srcCt = this.fishingContestCatches?.length ?? 0; - const dstCt = target.fishingContestCatches?.length ?? 0; - if (srcCt !== dstCt) { - errors.push(`Wrong number of fishingContestCatches rows. ${srcCt} vs ${dstCt}`); - } else if ( - !this.fishingContestCatches!.every(s => - target.fishingContestCatches!.some(t => s.name === t.name && s.id === t.id) - ) - ) { - errors.push("One or more fishingContestCatches don't match."); - } - } - - // BSO Limited Event Data: - // Mortimer Tricks - if (this.mortimerTricks !== target.mortimerTricks) { - const srcCt = this.mortimerTricks?.length ?? 0; - const dstCt = target.mortimerTricks?.length ?? 0; - if (srcCt !== dstCt) { - errors.push(`Wrong number of MortimerTricks rows. ${srcCt} vs ${dstCt}`); - } else if ( - !this.mortimerTricks!.every(s => - target.mortimerTricks!.some(t => { - if ([t.trickster_id, s.trickster_id].includes(this.id)) { - return t.target_id === s.target_id && s.id === t.id; - } - return t.trickster_id === s.trickster_id && s.id === t.id; - }) - ) - ) { - errors.push("One or more MortimerTricks rows don't match."); - } - } - if (errors.length > 0) { errors.unshift(`Failed comparing ${this.id} vs ${target.id}:`); return { result: false, errors }; @@ -791,13 +689,6 @@ const allTableCommands: TestCommand[] = [ await pohWallkitCommand(user, 'Hosidius'); } }, - { - name: 'Create new_users entry', - cmd: async user => { - await syncNewUserUsername(user, `testUser${randInt(1000, 9999).toString()}`); - }, - priority: true - }, { name: 'Buy command transaction', cmd: async user => { @@ -846,7 +737,7 @@ const allTableCommands: TestCommand[] = [ const { success: resultSuccess, failMsg, equippedGear } = gearEquipMultiImpl(user, setup, items); if (!resultSuccess) return failMsg!; - await user.update({ [`gear_${setup}`]: equippedGear }); + await user.update({ [`gear_${setup}`]: equippedGear as Prisma.InputJsonValue }); } }, { @@ -856,8 +747,8 @@ const allTableCommands: TestCommand[] = [ const items = 'Bandos chestplate, Bandos tassets, Berserker ring, Ghrazi rapier'; const { success: resultSuccess, failMsg, equippedGear } = gearEquipMultiImpl(user, setup, items); if (!resultSuccess) return failMsg!; - - await user.update({ [`gear_${setup}`]: equippedGear }); + if (!equippedGear) throw new Error('Equipped gear is undefined'); + await user.update({ [`gear_${setup}`]: equippedGear as Prisma.InputJsonValue }); } }, { @@ -1045,17 +936,18 @@ const allTableCommands: TestCommand[] = [ user_id: user.id }); + const stats = await user.fetchStats({ items_sold_bank: true }); await Promise.all([ updateClientGPTrackSetting('gp_sell', totalPrice), updateBankSetting('sold_items_bank', bankToSell), userStatsUpdate( user.id, - userStats => ({ - items_sold_bank: new Bank(userStats.items_sold_bank as ItemBank).add(bankToSell).bank, + { + items_sold_bank: new Bank(stats.items_sold_bank as ItemBank).add(bankToSell).bank, sell_gp: { increment: totalPrice } - }), + }, {} ), global.prisma!.botItemSell.createMany({ data: botItemSellData }) @@ -1159,12 +1051,17 @@ const allTableCommands: TestCommand[] = [ { name: 'Command usage', cmd: async user => { - const randCommands = ['minion', 'runecraft', 'chop', 'mine', 'buy']; + const randCommands = [ + command_name_enum.minion, + command_name_enum.runecraft, + command_name_enum.chop, + command_name_enum.mine, + command_name_enum.buy + ]; await global.prisma!.commandUsage.create({ data: { user_id: BigInt(user.id), channel_id: 1_111_111_111n, - status: 'Unknown', args: {}, command_name: randArrItem(randCommands), guild_id: null, @@ -1198,97 +1095,6 @@ const allTableCommands: TestCommand[] = [ } }); } - }, - // BSO Commands / data tables - { - name: 'Create tame', - cmd: async user => { - const tameNicknames = ['Xlaug', 'Smog', 'Xmaug', 'Infernape', 'Charmander', 'Charizard']; - await prisma.tame.create({ - data: { - user_id: user.id, - nickname: randArrItem(tameNicknames), - species_id: 1, - growth_stage: 'adult', - growth_percent: 100, - max_combat_level: randInt(75, 100), - max_artisan_level: randInt(1, 10), - max_gatherer_level: randInt(15, 30), - max_support_level: randInt(1, 10) - } - }); - }, - priority: true - }, - { - name: 'Create tame activity', - cmd: async user => { - const tame = await prisma.tame.findFirst({ where: { user_id: user.id }, select: { id: true } }); - if (!tame) return false; - const start_date = new Date(); - const duration = 60 * 60 * 1000; - const finish_date = new Date(start_date.getTime() + duration); - await prisma.tameActivity.create({ - data: { - start_date, - finish_date, - duration, - user_id: user.id, - tame_id: tame.id, - type: 'pvm', - channel_id: '1111111111111111111', - completed: true, - data: { type: 'pvm', monsterID: 707_070, quantity: randInt(30, 100) } - } - }); - } - }, - { - name: 'Fishing Contest Catch', - cmd: async user => { - const fishNames = [ - ['Pacific', 'Atlantic', 'Antarctic', 'Arctic', 'Indian', 'Summer', 'Frowning', 'Smiling'], - ['Outback', 'Tailback', 'Longfin', 'Tuna', 'Bluefin', 'Whaleback', 'Tigerfish', 'Striped-back'] - ]; - const name = fishNames.map(slug => randArrItem(slug)).join(' '); - await prisma.fishingContestCatch.create({ - data: { - user_id: BigInt(user.id), - name, - length_cm: randInt(80, 120) - } - }); - }, - priority: true - }, - // BSO Event data - { - name: 'Mortimer tricks you', - cmd: async user => { - const target_id = user.id; - const trickster_id = mockedId(); - await prisma.mortimerTricks.create({ - data: { - trickster_id, - target_id, - completed: false - } - }); - } - }, - { - name: 'Mortimer tricks target', - cmd: async user => { - const trickster_id = user.id; - const target_id = mockedId(); - await prisma.mortimerTricks.create({ - data: { - trickster_id, - target_id, - completed: false - } - }); - } } ]; @@ -1308,7 +1114,7 @@ async function runAllTestCommandsOnUser(user: TestUser) { return user; } -async function runRandomTestCommandsOnUser(user: TestUser, numCommands: number = 6, forceRoboChimp: boolean = false) { +async function runRandomTestCommandsOnUser(user: TestUser, numCommands = 6, forceRoboChimp = false) { const commandHistory: string[] = []; const priorityCommands = allTableCommands.filter(c => c.priority); const otherCommands = allTableCommands.filter(c => !c.priority); @@ -1382,238 +1188,217 @@ async function buildBaseUser(userId: string) { const user = await createTestUser(startBank, userData); return user; } -describe('migrate user test', async () => { - await mockClient(); - vi.doMock('../../src/lib/util', async () => { - const actual: any = await vi.importActual('../../src/lib/util'); - return { - ...actual, - channelIsSendable: () => false - }; - }); - - const logResult = ( - result: { result: boolean; errors: string[] }, - sourceData: UserData, - newData: UserData, - srcHistory?: string[], - dstHistory?: string[] - ) => { - if (!result.result) { - if (srcHistory) { - console.log(`Source Command History: ${sourceData.id}`); - console.log(srcHistory); - } - if (dstHistory) { - console.log(`Target Command History: ${newData.id}`); - console.log(dstHistory); - } - console.log(`source: ${sourceData.id} dest: ${newData.id}`); - console.log(result.errors); - console.log(JSON.stringify(sourceData)); - console.log(JSON.stringify(newData)); - } + +vi.doMock('../../src/lib/util', async () => { + const actual: any = await vi.importActual('../../src/lib/util'); + return { + ...actual, + channelIsSendable: () => false }; +}); + +const logResult = ( + result: { result: boolean; errors: string[] }, + sourceData: UserData, + newData: UserData, + srcHistory?: string[], + dstHistory?: string[] +) => { + if (!result.result) { + if (srcHistory) { + console.log(`Source Command History: ${sourceData.id}`); + console.log(srcHistory); + } + if (dstHistory) { + console.log(`Target Command History: ${newData.id}`); + console.log(dstHistory); + } + console.log(`source: ${sourceData.id} dest: ${newData.id}`); + console.log(result.errors); + console.log(JSON.stringify(sourceData)); + console.log(JSON.stringify(newData)); + } +}; + +test.concurrent('test preventing a double (clobber) robochimp migration (two bot-migration)', async () => { + const sourceUserId = mockedId(); + const destUserId = mockedId(); + + // Create source user, and populate data: + const sourceUser = await buildBaseUser(sourceUserId); + const srcHistory = await runRandomTestCommandsOnUser(sourceUser, 5, true); + + const sourceData = new UserData(sourceUser); + await sourceData.sync(); - await GrandExchange.totalReset(); - await GrandExchange.init(); + const migrateResult = await migrateUser(sourceUser.id, destUserId); + expect(migrateResult).toEqual(true); - test('test preventing a double (clobber) robochimp migration (two bot-migration)', async () => { - const sourceUserId = mockedId(); - const destUserId = mockedId(); + const destData = new UserData(destUserId); + await destData.sync(); - // Create source user, and populate data: - const sourceUser = await buildBaseUser(sourceUserId); - const srcHistory = await runRandomTestCommandsOnUser(sourceUser, 5, true); + const compareResult = sourceData.equals(destData); + logResult(compareResult, sourceData, destData, srcHistory, []); + expect(compareResult.result).toBe(true); - const sourceData = new UserData(sourceUser); - await sourceData.sync(); + // Now the actual test, everything above has to happen first... + await runAllTestCommandsOnUser(sourceUser); - const migrateResult = await migrateUser(sourceUser.id, destUserId); - expect(migrateResult).toEqual(true); + const newSourceData = new UserData(sourceUser); + await newSourceData.sync(); - const destData = new UserData(destUserId); - await destData.sync(); + const secondMigrateResult = await migrateUser(sourceUser.id, destUserId); + expect(secondMigrateResult).toEqual(true); - const compareResult = sourceData.equals(destData); - logResult(compareResult, sourceData, destData, srcHistory, []); - expect(compareResult.result).toBe(true); + const newDestData = new UserData(destUserId); + await newDestData.sync(); - // Now the actual test, everything above has to happen first... - await runAllTestCommandsOnUser(sourceUser); + const newCompareResult = sourceData.equals(destData); + logResult(newCompareResult, newSourceData, newDestData); + expect(newCompareResult.result).toBe(true); - const newSourceData = new UserData(sourceUser); - await newSourceData.sync(); + expect(newDestData.githubId).toEqual(sourceData.githubId); + expect(newDestData.githubId).toEqual(destData.githubId); - const secondMigrateResult = await migrateUser(sourceUser.id, destUserId); - expect(secondMigrateResult).toEqual(true); + // Make sure the 2nd transfer didn't overwrite robochimp: + expect(newDestData.githubId !== newSourceData.githubId).toBeTruthy(); - const newDestData = new UserData(destUserId); - await newDestData.sync(); + // Verify migrated id is correct + expect(newDestData.migratedUserId).toEqual(BigInt(sourceData.id)); +}); - const newCompareResult = sourceData.equals(destData); - logResult(newCompareResult, newSourceData, newDestData); - expect(newCompareResult.result).toBe(true); +beforeAll(async () => { + await mockClient(); +}); - expect(newDestData.githubId).toEqual(sourceData.githubId); - expect(newDestData.githubId).toEqual(destData.githubId); +test.concurrent('test migrating existing user to target with no records', async () => { + const sourceUser = await buildBaseUser(mockedId()); + await runAllTestCommandsOnUser(sourceUser); - // Make sure the 2nd transfer didn't overwrite robochimp: - expect(newDestData.githubId !== newSourceData.githubId).toBeTruthy(); + const destUserId = mockedId(); - // Verify migrated id is correct - expect(newDestData.migratedUserId).toEqual(BigInt(sourceData.id)); - }); - test('test migrating existing user to target with no records', async () => { - const sourceUser = await buildBaseUser(mockedId()); - await runAllTestCommandsOnUser(sourceUser); + const sourceData = new UserData(sourceUser); + await sourceData.sync(); - const destUserId = mockedId(); + const migrateResult = await migrateUser(sourceUser.id, destUserId); + expect(migrateResult).toEqual(true); - const sourceData = new UserData(sourceUser); - await sourceData.sync(); + const newData = new UserData(destUserId); + await newData.sync(); - const migrateResult = await migrateUser(sourceUser.id, destUserId); - expect(migrateResult).toEqual(true); + const compareResult = sourceData.equals(newData); + logResult(compareResult, sourceData, newData); - const newData = new UserData(destUserId); - await newData.sync(); + expect(compareResult.result).toBe(true); +}); - const compareResult = sourceData.equals(newData); - logResult(compareResult, sourceData, newData); +test.concurrent('test migrating full user on top of full profile', async () => { + const sourceUser = await buildBaseUser(mockedId()); + const destUser = await buildBaseUser(mockedId()); + await runAllTestCommandsOnUser(sourceUser); + await runAllTestCommandsOnUser(destUser); + + const sourceData = new UserData(sourceUser); + await sourceData.sync(); + + const migrateResult = await migrateUser(sourceUser.id, destUser.id); + expect(migrateResult).toEqual(true); + + const newData = new UserData(destUser.id); + await newData.sync(); + const compareResult = sourceData.equals(newData); + logResult(compareResult, sourceData, newData); + + expect(compareResult.result).toBe(true); + + if (newData.poh) newData.poh.spellbook_altar = 33; + if (newData.userStats) newData.userStats.sacrificed_bank = new Bank().add('Cannonball').bank; + newData.skillsAsLevels!.cooking = 1_000_000; + newData.bingos = []; + newData.botItemSell = []; + if (newData.gear?.melee) newData.gear.melee.weapon = null; + + const badResult = sourceData.equals(newData); + expect(badResult.result).toBe(false); + + const expectedBadResult = [ + `Failed comparing ${sourceUser.id} vs ${destUser.id}:`, + "melee gear doesn't match", + "cooking level doesn't match. 1 vs 1000000", + "POH Object doesn't match: null !== 33", + 'User Stats doesn\'t match: {} !== {"2":1}', + 'Wrong number of BotItemSell rows. 1 vs 0' + ]; + expect(badResult.errors).toEqual(expectedBadResult); +}); - expect(compareResult.result).toBe(true); - }); +test.concurrent('test migrating random user on top of empty profile', async () => { + const sourceUser = await buildBaseUser(mockedId()); + const destUserId = mockedId(); - test('test migrating full user on top of full profile', async () => { - const sourceUser = await buildBaseUser(mockedId()); - const destUser = await buildBaseUser(mockedId()); - await runAllTestCommandsOnUser(sourceUser); - await runAllTestCommandsOnUser(destUser); + const sourceRolls = randInt(6, 11); + const cmdHistory = await runRandomTestCommandsOnUser(sourceUser, sourceRolls); - const sourceData = new UserData(sourceUser); - await sourceData.sync(); + const sourceData = new UserData(sourceUser); + await sourceData.sync(); - const migrateResult = await migrateUser(sourceUser.id, destUser.id); - expect(migrateResult).toEqual(true); + const result = await migrateUser(sourceUser, destUserId); - const newData = new UserData(destUser.id); - await newData.sync(); - const compareResult = sourceData.equals(newData); - logResult(compareResult, sourceData, newData); + if (result !== true) throw new Error(`${sourceUser.id} - ${result}`); + expect(result).toEqual(true); - expect(compareResult.result).toBe(true); + const newData = new UserData(destUserId); + await newData.sync(); - if (newData.poh) newData.poh.spellbook_altar = 33; - if (newData.userStats) newData.userStats.sacrificed_bank = new Bank().add('Cannonball').bank; - newData.skillsAsLevels!.cooking = 1_000_000; - newData.bingos = []; - newData.botItemSell = []; - if (newData.gear?.melee) newData.gear.melee.weapon = null; + const compareResult = sourceData.equals(newData); + logResult(compareResult, sourceData, newData, cmdHistory, []); - // BSO Data failure simulation: - newData.tames![0].nickname = 'Not my real nickname'; - // BSO Limited Event Failure simulation: - newData.mortimerTricks![0].trickster_id = '111111111'; + expect(compareResult.result).toBe(true); +}); - const badResult = sourceData.equals(newData); - expect(badResult.result).toBe(false); +test.concurrent('test migrating random user on top of random profile', async () => { + const sourceUser = await buildBaseUser(mockedId()); + const destUser = await buildBaseUser(mockedId()); - const expectedBadResult = [ - `Failed comparing ${sourceUser.id} vs ${destUser.id}:`, - "melee gear doesn't match", - "cooking level doesn't match. 1 vs 1000000", - "POH Object doesn't match: null !== 33", - 'User Stats doesn\'t match: {} !== {"2":1}', - 'Wrong number of BotItemSell rows. 1 vs 0', - // BSO Failure Check - "One or more Tames don't match.", - // BSO Event Failure Check - "One or more MortimerTricks rows don't match." - ]; - expect(badResult.errors).toEqual(expectedBadResult); - }); + const sourceRolls = randInt(5, 12); + const destRolls = randInt(5, 12); - test( - 'test migrating random user on top of empty profile', - async () => { - const sourceUser = await buildBaseUser(mockedId()); - const destUserId = mockedId(); + const srcHistory = await runRandomTestCommandsOnUser(sourceUser, sourceRolls); + const dstHistory = await runRandomTestCommandsOnUser(destUser, destRolls); - const sourceRolls = randInt(6, 11); - const cmdHistory = await runRandomTestCommandsOnUser(sourceUser, sourceRolls); + const sourceData = new UserData(sourceUser); + await sourceData.sync(); - const sourceData = new UserData(sourceUser); - await sourceData.sync(); + const result = await migrateUser(sourceUser, destUser); + expect(result).toEqual(true); - const result = await migrateUser(sourceUser, destUserId); + const newData = new UserData(destUser); + await newData.sync(); - if (result !== true) throw new Error(`${sourceUser.id} - ${result}`); - expect(result).toEqual(true); + const compareResult = sourceData.equals(newData); + logResult(compareResult, sourceData, newData, srcHistory, dstHistory); - const newData = new UserData(destUserId); - await newData.sync(); + expect(compareResult.result).toBe(true); +}); - const compareResult = sourceData.equals(newData); - logResult(compareResult, sourceData, newData, cmdHistory, []); +test.concurrent('test migrating random user on top of full profile', async () => { + const sourceUser = await buildBaseUser(mockedId()); + const destUser = await buildBaseUser(mockedId()); - expect(compareResult.result).toBe(true); - }, - { repeats: 1 } - ); + const cmdHistory = await runRandomTestCommandsOnUser(sourceUser); + await runAllTestCommandsOnUser(destUser); - test( - 'test migrating random user on top of random profile', - async () => { - const sourceUser = await buildBaseUser(mockedId()); - const destUser = await buildBaseUser(mockedId()); + const sourceData = new UserData(sourceUser); + await sourceData.sync(); - const sourceRolls = randInt(5, 12); - const destRolls = randInt(5, 12); + const result = await migrateUser(sourceUser, destUser); + expect(result).toEqual(true); - const srcHistory = await runRandomTestCommandsOnUser(sourceUser, sourceRolls); - const dstHistory = await runRandomTestCommandsOnUser(destUser, destRolls); + const newData = new UserData(destUser); + await newData.sync(); - const sourceData = new UserData(sourceUser); - await sourceData.sync(); - - const result = await migrateUser(sourceUser, destUser); - expect(result).toEqual(true); - - const newData = new UserData(destUser); - await newData.sync(); - - const compareResult = sourceData.equals(newData); - logResult(compareResult, sourceData, newData, srcHistory, dstHistory); - - expect(compareResult.result).toBe(true); - }, - { repeats: 1 } - ); + const compareResult = sourceData.equals(newData); + logResult(compareResult, sourceData, newData, cmdHistory, []); - test( - 'test migrating random user on top of full profile', - async () => { - const sourceUser = await buildBaseUser(mockedId()); - const destUser = await buildBaseUser(mockedId()); - - const cmdHistory = await runRandomTestCommandsOnUser(sourceUser); - await runAllTestCommandsOnUser(destUser); - - const sourceData = new UserData(sourceUser); - await sourceData.sync(); - - const result = await migrateUser(sourceUser, destUser); - expect(result).toEqual(true); - - const newData = new UserData(destUser); - await newData.sync(); - - const compareResult = sourceData.equals(newData); - logResult(compareResult, sourceData, newData, cmdHistory, []); - - expect(compareResult.result).toBe(true); - }, - { repeats: 1 } - ); + expect(compareResult.result).toBe(true); }); diff --git a/tests/integration/misc.test.ts b/tests/integration/misc.test.ts index b1aaee16757..eae4db6a9a9 100644 --- a/tests/integration/misc.test.ts +++ b/tests/integration/misc.test.ts @@ -1,4 +1,3 @@ -import { UserEvent } from '@prisma/client'; import { randArrItem } from 'e'; import { describe, expect, test } from 'vitest'; @@ -18,30 +17,14 @@ describe('Integration Misc', () => { expect(await global.prisma!.analytic.count()).toBeGreaterThanOrEqual(1); }); test('fetchCLLeaderboard', async () => { + const cl = randArrItem(allCollectionLogsFlat); for (const ironManOnly of [true, false]) { - for (const method of ['cl_array', 'raw_cl'] as const) { - for (const userEvents of [ - [ - { - id: 'asdf', - date: new Date(), - user_id: '123', - type: 'CLCompletion', - skill: null, - collection_log_name: 'giant mole' - } as UserEvent - ], - null - ]) { - await fetchCLLeaderboard({ - ironmenOnly: ironManOnly, - method, - userEvents, - resultLimit: 100, - items: randArrItem(allCollectionLogsFlat).items - }); - } - } + await fetchCLLeaderboard({ + ironmenOnly: ironManOnly, + resultLimit: 100, + items: cl.items, + clName: cl.name + }); } await Promise.all([fetchCLLeaderboard]); }); diff --git a/tests/integration/mocks.ts b/tests/integration/mocks.ts new file mode 100644 index 00000000000..ddc4f87d33d --- /dev/null +++ b/tests/integration/mocks.ts @@ -0,0 +1,37 @@ +import { Image } from '@napi-rs/canvas'; +import { beforeEach, vi } from 'vitest'; + +import { BankImageTask } from '../../src/lib/bankImage'; + +vi.mock('../../src/lib/util/handleMahojiConfirmation.ts', () => ({ + handleMahojiConfirmation: vi.fn() +})); + +vi.mock('../../src/lib/util/interactionReply', () => ({ + deferInteraction: vi.fn(), + interactionReply: vi.fn() +})); + +const mockBankImageTask = { + init: vi.fn(), + run: vi.fn(), + generateBankImage: vi.fn().mockReturnValue(Promise.resolve({ image: Buffer.from(''), isTransparent: false })), + getItemImage: vi.fn().mockReturnValue(Promise.resolve(new Image())), + fetchAndCacheImage: vi.fn().mockReturnValue(Promise.resolve(new Image())) +}; + +global.bankImageGenerator = mockBankImageTask as any; +BankImageTask.prototype.init = mockBankImageTask.init; +BankImageTask.prototype.run = mockBankImageTask.init; +BankImageTask.prototype.generateBankImage = mockBankImageTask.generateBankImage; +BankImageTask.prototype.getItemImage = mockBankImageTask.getItemImage; +BankImageTask.prototype.fetchAndCacheImage = mockBankImageTask.fetchAndCacheImage; + +beforeEach(async () => { + global.bankImageGenerator = mockBankImageTask as any; + BankImageTask.prototype.init = mockBankImageTask.init; + BankImageTask.prototype.run = mockBankImageTask.init; + BankImageTask.prototype.generateBankImage = mockBankImageTask.generateBankImage; + BankImageTask.prototype.getItemImage = mockBankImageTask.getItemImage; + BankImageTask.prototype.fetchAndCacheImage = mockBankImageTask.fetchAndCacheImage; +}); diff --git a/tests/integration/monsterKilling.bso.test.ts b/tests/integration/monsterKilling.bso.test.ts index 8d4276970e9..355a6a420a1 100644 --- a/tests/integration/monsterKilling.bso.test.ts +++ b/tests/integration/monsterKilling.bso.test.ts @@ -1,11 +1,11 @@ import { Bank } from 'oldschooljs'; import { expect, test } from 'vitest'; +import { processPendingActivities } from '../../src/lib/Task'; import { convertStoredActivityToFlatActivity } from '../../src/lib/settings/prisma'; import { Gear } from '../../src/lib/structures/Gear'; -import { processPendingActivities } from '../../src/lib/Task'; -import { MonsterActivityTaskOptions } from '../../src/lib/types/minions'; -import { killCommand } from '../../src/mahoji/commands/k'; +import type { MonsterActivityTaskOptions } from '../../src/lib/types/minions'; +import { minionKCommand } from '../../src/mahoji/commands/k'; import { giveMaxStats } from '../../src/mahoji/commands/testpotato'; import { createTestUser, mockClient } from './util'; @@ -33,14 +33,21 @@ test('Killing Vlad', async () => { skills_hitpoints: 200_000_000 }); - await user.runCommand(killCommand, { + await user.runCommand(minionKCommand, { name: 'vladimir drakan' }); - const [finishedActivity] = await processPendingActivities(); - const data = convertStoredActivityToFlatActivity(finishedActivity) as MonsterActivityTaskOptions; + await processPendingActivities(); + await user.sync(); + const _activity = await global.prisma!.activity.findFirst({ + where: { + user_id: BigInt(user.id), + type: 'MonsterKilling' + } + }); + const data = convertStoredActivityToFlatActivity(_activity!) as MonsterActivityTaskOptions; - const quantityKilled = data.quantity; + const quantityKilled = data.q; expect(user.bank.amount('Shark')).toBeLessThan(1_000_000); expect(user.bank.amount('Vial of blood')).toEqual(1000 - quantityKilled); expect(user.bank.amount('Silver stake')).toEqual(1000 - quantityKilled); diff --git a/tests/integration/monsterKilling.test.ts b/tests/integration/monsterKilling.test.ts index 41e2d8d9293..fafe856aafd 100644 --- a/tests/integration/monsterKilling.test.ts +++ b/tests/integration/monsterKilling.test.ts @@ -1,21 +1,20 @@ import { Bank } from 'oldschooljs'; import { expect, test } from 'vitest'; -import { MonsterActivityTaskOptions } from '../../src/lib/types/minions'; -import { killCommand } from '../../src/mahoji/commands/k'; +import { minionKCommand } from '../../src/mahoji/commands/k'; import { createTestUser, mockClient } from './util'; test('Killing Men', async () => { - await mockClient(); + const client = await mockClient(); const user = await createTestUser(); const startingBank = new Bank().add('Shark', 1_000_000); await user.addItemsToBank({ items: startingBank }); await user.max(); - await user.runCommand(killCommand, { + await user.runCommand(minionKCommand, { name: 'general graardor' }); - (await user.runActivity()) as MonsterActivityTaskOptions; + await client.processActivities(); await user.sync(); expect(user.bank.amount('Shark')).toBeLessThan(1_000_000); diff --git a/tests/integration/paymentConflict.test.ts b/tests/integration/paymentConflict.test.ts index d44d5ba93e4..15abc78a3c7 100644 --- a/tests/integration/paymentConflict.test.ts +++ b/tests/integration/paymentConflict.test.ts @@ -1,13 +1,14 @@ -import { randArrItem, randInt, roll, Time } from 'e'; +import { Time, randArrItem, randInt, roll } from 'e'; import { Bank } from 'oldschooljs'; import { describe, expect, test } from 'vitest'; import { payCommand } from '../../src/mahoji/commands/pay'; -import { createTestUser, mockClient, TestUser } from './util'; +import type { TestUser } from './util'; +import { createTestUser, mockClient } from './util'; describe('Payment conflicts', async () => { - const payerCount = 50; - const iterations = 100; + const payerCount = 20; + const iterations = 20; const addChance = 3; const repeats = 1; diff --git a/tests/integration/preStartup.test.ts b/tests/integration/preStartup.test.ts new file mode 100644 index 00000000000..66911fbd99b --- /dev/null +++ b/tests/integration/preStartup.test.ts @@ -0,0 +1,9 @@ +import { test } from 'vitest'; + +import { preStartup } from '../../src/lib/preStartup'; +import { mockClient } from './util'; + +test.skip('PreStartup', async () => { + await mockClient(); + await preStartup(); +}); diff --git a/tests/integration/redis.test.ts b/tests/integration/redis.test.ts new file mode 100644 index 00000000000..86d14448935 --- /dev/null +++ b/tests/integration/redis.test.ts @@ -0,0 +1,105 @@ +import { expect, test } from 'vitest'; + +import { TSRedis } from '@oldschoolgg/toolkit/TSRedis'; +import { sleep } from 'e'; +import { BadgesEnum, BitField, globalConfig } from '../../src/lib/constants'; +import { roboChimpCache } from '../../src/lib/perkTier'; +import { getUsersPerkTier } from '../../src/lib/perkTiers'; +import { createTestUser } from './util'; + +function makeSender() { + return new TSRedis({ mocked: !globalConfig.redisPort, port: globalConfig.redisPort }); +} + +test('Should add patron badge', async () => { + const user = await createTestUser(); + expect(user.user.badges).not.includes(BadgesEnum.Patron); + const _redis = makeSender(); + await _redis.publish({ + type: 'patron_tier_change', + discord_ids: [user.id], + new_tier: 1, + old_tier: 0, + first_time_patron: false + }); + await sleep(250); + await user.sync(); + expect(user.user.badges).includes(BadgesEnum.Patron); +}); + +test('Should remove patron badge', async () => { + const user = await createTestUser(undefined, { badges: [BadgesEnum.Patron] }); + expect(user.user.badges).includes(BadgesEnum.Patron); + const _redis = makeSender(); + await _redis.publish({ + type: 'patron_tier_change', + discord_ids: [user.id], + new_tier: 0, + old_tier: 1, + first_time_patron: false + }); + await sleep(250); + await user.sync(); + expect(user.user.badges).not.includes(BadgesEnum.Patron); +}); + +test('Should add to cache', async () => { + const users = [await createTestUser(), await createTestUser(), await createTestUser()]; + await roboChimpClient.user.createMany({ + data: users.map(u => ({ + id: BigInt(u.id), + perk_tier: 5 + })) + }); + const _redis = makeSender(); + await _redis.publish({ + type: 'patron_tier_change', + discord_ids: users.map(u => u.id), + new_tier: 5, + old_tier: 2, + first_time_patron: false + }); + await sleep(250); + for (const user of users) { + const cached = roboChimpCache.get(user.id); + expect(getUsersPerkTier(user)).toEqual(5); + expect(cached!.perk_tier).toEqual(5); + } +}); + +test('Should remove from cache', async () => { + const users = [await createTestUser(), await createTestUser(), await createTestUser()]; + await roboChimpClient.user.createMany({ + data: users.map(u => ({ + id: BigInt(u.id), + perk_tier: 0 + })) + }); + const _redis = makeSender(); + await _redis.publish({ + type: 'patron_tier_change', + discord_ids: users.map(u => u.id), + new_tier: 0, + old_tier: 5, + first_time_patron: false + }); + await sleep(250); + for (const user of users) { + expect(getUsersPerkTier(user)).toEqual(0); + const cached = roboChimpCache.get(user.id); + expect(cached).toEqual(undefined); + } +}); + +test('Should recognize special bitfields', async () => { + expect(getUsersPerkTier(await createTestUser(undefined, { bitfield: [BitField.HasPermanentTierOne] }))).toEqual(3); + expect( + getUsersPerkTier(await createTestUser(undefined, { bitfield: [BitField.BothBotsMaxedFreeTierOnePerks] })) + ).toEqual(2); +}); + +test('Should sdffsddfss', async () => { + const user = await createTestUser(); + roboChimpCache.set(user.id, { perk_tier: 5 } as any); + expect(getUsersPerkTier(user)).toEqual(5); +}); diff --git a/tests/integration/repairBank.test.ts b/tests/integration/repairBank.test.ts new file mode 100644 index 00000000000..e0546917690 --- /dev/null +++ b/tests/integration/repairBank.test.ts @@ -0,0 +1,88 @@ +import { Bank } from 'oldschooljs'; +import { expect, test } from 'vitest'; + +import type { Prisma } from '@prisma/client'; +import { deepClone } from 'e'; +import { EquipmentSlot } from 'oldschooljs/dist/meta/types'; +import { TOBMaxMeleeGear } from '../../src/lib/data/tob'; +import { repairBrokenItemsFromUser } from '../../src/lib/util/repairBrokenItems'; +import resolveItems from '../../src/lib/util/resolveItems'; +import { createTestUser, mockClient } from './util'; + +test('Repair Bank', async () => { + await mockClient(); + const user = await createTestUser(); + const bank = new Bank().add('Coins', 1000).add('Coal', 100).add('Egg', 50).add('Trout', 999); + const gear = TOBMaxMeleeGear.clone().raw(); + gear[EquipmentSlot.Body] = null; + const cl = bank.clone(); + const temp_cl = bank.clone(); + const favoriteItems = resolveItems(['Coins', 'Coal', 'Egg', 'Trout']); + const expectedData: Parameters<(typeof user)['update']>['0'] = { + bank: bank.bank, + collectionLogBank: cl.bank, + temp_cl: temp_cl.bank, + favoriteItems: favoriteItems, + gear_fashion: gear as any as Prisma.InputJsonValue, + gear_melee: gear as any as Prisma.InputJsonValue, + gear_mage: gear as any as Prisma.InputJsonValue, + gear_range: gear as any as Prisma.InputJsonValue, + gear_misc: {} + }; + await user.update(expectedData); + expect(user.user).toMatchObject(expectedData); + expect(await repairBrokenItemsFromUser(user)).toEqual('You have no broken items on your account!'); + expect(user.user).toMatchObject(expectedData); + + const brokenData: any = deepClone(expectedData); + brokenData.bank[3535] = 5; + brokenData.collectionLogBank[33333] = 5; + brokenData.temp_cl[8888888] = 5; + brokenData.favoriteItems.push(9999999); + brokenData.gear_fashion[EquipmentSlot.Body] = { item: 353535, quantity: 1 }; + brokenData.gear_range[EquipmentSlot.Body] = { item: 222222, quantity: 1 }; + brokenData.gear_misc[EquipmentSlot.Body] = { item: 3111111, quantity: 1 }; + + const newUser = await prisma.user.update({ + where: { + id: user.id + }, + data: brokenData + }); + user.user = newUser; + + expect(await repairBrokenItemsFromUser(user)).toContain('broken items in your bank/collection log/favorites/gea'); + expect(user.user).toMatchObject(expectedData); + await user.sync(); + expect(user.user).toMatchObject(expectedData); + const copyUser = await mUserFetch(user.id); + expect(copyUser.user).toMatchObject(expectedData); + expect(user.user.bank).toStrictEqual(expectedData.bank); + expect(user.gear.fashion.allItems(false).sort()).toStrictEqual( + resolveItems([ + 'Torva full helm', + 'Amulet of torture', + 'Tzkal cape', + 'Torva gloves', + 'Torva platelegs', + 'Torva boots', + 'Drygore longsword', + 'Offhand drygore longsword', + 'Ignis ring (i)' + ]).sort() + ); + expect(user.gear.range.allItems(false).sort()).toStrictEqual( + resolveItems([ + 'Torva full helm', + 'Amulet of torture', + 'Tzkal cape', + 'Torva gloves', + 'Torva platelegs', + 'Torva boots', + 'Drygore longsword', + 'Offhand drygore longsword', + 'Ignis ring (i)' + ]).sort() + ); + expect(user.gear.misc.allItems(false).sort()).toStrictEqual(resolveItems([])); +}); diff --git a/tests/integration/rolesTask.test.ts b/tests/integration/rolesTask.test.ts index 804ec2c9ceb..94cfb8ec43b 100644 --- a/tests/integration/rolesTask.test.ts +++ b/tests/integration/rolesTask.test.ts @@ -3,12 +3,12 @@ import { Bank } from 'oldschooljs'; import { describe, expect, test } from 'vitest'; import { runRolesTask } from '../../src/lib/rolesTask'; -import { MinigameName, Minigames } from '../../src/lib/settings/minigames'; -import { cryptoRand } from '../../src/lib/util'; +import type { MinigameName } from '../../src/lib/settings/minigames'; +import { Minigames } from '../../src/lib/settings/minigames'; import { userStatsBankUpdate } from '../../src/mahoji/mahojiSettings'; -import { createTestUser, mockedId } from './util'; +import { createTestUser, mockedId, unMockedCyptoRand } from './util'; -describe('Roles Task', async () => { +describe.skip('Roles Task', async () => { test('Should not throw', async () => { const user = await createTestUser(); await userStatsBankUpdate(user.id, 'sacrificed_bank', new Bank().add('Coal', 10_000)); @@ -20,7 +20,7 @@ describe('Roles Task', async () => { }); const ironUser = await createTestUser(); await ironUser.update({ minion_ironman: true, sacrificedValue: 1_000_000 }); - await userStatsBankUpdate(ironUser.id, 'sacrificed_bank', new Bank().add('Coal', 10_000)); + await userStatsBankUpdate(ironUser, 'sacrificed_bank', new Bank().add('Coal', 10_000)); // Create minigame scores: const minigames = Minigames.map(game => game.column).filter(i => i !== 'tithe_farm'); @@ -44,12 +44,12 @@ describe('Roles Task', async () => { message_id: mockedId(), reaction_id: mockedId(), users_entered: [], - id: cryptoRand(1, 10_000_000), + id: unMockedCyptoRand(1, 10_000_000), completed: false, duration: 10_000 } }); - const result = await runRolesTask(); + const result = await runRolesTask(true); expect(result).toBeTruthy(); expect(result).includes('Roles'); }); diff --git a/tests/integration/setup.ts b/tests/integration/setup.ts index b75f8b50a92..d787ece4d57 100644 --- a/tests/integration/setup.ts +++ b/tests/integration/setup.ts @@ -1,15 +1,18 @@ import '../globalSetup'; +import '../../src/lib/globals'; +import '../../src/lib/util/transactItemsFromBank'; +import './mocks'; -import { afterEach, beforeEach, vi } from 'vitest'; +import { Image } from '@napi-rs/canvas'; +import { beforeEach, vi } from 'vitest'; -import { prisma } from '../../src/lib/settings/prisma'; +import { PrismaClient } from '@prisma/client'; +import { noOp } from 'e'; +import { BankImageTask, bankImageTask } from '../../src/lib/bankImage'; -vi.mock('../../src/lib/util/handleMahojiConfirmation', () => ({ - handleMahojiConfirmation: vi.fn() -})); -vi.mock('../../src/lib/util/interactionReply', () => ({ - deferInteraction: vi.fn() -})); +if (!roboChimpClient) { + throw new Error('Robochimp client not found.'); +} export function randomMock(random = 0.1) { Math.random = () => random; @@ -19,18 +22,23 @@ vi.mock('../../src/lib/util/webhook', async () => { const actual: any = await vi.importActual('../../src/lib/util/webhook'); return { ...actual, - sendToChannelID: async (_args: any) => {} + sendToChannelID: vi.fn() + }; +}); + +vi.mock('../../src/lib/gear/functions/generateGearImage', async () => { + const actual: any = await vi.importActual('../../src/lib/gear/functions/generateGearImage'); + return { + ...actual, + generateGearImage: vi.fn().mockReturnValue(Promise.resolve(Buffer.from(''))) }; }); -vi.mock('../../src/lib/leagues/stats', async () => { - const actual: any = await vi.importActual('../../src/lib/leagues/stats'); +vi.mock('../../src/lib/util/chart', async () => { + const actual: any = await vi.importActual('../../src/lib/gear/functions/generateGearImage'); return { ...actual, - calcLeaguesRanking: async () => ({ - pointsRanking: 1, - tasksRanking: 1 - }) + createChart: vi.fn().mockReturnValue(Promise.resolve(Buffer.from(''))) }; }); @@ -40,16 +48,32 @@ globalClient.fetchUser = async (id: string | bigint) => ({ send: async () => {} }); -beforeEach(async () => { - await prisma.$connect(); -}); +const mockBankImageTask = { + init: vi.fn(), + run: vi.fn(), + generateBankImage: vi.fn().mockReturnValue(Promise.resolve({ image: Buffer.from(''), isTransparent: false })), + getItemImage: vi.fn().mockReturnValue(Promise.resolve(new Image())), + fetchAndCacheImage: vi.fn().mockReturnValue(Promise.resolve(new Image())), + backgroundImages: [] +}; -afterEach(async () => { - await prisma.$disconnect(); -}); +bankImageTask.fetchAndCacheImage = mockBankImageTask.fetchAndCacheImage; +global.bankImageGenerator = mockBankImageTask as any; +BankImageTask.prototype.init = mockBankImageTask.init; +BankImageTask.prototype.run = mockBankImageTask.init; +BankImageTask.prototype.generateBankImage = mockBankImageTask.generateBankImage; +BankImageTask.prototype.getItemImage = mockBankImageTask.getItemImage; +BankImageTask.prototype.fetchAndCacheImage = mockBankImageTask.fetchAndCacheImage; -async function init() { - await prisma.$queryRaw`CREATE EXTENSION IF NOT EXISTS intarray;`; -} +const __prismaClient = new PrismaClient(); +__prismaClient.$queryRawUnsafe('CREATE EXTENSION IF NOT EXISTS intarray;').then(noOp).catch(noOp); -init(); +beforeEach(async () => { + global.prisma = __prismaClient; + global.bankImageGenerator = mockBankImageTask as any; + BankImageTask.prototype.init = mockBankImageTask.init; + BankImageTask.prototype.run = mockBankImageTask.init; + BankImageTask.prototype.generateBankImage = mockBankImageTask.generateBankImage; + BankImageTask.prototype.getItemImage = mockBankImageTask.getItemImage; + BankImageTask.prototype.fetchAndCacheImage = mockBankImageTask.fetchAndCacheImage; +}); diff --git a/tests/integration/tradeTransaction.test.ts b/tests/integration/tradeTransaction.test.ts index deeb8c24672..77a3a5f7430 100644 --- a/tests/integration/tradeTransaction.test.ts +++ b/tests/integration/tradeTransaction.test.ts @@ -1,4 +1,4 @@ -import { Prisma } from '@prisma/client'; +import type { Prisma } from '@prisma/client'; import { Bank } from 'oldschooljs'; import { describe, expect, test } from 'vitest'; @@ -6,10 +6,11 @@ import { tradePlayerItems } from '../../src/lib/util/tradePlayerItems'; import { mockedId } from './util'; describe('Transactionalized Trade Test', async () => { - async function createUserWithBank(bank: Bank, userData: Partial = {}) { + async function createUserWithBank(_bank: Bank, userData: Partial = {}) { const userId = mockedId(); + const bank = _bank.clone(); const GP = bank.amount('Coins'); - delete bank.bank[995]; + bank.remove('Coins', GP); await global.prisma!.user.create({ data: { id: userId, GP, bank: bank.bank, ...userData } @@ -105,46 +106,66 @@ describe('Transactionalized Trade Test', async () => { }); test('Test not enough GP trade...', async () => { - const cyrStartingBank = new Bank().add('Coins', 1_000_000).add('Twisted bow', 2).add('Dragon arrow', 1000); + const cyrStartingBank = new Bank() + .add('Coins', 1_000_000) + .add('Twisted bow', 2) + .add('Dragon arrow', 1000) + .freeze(); const cyr = await createUserWithBank(cyrStartingBank); - const magnaStartingBank = new Bank().add('Coins', 20_000_000).add('Cannonball', 10_000).add('Feather', 500); + const magnaStartingBank = new Bank() + .add('Coins', 20_000_000) + .add('Cannonball', 10_000) + .add('Feather', 500) + .freeze(); const magna = await createUserWithBank(magnaStartingBank); - const uCyr = await mUserFetch(cyr); + const uCyr = await mUserFetch(cyr, { username: 'Cyr' }); const uMagna = await mUserFetch(magna); - const tradeFromCyr = new Bank().add('Coins', 2_000_000).add('Twisted bow', 1); - const tradeFromMagna = new Bank().add('Coins', 2_000_000).add('Feather', 500).add('Cannonball', 2000); + expect(uCyr.GP).toBe(1_000_000); + expect(uMagna.GP).toBe(20_000_000); + + const tradeFromCyr = new Bank().add('Coins', 2_000_000).add('Twisted bow', 1).freeze(); + const tradeFromMagna = new Bank().add('Coins', 2_000_000).add('Feather', 500).add('Cannonball', 2000).freeze(); const result = await tradePlayerItems(uCyr, uMagna, tradeFromCyr, tradeFromMagna); - const expectedResult = { success: false, message: `<@${cyr}> doesn't own all items.` }; + const expectedResult = { success: false, message: `Cyr doesn't own all items.` }; expect(result).toMatchObject(expectedResult); - expect(uCyr.bank.equals(cyrStartingBank)).toBe(true); - expect(uMagna.bank.equals(magnaStartingBank)).toBe(true); + expect(uCyr.bankWithGP.toString()).toEqual(cyrStartingBank.toString()); + expect(uCyr.bankWithGP.equals(cyrStartingBank)).toBe(true); + expect(uMagna.bankWithGP.equals(magnaStartingBank)).toBe(true); }); test('Test not enough items trade...', async () => { - const cyrStartingBank = new Bank().add('Coins', 1_000_000).add('Twisted bow', 2).add('Dragon arrow', 1000); + const cyrStartingBank = new Bank() + .add('Coins', 1_000_000) + .add('Twisted bow', 2) + .add('Dragon arrow', 1000) + .freeze(); const cyr = await createUserWithBank(cyrStartingBank); - const magnaStartingBank = new Bank().add('Coins', 20_000_000).add('Cannonball', 10_000).add('Feather', 500); + const magnaStartingBank = new Bank() + .add('Coins', 20_000_000) + .add('Cannonball', 10_000) + .add('Feather', 500) + .freeze(); const magna = await createUserWithBank(magnaStartingBank); const uCyr = await mUserFetch(cyr); - const uMagna = await mUserFetch(magna); + const uMagna = await mUserFetch(magna, { username: 'magna' }); - const tradeFromCyr = new Bank().add('Coins', 1_000_000).add('Twisted bow', 1); - const tradeFromMagna = new Bank().add('Coins', 2_000_000).add('Feather', 5000).add('Cannonball', 2000); + const tradeFromCyr = new Bank().add('Coins', 1_000_000).add('Twisted bow', 1).freeze(); + const tradeFromMagna = new Bank().add('Coins', 2_000_000).add('Feather', 5000).add('Cannonball', 2000).freeze(); const result = await tradePlayerItems(uCyr, uMagna, tradeFromCyr, tradeFromMagna); - const expectedResult = { success: false, message: `<@${magna}> doesn't own all items.` }; + const expectedResult = { success: false, message: `magna doesn't own all items.` }; expect(result).toMatchObject(expectedResult); - expect(uCyr.bank.equals(cyrStartingBank)).toBe(true); - expect(uMagna.bank.equals(magnaStartingBank)).toBe(true); + expect(uCyr.bankWithGP.equals(cyrStartingBank)).toBe(true); + expect(uMagna.bankWithGP.equals(magnaStartingBank)).toBe(true); }); }); diff --git a/tests/integration/trading.test.ts b/tests/integration/trading.test.ts index ac19db7f0e4..1cb9065481b 100644 --- a/tests/integration/trading.test.ts +++ b/tests/integration/trading.test.ts @@ -3,18 +3,20 @@ import { Bank } from 'oldschooljs'; import { expect, test } from 'vitest'; import { tradeCommand } from '../../src/mahoji/commands/trade'; -import { createTestUser, mockClient, TestUser } from './util'; +import type { TestUser } from './util'; +import { createTestUser, mockClient } from './util'; test('Trade consistency', async () => { await mockClient(); const bank = new Bank().add('Coins', 1000).add('Egg', 1000).add('Coal', 1000).add('Trout', 1000).freeze(); - const NUMBER_OF_USERS = 50; + const NUMBER_OF_USERS = 20; - const users: TestUser[] = []; + let users: TestUser[] = []; for (let i = 0; i < NUMBER_OF_USERS; i++) { - users.push(await createTestUser(bank)); + users.push(createTestUser(bank) as any); } + users = await Promise.all(users); function checkMatch() { const expectedBank = bank.clone().multiply(NUMBER_OF_USERS); @@ -27,9 +29,9 @@ test('Trade consistency', async () => { checkMatch(); - for (let i = 0; i < 3; i++) { - const promises = []; + const promises = []; + for (let i = 0; i < 3; i++) { for (const user of shuffleArr(users)) { const other = randArrItem(users); const method = randArrItem(['send', 'receive', 'both']); diff --git a/tests/integration/tripEffects.test.ts b/tests/integration/tripEffects.test.ts new file mode 100644 index 00000000000..ddf5c2c8789 --- /dev/null +++ b/tests/integration/tripEffects.test.ts @@ -0,0 +1,30 @@ +import { activity_type_enum } from '@prisma/client'; +import { Time } from 'e'; +import { Monsters } from 'oldschooljs'; +import { expect, test } from 'vitest'; + +import { minionKCommand } from '../../src/mahoji/commands/k'; +import { createTestUser, mockClient, mockMathRandom } from './util'; + +test('Random Events', async () => { + const unmock = mockMathRandom(0.03); + const client = await mockClient(); + const user = await createTestUser(); + await user.runCommand(minionKCommand, { name: 'man' }); + await prisma.activity.updateMany({ + where: { + user_id: BigInt(user.id), + type: activity_type_enum.MonsterKilling + }, + data: { + duration: Time.Hour + } + }); + await client.processActivities(); + expect(await user.getKC(Monsters.Man.id)).toBeGreaterThan(1); + const userStats = await user.fetchStats({ random_event_completions_bank: true }); + await user.sync(); + expect(userStats.random_event_completions_bank).toEqual({ 1: 1 }); + expect(user.bank.amount("Beekeeper's hat")).toEqual(1); + unmock(); +}); diff --git a/tests/integration/tsconfig.json b/tests/integration/tsconfig.json index 1ed63529c0f..47325df54ae 100644 --- a/tests/integration/tsconfig.json +++ b/tests/integration/tsconfig.json @@ -1,5 +1,8 @@ { "extends": "../../tsconfig.base.json", "include": [".", "../../src/**/*"], + "compilerOptions": { + "noEmit": true + }, "references": [{ "path": "../../src" }] } diff --git a/tests/integration/userStats.test.ts b/tests/integration/userStats.test.ts index f6815ebaba5..f0a74eef4c6 100644 --- a/tests/integration/userStats.test.ts +++ b/tests/integration/userStats.test.ts @@ -23,11 +23,11 @@ describe('User Stats', async () => { expect(result).toEqual({ user_id: BigInt(userID) }); const result2 = await userStatsUpdate( userID, - () => ({ + { ash_sanctifier_prayer_xp: { increment: 100 } - }), + }, {} ); expect(result2).toEqual({ user_id: BigInt(userID) }); diff --git a/tests/integration/util.ts b/tests/integration/util.ts index f06d9a51072..61eb66035fb 100644 --- a/tests/integration/util.ts +++ b/tests/integration/util.ts @@ -1,32 +1,33 @@ -import { Prisma } from '@prisma/client'; -import { randInt, shuffleArr, Time, uniqueArr } from 'e'; -import { CommandRunOptions } from 'mahoji'; +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import type { Prisma } from '@prisma/client'; +import { Time, randInt, shuffleArr, uniqueArr } from 'e'; import { Bank } from 'oldschooljs'; +import { integer, nodeCrypto } from 'random-js'; +import { vi } from 'vitest'; -import { globalConfig } from '../../src/lib/constants'; import { MUserClass } from '../../src/lib/MUser'; -import { convertStoredActivityToFlatActivity, prisma } from '../../src/lib/settings/prisma'; import { processPendingActivities } from '../../src/lib/Task'; -import { ItemBank } from '../../src/lib/types'; -import { ActivityTaskOptions } from '../../src/lib/types/minions'; -import { cryptoRand } from '../../src/lib/util'; +import { globalConfig } from '../../src/lib/constants'; +import type { ItemBank } from '../../src/lib/types'; import { giveMaxStats } from '../../src/mahoji/commands/testpotato'; import { ironmanCommand } from '../../src/mahoji/lib/abstracted_commands/ironmanCommand'; -import { OSBMahojiCommand } from '../../src/mahoji/lib/util'; -import { ClientStorage, User, UserStats } from '.prisma/client'; +import type { OSBMahojiCommand } from '../../src/mahoji/lib/util'; +import type { ClientStorage, User, UserStats } from '.prisma/client'; -export const commandRunOptions = (userID: string): Omit => ({ +const commandRunOptions = (userID: string): Omit => ({ userID, guildID: '342983479501389826', member: {} as any, user: { id: userID, createdAt: new Date().getTime() - Time.Year } as any, channelID: '111111111', interaction: { + channelId: '1', deferReply: () => Promise.resolve(), editReply: () => Promise.resolve(), followUp: () => Promise.resolve() } as any, - client: {} as any + client: {} as any, + djsClient: {} as any }); export class TestUser extends MUserClass { @@ -68,7 +69,6 @@ export class TestUser extends MUserClass { async runCommand(command: OSBMahojiCommand, options: object = {}) { const result = await command.run({ ...commandRunOptions(this.id), options }); - await this.sync(); return result; } @@ -103,18 +103,6 @@ export class TestUser extends MUserClass { return this; } - async runActivity(): Promise { - const [finishedActivity] = await processPendingActivities(); - if (!finishedActivity) { - throw new Error('runActivity: No activity was ran'); - } - if (finishedActivity.user_id.toString() !== this.id) { - throw new Error('runActivity: Ran activity, but it didnt belong to this user'); - } - const data = convertStoredActivityToFlatActivity(finishedActivity); - return data as any; - } - randomBankSubset() { const bank = new Bank(); const items = shuffleArr(this.bankWithGP.items()).slice(0, randInt(0, this.bankWithGP.length)); @@ -127,25 +115,43 @@ export class TestUser extends MUserClass { const idsUsed = new Set(); +export function unMockedCyptoRand(min: number, max: number) { + return integer(min, max)(nodeCrypto); +} + export function mockedId() { - return cryptoRand(1_000_000_000, 5_000_000_000_000).toString(); + return unMockedCyptoRand(1_000_000_000_000, 5_000_000_000_000).toString(); } -export async function createTestUser(bank?: Bank, userData: Partial = {}) { +export async function createTestUser(_bank?: Bank, userData: Partial = {}) { const id = userData?.id ?? mockedId(); if (idsUsed.has(id)) { throw new Error(`ID ${id} has already been used`); } idsUsed.add(id); + + const bank = _bank ? _bank.clone() : null; + let GP = userData.GP ? Number(userData.GP) : undefined; + if (bank) { + if (GP) { + GP += bank.amount('Coins'); + } else { + GP = bank.amount('Coins'); + } + bank.remove('Coins', GP); + } + const user = await global.prisma!.user.upsert({ create: { id, ...userData, - bank: bank?.bank + bank: bank?.bank, + GP: GP ?? undefined }, update: { ...userData, - bank: bank?.bank + bank: bank?.bank, + GP }, where: { id @@ -187,6 +193,10 @@ class TestClient { throw new Error(`Expected ${key} to be ${value} but got ${this.data[key]}`); } } + + async processActivities() { + await processPendingActivities(); + } } export async function mockClient() { @@ -204,3 +214,9 @@ export async function mockClient() { if (uniqueArr([mockedId(), mockedId(), mockedId()]).length !== 3) { throw new Error('mockedId is broken'); } + +const originalMathRandom = Math.random; +export function mockMathRandom(value: number) { + vi.spyOn(Math, 'random').mockImplementation(() => value); + return () => (Math.random = originalMathRandom); +} diff --git a/tests/integration/wildyGearLost.bso.test.ts b/tests/integration/wildyGearLost.bso.test.ts index 96d67dcca87..85d94769643 100644 --- a/tests/integration/wildyGearLost.bso.test.ts +++ b/tests/integration/wildyGearLost.bso.test.ts @@ -3,6 +3,7 @@ import { Monsters } from 'oldschooljs'; import { expect, test } from 'vitest'; import { Gear } from '../../src/lib/structures/Gear'; +import type { MonsterActivityTaskOptions } from '../../src/lib/types/minions'; import { monsterTask } from '../../src/tasks/minions/monsterActivity'; import { createTestUser, mockClient } from './util'; @@ -27,8 +28,8 @@ test('calculateGearLostOnDeathWilderness', async () => { await monsterTask.run({ type: 'MonsterKilling', - monsterID: Monsters.Venenatis.id, - quantity: 10, + mi: Monsters.Venenatis.id, + q: 10, died: true, pkEncounters: 3, hasWildySupplies: true, @@ -37,7 +38,7 @@ test('calculateGearLostOnDeathWilderness', async () => { id: 123, finishDate: new Date().getTime(), channelID: '' - }); + } as MonsterActivityTaskOptions); await user.sync(); expect(user.gear.wildy.toString()).toEqual("Abyssal cape, Inquisitor's plateskirt, Ignis ring"); diff --git a/tests/unit/Gear.test.ts b/tests/unit/Gear.test.ts index 87b0a004948..4ec3741e40d 100644 --- a/tests/unit/Gear.test.ts +++ b/tests/unit/Gear.test.ts @@ -1,10 +1,10 @@ -import { GearPreset } from '@prisma/client'; +import type { GearPreset } from '@prisma/client'; import { Bank } from 'oldschooljs'; import { itemID } from 'oldschooljs/dist/util'; import { describe, expect, it, test } from 'vitest'; import { GearStat } from '../../src/lib/gear/types'; -import { constructGearSetup, Gear } from '../../src/lib/structures/Gear'; +import { Gear, constructGearSetup } from '../../src/lib/structures/Gear'; import { itemNameFromID } from '../../src/lib/util'; import getOSItem from '../../src/lib/util/getOSItem'; diff --git a/tests/unit/GeneralBank.fuzz.test.ts b/tests/unit/GeneralBank.fuzz.test.ts deleted file mode 100644 index 05015242def..00000000000 --- a/tests/unit/GeneralBank.fuzz.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import fc from 'fast-check'; -import { describe, expect, it } from 'vitest'; - -import { GeneralBank } from '../../src/lib/structures/GeneralBank'; - -describe('GeneralBank Fuzz Tests', () => { - it('maintains valid value ranges on add', () => { - fc.assert( - fc.property( - fc - .integer({ min: 1, max: 1000 }) - .chain(min => - fc - .integer({ min, max: 1000 }) - .chain(max => - fc - .dictionary(fc.string(), fc.integer({ min, max })) - .map(initialBank => ({ initialBank, min, max })) - ) - ), - fc.boolean(), - ({ initialBank, min, max }, floats) => { - const valueSchema = { min, max, floats }; - const bank = new GeneralBank({ - initialBank, - valueSchema: valueSchema as any - }); - for (const [key, value] of Object.entries(initialBank)) { - expect(value).toBeGreaterThanOrEqual(min); - expect(value).toBeLessThanOrEqual(max); - if (!floats) { - expect(Number.isInteger(value)).toBe(true); - } - } - } - ) - ); - }); - - it('validates all entries on cloning', () => { - fc.assert( - fc.property(fc.dictionary(fc.string(), fc.integer({ min: 1 })), initialBank => { - const bank = new GeneralBank({ initialBank }); - const clonedBank = bank.clone(); - expect(clonedBank.entries()).toEqual(bank.entries()); - }) - ); - }); -}); diff --git a/tests/unit/GeneralBank.test.ts b/tests/unit/GeneralBank.test.ts deleted file mode 100644 index eb6acde5c57..00000000000 --- a/tests/unit/GeneralBank.test.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { beforeEach, describe, expect, it, test } from 'vitest'; - -import { GeneralBank } from '../../src/lib/structures/GeneralBank'; - -describe('GeneralBank', () => { - // eslint-disable-next-line @typescript-eslint/init-declarations - let bank: GeneralBank; - // eslint-disable-next-line @typescript-eslint/init-declarations - let validator: (key: string, value: number, bank: Record) => void; - - beforeEach(() => { - validator = (key, value, bank) => { - if (!key.startsWith('F')) throw new Error(`Key ${key} does not start with F`); - }; - bank = new GeneralBank({ validator }); - }); - - it('initializes an empty bank correctly', () => { - expect(bank.toString()).toBe('Bank is empty'); - }); - - it('adds and retrieves an item correctly', () => { - bank.add('Fgold', 100); - expect(bank.amount('Fgold')).toBe(100); - }); - - it('throws an error if adding an item with a key not starting with F', () => { - expect(() => bank.add('gold', 100)).toThrow('Key gold does not start with F'); - }); - - it('removes items correctly and respects item non-existence', () => { - bank.add('Fsilver', 50); - bank.remove('Fsilver', 20); - expect(bank.amount('Fsilver')).toBe(30); - expect(() => bank.remove('Fsilver', 50)).toThrow(); - }); - - it('handles cloning correctly', () => { - bank.add('Fcopper', 200); - const newBank = bank.clone(); - expect(newBank.amount('Fcopper')).toBe(200); - newBank.add('Fcopper', 100); - expect(bank.amount('Fcopper')).toBe(200); - expect(newBank.amount('Fcopper')).toBe(300); - }); - - it('supports adding another bank', () => { - const otherBank = new GeneralBank(); - otherBank.add('Fbronze', 300); - bank.add(otherBank); - expect(bank.amount('Fbronze')).toBe(300); - }); - - it('supports removing quantities via another bank', () => { - bank.add('Firon', 500); - const otherBank = new GeneralBank(); - otherBank.add('Firon', 200); - bank.remove(otherBank); - expect(bank.amount('Firon')).toBe(300); - }); -}); - -describe('GeneralBank 2', () => { - // eslint-disable-next-line @typescript-eslint/init-declarations - let bank: GeneralBank; - - beforeEach(() => { - bank = new GeneralBank(); - }); - - it('rejects negative quantities for addition', () => { - expect(() => bank.add('Fgold', -10)).toThrow('Quantity must be non-negative.'); - }); - - it('rejects negative quantities for removal', () => { - bank.add('Fsilver', 20); - expect(() => bank.remove('Fsilver', -5)).toThrow('Quantity must be non-negative.'); - }); - - it('handles zero quantity operations without changing the bank', () => { - bank.add('Fgold', 100); - bank.add('Fgold', 0); - expect(bank.amount('Fgold')).toBe(100); - bank.remove('Fgold', 0); - expect(bank.amount('Fgold')).toBe(100); - }); - - it('ensures remove does not fall below zero', () => { - bank.add('Firon', 50); - expect(() => bank.remove('Firon', 51)).toThrow('Not enough Firon to remove.'); - }); - - it('ensures immutability after cloning', () => { - bank.add('Fsilver', 100); - const cloneBank = bank.clone(); - cloneBank.add('Fsilver', 50); - expect(bank.amount('Fsilver')).toBe(100); - expect(cloneBank.amount('Fsilver')).toBe(150); - }); - - it('handles complex validators', () => { - const complexBank = new GeneralBank({ - validator: (key, value, bank) => { - if (value > 1000) throw new Error('Values above 1000 are not allowed'); - } - }); - complexBank.add('Fgold', 500); - expect(() => complexBank.add('Fgold', 600)).toThrow('Values above 1000 are not allowed'); - }); - - it('processes high volume operations correctly', async () => { - for (let i = 0; i < 1000; i++) { - bank.add(`Fitem${i}`, i + 1); - } - expect(bank.amount('Fitem999')).toBe(1000); - for (let i = 0; i < 1000; i++) { - bank.remove(`Fitem${i}`, i + 1); - } - expect(bank.amount('Fitem999')).toBe(0); - }); -}); - -describe('Bank with allowedKeys', () => { - // eslint-disable-next-line @typescript-eslint/init-declarations - let bank: GeneralBank; - - beforeEach(() => { - // Initialize the bank with a set of allowed keys - bank = new GeneralBank({ allowedKeys: ['Fgold', 'Fsilver', 'Fcopper'] }); - }); - - it('allows adding items with allowed keys', () => { - expect(() => bank.add('Fgold', 100)).not.toThrow(); - expect(bank.amount('Fgold')).toBe(100); - }); - - it('prevents adding items with disallowed keys', () => { - expect(() => bank.add('Fplatinum', 50)).toThrow('Key Fplatinum is not allowed.'); - }); - - it('allows removing items with allowed keys', () => { - bank.add('Fsilver', 50); - expect(() => bank.remove('Fsilver', 25)).not.toThrow(); - expect(bank.amount('Fsilver')).toBe(25); - }); - - it('throws error on attempt to clone with disallowed key modifications', () => { - const cloneBank = bank.clone(); - expect(() => cloneBank.add('Firon', 100)).toThrow('Key Firon is not allowed.'); - }); - - it('ensures that operations on cloned banks respect original allowed keys', () => { - const cloneBank = bank.clone(); - cloneBank.add('Fsilver', 200); - expect(() => cloneBank.add('Fbronze', 100)).toThrow('Key Fbronze is not allowed.'); - expect(cloneBank.amount('Fsilver')).toBe(200); - }); - - it('should throw for floats in int bank', () => { - const b = new GeneralBank(); - expect(() => b.add('a', 0.15)).toThrow(); - }); -}); - -test('Float Banks', () => { - const floatBank = new GeneralBank({ valueSchema: { floats: true, min: 0, max: 1_222_222.100_150_02 } }); - floatBank.add('a', 1); - floatBank.add('a', 0.15); - expect(floatBank.amount('a')).toBe(1.15); - floatBank.add('b', 0.100_15); - expect(floatBank.amount('b')).toBe(0.100_15); - floatBank.add('b', 0.000_000_01); - expect(floatBank.amount('b')).toBe(0.100_150_01); - floatBank.add('b', 1_222_222); - expect(floatBank.amount('b')).toBe(1_222_222.100_150_01); - expect(floatBank.entries()).toEqual([ - ['a', 1.15], - ['b', 1_222_222.100_150_01] - ]); -}); diff --git a/tests/unit/bankBackgrounds.test.ts b/tests/unit/bankBackgrounds.test.ts index 9eb537e88cc..1745daf451f 100644 --- a/tests/unit/bankBackgrounds.test.ts +++ b/tests/unit/bankBackgrounds.test.ts @@ -1,4 +1,4 @@ -import fs from 'fs'; +import fs from 'node:fs'; import { describe, expect, test } from 'vitest'; import backgroundImages from '../../src/lib/minions/data/bankBackgrounds'; diff --git a/tests/unit/banksnapshots.test.ts b/tests/unit/banksnapshots.test.ts index 92232acef26..63ba546fa8b 100644 --- a/tests/unit/banksnapshots.test.ts +++ b/tests/unit/banksnapshots.test.ts @@ -1,3 +1,4 @@ +import { isFunction } from 'e'; import { Bank } from 'oldschooljs'; import { expect, it } from 'vitest'; @@ -9,9 +10,9 @@ import { mockMUser } from './utils'; it(`${BOT_TYPE} Creatables`, () => { const result = Createables.map(i => ({ ...i, - inputItems: new Bank(i.inputItems), - outputItems: new Bank(i.outputItems), - cantHaveItems: Boolean(i.cantHaveItems) ? new Bank(i.cantHaveItems) : undefined + inputItems: isFunction(i.inputItems) ? 'function' : new Bank(i.inputItems), + outputItems: isFunction(i.outputItems) ? 'function' : new Bank(i.outputItems), + cantHaveItems: i.cantHaveItems ? new Bank(i.cantHaveItems) : undefined })); expect(result).toMatchSnapshot(); }); @@ -23,8 +24,8 @@ it(`${BOT_TYPE} Buyables`, () => { outputItems: !i.outputItems ? undefined : i.outputItems instanceof Bank - ? i.outputItems - : i.outputItems(mockMUser()) + ? i.outputItems + : i.outputItems(mockMUser()) })); expect(result).toMatchSnapshot(); }); diff --git a/tests/unit/bso/compCape.bso.test.ts b/tests/unit/bso/compCape.bso.test.ts new file mode 100644 index 00000000000..8a4fa51fa98 --- /dev/null +++ b/tests/unit/bso/compCape.bso.test.ts @@ -0,0 +1,19 @@ +import { expect, test } from 'vitest'; + +import getOSItem from '../../../src/lib/util/getOSItem'; + +test('comp cape reqs', () => { + const items = [ + getOSItem('Completionist cape'), + getOSItem('Completionist cape (t)'), + getOSItem('Completionist hood') + ]; + for (const item of items) { + expect(item.equipment!.requirements?.agility).toEqual(120); + expect(item.equipment!.requirements?.attack).toEqual(120); + // @ts-ignore ignore + expect(item.equipment!.requirements?.divination).toEqual(120); + } + // @ts-ignore ignore + expect(getOSItem("Gatherer's cape").equipment!.requirements?.divination).toEqual(120); +}); diff --git a/tests/unit/bso/dyedItems.bso.test.ts b/tests/unit/bso/dyedItems.bso.test.ts new file mode 100644 index 00000000000..5f6b567ed76 --- /dev/null +++ b/tests/unit/bso/dyedItems.bso.test.ts @@ -0,0 +1,21 @@ +import { uniqueArr } from 'e'; +import { expect, test } from 'vitest'; + +import { allMbTables } from '../../../src/lib/bsoOpenables'; +import { dyedItems } from '../../../src/lib/dyedItems'; +import { isSuperUntradeable } from '../../../src/lib/util'; +import itemIsTradeable from '../../../src/lib/util/itemIsTradeable'; + +test('No duplicate dyed items', () => { + const all = dyedItems.map(i => i.dyedVersions.map(i => i.item.id)).flat(2); + const allUnique = uniqueArr(all); + expect(all.length).toEqual(allUnique.length); +}); + +test('all dyed items should be untradeable and not in boxes', () => { + for (const item of dyedItems.flatMap(i => i.dyedVersions.map(t => t.item))) { + expect(itemIsTradeable(item.id)).toBe(false); + expect(isSuperUntradeable(item.id)).toBe(true); + expect(allMbTables.includes(item.id)).toBe(false); + } +}); diff --git a/tests/unit/bso/mysteryboxes.bso.test.ts b/tests/unit/bso/mysteryboxes.bso.test.ts new file mode 100644 index 00000000000..d7f9d6a11f6 --- /dev/null +++ b/tests/unit/bso/mysteryboxes.bso.test.ts @@ -0,0 +1,121 @@ +import { itemID, resolveItems } from 'oldschooljs/dist/util'; +import { expect, test } from 'vitest'; + +import { PMBTable, allMbTables, embTable, tmbTable, umbTable } from '../../../src/lib/bsoOpenables'; +import { toaCL } from '../../../src/lib/data/CollectionsExport'; +import { growablePets } from '../../../src/lib/growablePets'; +import { itemNameFromID } from '../../../src/lib/util'; + +test("Items that shouldn't be dropped in mystery boxes", () => { + const shouldntBeIn = resolveItems([ + 'Coins', + 'Tester gift box', + 'Abyssal pouch', + 'Cob', + 'Runite stone spirit', + 'Coal stone spirit', + 'Frozen santa hat', + 'Flappy meal', + 'Seer', + 'Pretzel', + 'Smokey painting', + 'Festive present', + 'Smokey', + 'Pink partyhat', + 'Santa hat', + 'Dwarven ore', + '100 sided die', + 'Party horn', + 'Diamond crown', + 'Snappy the Turtle', + 'Liber tea', + 'Invention master cape', + 'Portable tanner', + 'Clue upgrader', + 'Justiciar armour set', + 'Justiciar legguards', + 'Justiciar chestguard', + 'Justiciar faceguard', + 'Accursed sceptre', + 'Masori assembler max cape', + ...toaCL + ]); + for (const i of shouldntBeIn) { + if (allMbTables.includes(i)) { + throw new Error(`${itemNameFromID(i)} is in the mystery box tables, but it shouldn't be.`); + } + } +}); +test('exclude certain openables from mystery boxes', () => { + // These items appear in some Openables but should still also appear in Mystery boxes: + const shouldBeIn = resolveItems([ + 'Coal', + 'Blacksmith helmet', + 'Blacksmith boots', + 'Blacksmith gloves', + 'Uncut sapphire', + 'Oak plank', + 'Pure essence', + 'Runite bolts', + 'Lava flower crown', + 'Purple flower crown' + ]); + // These items should all still excluded by the 'Openables' rule. Some items are also excluded by other means. + const shouldntBeIn = resolveItems([ + 'Christmas cracker', + 'White partyhat', + 'Corgi', + 'Beach ball', + 'Glass of bubbly', + 'Sparkler', + 'Liber tea', + 'Party music box', + '6 sided die', + 'Huge lamp', + 'Ancient hilt', + 'Nihil horn', + 'Zaryte vambraces', + 'Ancient godsword', + 'Seed pack', + 27_499, + 27_828, + 'Paint box', + 'Ruby Red paint can', + 'Scurry', + 'Trailblazer reloaded dragon trophy', + 'Trailblazer reloaded rune trophy', + 'Trailblazer reloaded adamant trophy', + 'Trailblazer reloaded mithril trophy', + 'Trailblazer reloaded steel trophy', + 'Trailblazer reloaded iron trophy', + 'Trailblazer reloaded bronze trophy' + ]); + for (const i of shouldntBeIn) { + if (allMbTables.includes(i)) { + console.error('wtf'); + throw new Error(`Item ${itemNameFromID(i)} shouldn't be in Mystery Boxes, but is.`); + } + } + for (const i of shouldBeIn) { + if (!allMbTables.includes(i)) { + console.error('wtf'); + throw new Error(`Item ${itemNameFromID(i)} should be in Mystery Boxes, but isn't.`); + } + } + expect(shouldBeIn.every(ss => allMbTables.includes(ss))).toEqual(true); + expect(shouldntBeIn.some(ss => allMbTables.includes(ss))).toEqual(false); +}); + +test('Growable pets cant come from mystery boxes', () => { + const allGrowablePets = growablePets.flatMap(p => p.stages); + expect(allGrowablePets.every(growablePet => !PMBTable.allItems.includes(growablePet))).toEqual(true); + expect(allGrowablePets.every(growablePet => !allMbTables.includes(growablePet))).toEqual(true); +}); + +test('CMB should not be in any boxes', () => { + expect(tmbTable.includes(itemID('Clothing Mystery Box'))).toEqual(false); + expect(umbTable.includes(itemID('Clothing Mystery Box'))).toEqual(false); + expect(tmbTable.includes(itemID('Swanky boots'))).toEqual(false); + expect(embTable.includes(itemID('Swanky boots'))).toEqual(false); + expect(umbTable.includes(itemID('Swanky boots'))).toEqual(false); +}); diff --git a/tests/unit/bso/similarItems.bso.test.ts b/tests/unit/bso/similarItems.bso.test.ts new file mode 100644 index 00000000000..64d733c8322 --- /dev/null +++ b/tests/unit/bso/similarItems.bso.test.ts @@ -0,0 +1,16 @@ +import { expect, test } from 'vitest'; + +import { Gear } from '../../../src/lib/structures/Gear'; + +test('comp cape similar items', () => { + const gear = new Gear(); + gear.equip('Completionist cape'); + expect(gear.hasEquipped("Combatant's cape")).toEqual(true); + expect(gear.hasEquipped('Support cape')).toEqual(true); + + const gear2 = new Gear(); + gear2.equip('Completionist cape (t)'); + + expect(gear2.hasEquipped("Combatant's cape")).toEqual(true); + expect(gear2.hasEquipped('Support cape')).toEqual(true); +}); diff --git a/tests/unit/bso/tradeability.bso.test.ts b/tests/unit/bso/tradeability.bso.test.ts new file mode 100644 index 00000000000..b1b4e40c05a --- /dev/null +++ b/tests/unit/bso/tradeability.bso.test.ts @@ -0,0 +1,37 @@ +import { itemID } from 'oldschooljs/dist/util'; +import { expect, test } from 'vitest'; + +import { masterCapesCL } from '../../../src/lib/data/CollectionsExport'; +import { isSuperUntradeable } from '../../../src/lib/util'; +import getOSItem from '../../../src/lib/util/getOSItem'; +import itemIsTradeable from '../../../src/lib/util/itemIsTradeable'; + +test('santa hats should be tradeable', () => { + expect(itemIsTradeable(itemID('Black santa hat'))).toEqual(true); + expect(itemIsTradeable(itemID('Inverted santa hat'))).toEqual(true); + expect(itemIsTradeable(itemID('Santa hat'))).toEqual(true); + expect(itemIsTradeable(itemID('Coal'))).toEqual(true); + expect(itemIsTradeable(itemID('Golden partyhat'))).toEqual(true); + expect(itemIsTradeable(itemID('Rune pouch'))).toEqual(true); + expect(itemIsTradeable(itemID('Agility cape'))).toEqual(true); + expect(itemIsTradeable(itemID('Achievement diary cape'))).toEqual(true); + expect(itemIsTradeable(itemID('Crafting master cape'))).toEqual(false); + expect(itemIsTradeable(itemID('Infernal bulwark'))).toEqual(false); +}); + +test('isSuperUntradeable', () => { + expect(isSuperUntradeable(getOSItem('TzKal Cape'))).toEqual(true); + expect(isSuperUntradeable(getOSItem("TzKal-Zuk's skin"))).toEqual(true); + expect(isSuperUntradeable(getOSItem('Jal-MejJak'))).toEqual(true); + expect(isSuperUntradeable(getOSItem('Infernal slayer helmet'))).toEqual(true); + expect(isSuperUntradeable(getOSItem('Infernal slayer helmet(i)'))).toEqual(true); + expect(isSuperUntradeable(getOSItem('TzKal cape'))).toEqual(true); + expect(isSuperUntradeable(getOSItem('Head of TzKal Zuk'))).toEqual(true); + expect(isSuperUntradeable(getOSItem('Infernal bulwark'))).toEqual(true); + expect(isSuperUntradeable(getOSItem('Infernal core'))).toEqual(true); + expect(isSuperUntradeable(getOSItem('Seed pack'))).toEqual(true); + + for (const cape of masterCapesCL) { + expect(isSuperUntradeable(cape)).toEqual(true); + } +}); diff --git a/tests/unit/calcPOHBoosts.test.ts b/tests/unit/calcPOHBoosts.test.ts index a54c5320bf6..6cd5262acfa 100644 --- a/tests/unit/calcPOHBoosts.test.ts +++ b/tests/unit/calcPOHBoosts.test.ts @@ -1,4 +1,4 @@ -import { PlayerOwnedHouse } from '@prisma/client'; +import type { PlayerOwnedHouse } from '@prisma/client'; import { describe, expect, test } from 'vitest'; import { calcPOHBoosts, getPOHObject } from '../../src/lib/poh'; diff --git a/tests/unit/circular.test.ts b/tests/unit/circular.test.ts deleted file mode 100644 index 10f575f9d8b..00000000000 --- a/tests/unit/circular.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Time } from 'e'; -import madge from 'madge'; -import { describe, expect, test } from 'vitest'; - -describe('Circular Dependencies', () => { - test( - 'Circular Dependencies', - async () => { - const res = Object.values(await madge('./dist/index.js').then(res => res.circularGraph())).flat(2); - expect(res).toEqual([]); - }, - { timeout: Time.Minute } - ); -}); diff --git a/tests/unit/clsnapshots.test.ts b/tests/unit/clsnapshots.test.ts index 8ad09c03edd..5dcb49a062c 100644 --- a/tests/unit/clsnapshots.test.ts +++ b/tests/unit/clsnapshots.test.ts @@ -1,8 +1,9 @@ import { expect, it } from 'vitest'; +import { alphabeticalSort } from '@oldschoolgg/toolkit'; import { BOT_TYPE } from '../../src/lib/constants'; import { allCLItemsFiltered, allCollectionLogsFlat } from '../../src/lib/data/Collections'; -import { alphabeticalSort, itemNameFromID } from '../../src/lib/util'; +import { itemNameFromID } from '../../src/lib/util/smallUtils'; it(`${BOT_TYPE} Overall Collection Log Items`, () => { expect( diff --git a/tests/unit/dyedItems.test.ts b/tests/unit/dyedItems.test.ts deleted file mode 100644 index e3afe790352..00000000000 --- a/tests/unit/dyedItems.test.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { uniqueArr } from 'e'; -import { describe, expect, test } from 'vitest'; - -import { dyedItems } from '../../src/lib/dyedItems'; - -describe('Dyed Items', () => { - test('', () => { - let all = dyedItems.map(i => i.dyedVersions.map(i => i.item.id)).flat(2); - let allUnique = uniqueArr(all); - expect(all.length).toEqual(allUnique.length); - }); -}); diff --git a/tests/unit/getUsersPerkTier.test.ts b/tests/unit/getUsersPerkTier.test.ts deleted file mode 100644 index 68f92ebf4aa..00000000000 --- a/tests/unit/getUsersPerkTier.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Time } from 'e'; -import { describe, expect, test } from 'vitest'; - -import { BitField, PerkTier } from '../../src/lib/constants'; -import { MUserClass } from '../../src/lib/MUser'; -import { getUsersPerkTier } from '../../src/lib/perkTiers'; -import { mockMUser } from './utils'; - -describe('getUsersPerkTier', () => { - test('general', () => { - expect(getUsersPerkTier([])).toEqual(0); - expect(getUsersPerkTier(mockMUser())).toEqual(0); - expect(getUsersPerkTier(mockMUser({ bitfield: [BitField.IsPatronTier3] }))).toEqual(PerkTier.Four); - expect(getUsersPerkTier(mockMUser({ bitfield: [BitField.isModerator] }))).toEqual(PerkTier.Four); - }); - test('balance', () => { - const user = mockMUser({ premium_balance_expiry_date: Date.now() + Time.Day, premium_balance_tier: 3 }); - expect(user instanceof MUserClass).toEqual(true); - expect(user.user.premium_balance_tier !== null).toEqual(true); - expect(user.perkTier()).toEqual(PerkTier.Four); - }); -}); diff --git a/tests/unit/http/http.test.ts b/tests/unit/http/http.test.ts deleted file mode 100644 index 5cd54042af5..00000000000 --- a/tests/unit/http/http.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { afterAll, beforeAll, expect, test } from 'vitest'; - -import { makeServer } from '../../../src/lib/http'; -import { FastifyServer } from '../../../src/lib/http/types'; - -let app: FastifyServer = null!; -beforeAll(async () => { - app = await makeServer(); - await app.ready(); -}); - -test('Commands route', async () => { - const response = await app.inject({ - method: 'GET', - url: '/commands' - }); - - expect(response.statusCode).toBe(200); - expect(response.payload).toEqual('[{"name":"test","desc":"test description","subOptions":[]}]'); -}); - -test('github route', async () => { - const response = await app.inject({ - method: 'POST', - url: '/webhooks/github_sponsors' - }); - - expect(response.statusCode).toBe(400); - expect(response.payload).toEqual('{"statusCode":400,"error":"Bad Request","message":"Bad Request"}'); - - const response2 = await app.inject({ - method: 'POST', - url: '/webhooks/github_sponsors', - payload: {}, - headers: { - 'x-hub-signature': 'test' - } - }); - - expect(response2.statusCode).toBe(400); - expect(response2.payload).toEqual('{"statusCode":400,"error":"Bad Request","message":"Bad Request"}'); -}); - -test('root route ratelimiting', async () => { - await app.inject({ - method: 'GET', - url: '/' - }); - const response = await app.inject({ - method: 'GET', - url: '/' - }); - - expect(response.statusCode).toBe(429); -}); - -afterAll(async () => { - await app.close(); -}); diff --git a/tests/unit/http/patreon.test.ts b/tests/unit/http/patreon.test.ts deleted file mode 100644 index 29239b4e353..00000000000 --- a/tests/unit/http/patreon.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { createHmac } from 'node:crypto'; - -import { afterAll, beforeAll, expect, test } from 'vitest'; - -import { makeServer } from '../../../src/lib/http'; -import { FastifyServer } from '../../../src/lib/http/types'; -import { mockPatreonWebhookSecret } from '../setup'; - -let app: FastifyServer = null!; - -beforeAll(async () => { - app = await makeServer(); - await app.ready(); -}); - -test('patreon route', async () => { - const response = await app.inject({ - method: 'POST', - url: '/webhooks/patreon' - }); - - expect(response.statusCode).toBe(400); - expect(response.payload).toEqual('{"statusCode":400,"error":"Bad Request","message":"Bad Request"}'); - - const response2 = await app.inject({ - method: 'POST', - url: '/webhooks/patreon', - payload: {} - }); - - expect(response2.statusCode).toBe(401); - expect(response2.payload).toEqual('{"statusCode":401,"error":"Unauthorized","message":"Unauthorized"}'); - - const response3 = await app.inject({ - method: 'POST', - url: '/webhooks/patreon', - payload: {}, - headers: { - 'x-patreon-signature': 'test' - } - }); - - expect(response3.statusCode).toBe(401); - expect(response3.payload).toEqual('{"statusCode":401,"error":"Unauthorized","message":"Unauthorized"}'); - - const payload = { someData: 'data' }; - const hmac = createHmac('md5', mockPatreonWebhookSecret); - hmac.update(JSON.stringify(payload)); - const calculated = hmac.digest('hex'); - - const response4 = await app.inject({ - method: 'POST', - url: '/webhooks/patreon', - payload, - headers: { - 'x-patreon-signature': calculated - } - }); - - expect(response4.statusCode).toBe(200); - expect(response4.payload).toEqual('{}'); -}); - -afterAll(async () => { - await app.close(); -}); diff --git a/tests/unit/images.test.ts b/tests/unit/images.test.ts index 2ed5aff1fb3..e8544cae1cd 100644 --- a/tests/unit/images.test.ts +++ b/tests/unit/images.test.ts @@ -1,39 +1,37 @@ -import { configureToMatchImageSnapshot } from 'jest-image-snapshot'; +import { readFile, writeFile } from 'node:fs/promises'; +import { Time } from 'e'; +import deepEqual from 'fast-deep-equal'; import { Bank, Monsters } from 'oldschooljs'; -import { describe, expect, test } from 'vitest'; +import { describe, test } from 'vitest'; +import { MUserClass } from '../../src/lib/MUser'; import { drawChestLootImage } from '../../src/lib/bankImage'; import { clImageGenerator } from '../../src/lib/collectionLogTask'; +import { BOT_TYPE } from '../../src/lib/constants'; import { pohImageGenerator } from '../../src/lib/pohImage'; +import { type ChartOptions, createApexChartConfig, createChart } from '../../src/lib/util/chart'; import { mahojiChatHead } from '../../src/lib/util/chatHeadImage'; import { makeBankImage } from '../../src/lib/util/makeBankImage'; import { mockMUser } from './utils'; -declare module 'vitest' { - interface Assertion { - toMatchImageSnapshot(): T; - } -} +describe('Images', async () => { + await bankImageGenerator.ready; -const toMatchImageSnapshotPlugin = configureToMatchImageSnapshot({ - customSnapshotsDir: './tests/unit/snapshots', - noColors: true, - failureThreshold: 5, - failureThresholdType: 'percent' -}); -expect.extend({ toMatchImageSnapshot: toMatchImageSnapshotPlugin }); - -describe('Images', () => { - test('Chat Heads', async () => { + test.concurrent('Chat Heads', async () => { const result = await mahojiChatHead({ content: 'Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test', head: 'santa' }); - expect(result.files[0].attachment).toMatchImageSnapshot(); + await writeFile(`tests/unit/snapshots/chatHead.${BOT_TYPE}.png`, result.files[0].attachment); }); - test.skip('Collection Log', async () => { + test.concurrent('Collection Log', async () => { + // @ts-expect-error + global.prisma = { userStats: { upsert: () => {} } }; + MUserClass.prototype.fetchStats = async () => { + return { monster_scores: {} } as any; + }; const result: any = await clImageGenerator.generateLogImage({ user: mockMUser({ cl: new Bank().add('Harmonised orb') }), collection: 'nightmare', @@ -48,12 +46,12 @@ describe('Images', () => { highGambles: 1, gotrRiftSearches: 1 } - }); - expect(result.files[0].attachment).toMatchImageSnapshot(); + } as any); + await writeFile(`tests/unit/snapshots/cl.${BOT_TYPE}.png`, result.files[0].attachment); }); - test.skip('Bank Image', async () => { - let bank = new Bank(); + test.concurrent('Bank Image', async () => { + const bank = new Bank(); for (const item of [...Monsters.Cow.allItems]) { bank.add(item); } @@ -63,10 +61,10 @@ describe('Images', () => { bank, title: 'Test Image' }); - expect(result.file.attachment).toMatchImageSnapshot(); + await writeFile(`tests/unit/snapshots/bank.${BOT_TYPE}.png`, result.file.attachment); }); - test('POH Image', async () => { + test.concurrent('POH Image', async () => { const result = await pohImageGenerator.run({ prayer_altar: 13_197, throne: 13_667, @@ -74,18 +72,62 @@ describe('Images', () => { mounted_cape: 29_210, background_id: 1 } as any); - expect(result).toMatchImageSnapshot(); + await writeFile(`tests/unit/snapshots/poh.${BOT_TYPE}.png`, result); }); - // test('Chart Image', async () => { - // const result = await pieChart('Test', val => `${toKMB(val)}%`, [ - // ['Complete Collection Log Items', 20, '#9fdfb2'], - // ['Incomplete Collection Log Items', 80, '#df9f9f'] - // ]); - // expect(result).toMatchImageSnapshot(); - // }); + test( + 'Charts', + async () => { + const sampleData: Record<'kmb' | 'percent', ChartOptions['values'][]> = { + percent: [ + [ + ['Magna', 55], + ['Cyr', 45] + ] + ], + kmb: [ + [ + ['Twisted bow', 5_000_000_000], + ['Egg', 1_500_000_000], + ['Cat', 500_000_000], + ['Dog', 2500_000_000], + ['Trout', 4500_000_000] + ] + ] + } as const; + + for (const chartType of ['bar', 'line'] as const) { + for (const format of ['kmb', 'percent'] as const) { + const chartOptions: ChartOptions = { + type: chartType, + title: `${chartType} ${format} title`, + values: sampleData[format][0], + format: format + }; + + const config = createApexChartConfig(chartOptions); + const configFilePath = `tests/unit/snapshots/chart.${chartType}.${format}.json`; + const existingConfigRaw = await readFile(configFilePath, 'utf-8').catch(() => null); + if (existingConfigRaw) { + const existingConfig = JSON.parse(existingConfigRaw); + if (deepEqual(existingConfig, config)) { + console.log(`Skipping ${chartType} ${format} chart, no changes.`); + continue; + } + } + + const res = await createChart(chartOptions); + await writeFile(`tests/unit/snapshots/chart.${chartType}.${format}.png`, res); + await writeFile(configFilePath, `${JSON.stringify(config, null, 4)}\n`); + } + } + }, + { + timeout: Time.Second * 30 + } + ); - test('TOA Image', async () => { + test.concurrent('TOA Image', async () => { const image = await drawChestLootImage({ entries: [ { @@ -103,10 +145,10 @@ describe('Images', () => { ], type: 'Tombs of Amascut' }); - expect(image.attachment as Buffer).toMatchImageSnapshot(); + await writeFile(`tests/unit/snapshots/toa.${BOT_TYPE}.png`, image.attachment); }); - test('COX Image', async () => { + test.concurrent('COX Image', async () => { const image = await drawChestLootImage({ entries: [ { @@ -124,6 +166,6 @@ describe('Images', () => { ], type: 'Chambers of Xerician' }); - expect(image.attachment as Buffer).toMatchImageSnapshot(); + await writeFile(`tests/unit/snapshots/cox.${BOT_TYPE}.png`, image.attachment); }); }); diff --git a/tests/unit/interactionid.test.ts b/tests/unit/interactionid.test.ts new file mode 100644 index 00000000000..50187c493ca --- /dev/null +++ b/tests/unit/interactionid.test.ts @@ -0,0 +1,14 @@ +import { test } from 'vitest'; + +import { InteractionID } from '../../src/lib/InteractionID'; + +test('InteractionID', () => { + const allStrings = Object.values(InteractionID) + .map(obj => Object.values(obj)) + .flat(2); + for (const string of allStrings) { + if (string.length < 1 || string.length > 100) { + throw new Error(`String ${string} has length ${string.length} which is not between 1 and 100`); + } + } +}); diff --git a/tests/unit/itemSwitches.test.ts b/tests/unit/itemSwitches.test.ts new file mode 100644 index 00000000000..39882024bb6 --- /dev/null +++ b/tests/unit/itemSwitches.test.ts @@ -0,0 +1,25 @@ +import { writeFileSync } from 'node:fs'; +import { EquipmentSlot } from 'oldschooljs/dist/meta/types'; +import { getItemOrThrow } from 'oldschooljs/dist/util'; +import { expect, test } from 'vitest'; + +import { BOT_TYPE } from '../../src/lib/constants'; +import { itemDataSwitches } from '../../src/lib/data/itemAliases'; +import { itemNameFromID } from '../../src/lib/util/smallUtils'; + +test('Item Switches', () => { + writeFileSync( + `tests/unit/snapshots/itemSwitches.${BOT_TYPE}.json`, + `${JSON.stringify( + itemDataSwitches.map(a => ({ + from: `${itemNameFromID(a.from)} [${a.from}]`, + to: `${itemNameFromID(a.to)} [${a.to}]` + })), + null, + ' ' + )}\n` + ); + expect(getItemOrThrow('Ultor ring').equipment?.melee_strength).toBe(12); + expect(getItemOrThrow('Ultor ring').id).toBe(25485); + expect(getItemOrThrow('Ultor ring').equipment?.slot).toBe(EquipmentSlot.Ring); +}); diff --git a/tests/unit/parseStringBank.test.ts b/tests/unit/parseStringBank.test.ts index 7a1980af807..4a9f96fd8fa 100644 --- a/tests/unit/parseStringBank.test.ts +++ b/tests/unit/parseStringBank.test.ts @@ -1,23 +1,17 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { randInt } from 'e'; import { Bank, Items } from 'oldschooljs'; import { describe, expect, test } from 'vitest'; import getOSItem from '../../src/lib/util/getOSItem'; import itemID from '../../src/lib/util/itemID'; -import { - parseBank, - parseInputCostBank, - parseQuantityAndItem, - parseStringBank -} from '../../src/lib/util/parseStringBank'; +import { parseBank, parseQuantityAndItem, parseStringBank } from '../../src/lib/util/parseStringBank'; const psb = parseStringBank; const get = getOSItem; const pQI = parseQuantityAndItem; describe('Bank Parsers', () => { - test('parseStringBank', async () => { + test.concurrent('parseStringBank', async () => { const output = psb(` 1x twisted bow, coal, 5k egg, 1b trout, 5 ${itemID('Feather')} `); const expected = [ [get('Twisted bow'), 1], @@ -30,12 +24,14 @@ describe('Bank Parsers', () => { expect(expected).toEqual(expect.arrayContaining(output)); expect(output.length).toEqual(expected.length); for (let i = 0; i < output.length; i++) { - let [resItem, resQty] = output[i]; - let [expItem, expQty] = expected[i]; + const [resItem, resQty] = output[i]; + const [expItem, expQty] = expected[i]; expect(resItem).toEqual(expItem); expect(resQty).toEqual(expQty); } + }); + test.concurrent('parseStringBank2', async () => { expect(psb('')).toEqual([]); expect(psb(' ')).toEqual([]); expect(psb(', ')).toEqual([]); @@ -69,7 +65,7 @@ describe('Bank Parsers', () => { ]); }); - test('parseBank - flags', async () => { + test.concurrent('parseBank - flags', async () => { const bank = new Bank().add('Steel arrow').add('Bones').add('Coal').add('Clue scroll (easy)'); const res = parseBank({ inputBank: bank, @@ -90,7 +86,7 @@ describe('Bank Parsers', () => { expect(res3.length).toEqual(1); }); - test('parseBank - filters', async () => { + test.concurrent('parseBank - filters', async () => { const bank = new Bank().add('Steel arrow').add('Bones').add('Coal').add('Clue scroll (easy)'); const res = parseBank({ inputBank: bank, @@ -100,7 +96,7 @@ describe('Bank Parsers', () => { expect(res.amount('Clue scroll (easy)')).toEqual(1); }); - test('parseBank - search', async () => { + test.concurrent('parseBank - search', async () => { const bank = new Bank() .add('Steel arrow') .add('Bones') @@ -119,7 +115,7 @@ describe('Bank Parsers', () => { expect(res.amount('Rune arrow')).toEqual(1); }); - test('parseBank - inputStr', async () => { + test.concurrent('parseBank - inputStr', async () => { const bank = new Bank() .add('Steel arrow') .add('Bones', 2) @@ -146,7 +142,7 @@ describe('Bank Parsers', () => { expect(res2.amount('Bones')).toEqual(2); }); - test('parseBank - other', async () => { + test.concurrent('parseBank - other', async () => { const bank = new Bank() .add('Steel arrow') .add('Bones', 2) @@ -164,7 +160,7 @@ describe('Bank Parsers', () => { expect(res.amount('Coal')).toEqual(6); }); - test('parseBank - same item names', async () => { + test.concurrent('parseBank - same item names', async () => { const bank = new Bank().add(22_002); const res = parseBank({ inputBank: bank, @@ -175,7 +171,7 @@ describe('Bank Parsers', () => { expect(res.amount(22_002)).toEqual(1); }); - test('parseBank - extra number', async () => { + test.concurrent('parseBank - extra number', async () => { const bank = new Bank().add('Coal', 5).add('3rd age platebody', 100).add('Egg', 3); const res = parseBank({ inputBank: bank, @@ -191,7 +187,7 @@ describe('Bank Parsers', () => { expect(other.amount('Egg')).toEqual(3); }); - test('parseBank - look for nonexistent items', async () => { + test.concurrent('parseBank - look for nonexistent items', async () => { const bank = new Bank().add('Steel arrow').add('Bones').add('Coal', 500).add('Clue scroll (easy)'); expect(parseBank({ inputBank: bank, inputStr: '1 Portrait' }).toString()).toEqual('No items'); expect(parseBank({ inputBank: bank, inputStr: '1 666' }).toString()).toEqual('No items'); @@ -199,10 +195,10 @@ describe('Bank Parsers', () => { expect(parseBank({ inputBank: bank, inputStr: '0 cOaL' }).toString()).toEqual('500x Coal'); }); - test('parseBank - check item aliases', async () => { - const bank = new Bank().add('Arceuus graceful top', 30).add('Bones').add('Tradeable mystery box'); - expect(parseBank({ inputBank: bank, inputStr: 'pUrPle gRaceful top, 5 tmb' }).toString()).toEqual( - '30x Arceuus graceful top, 1x Tradeable Mystery Box' + test.concurrent('parseBank - check item aliases', async () => { + const bank = new Bank().add('Arceuus graceful top', 30).add('Bones'); + expect(parseBank({ inputBank: bank, inputStr: 'pUrPle gRaceful top' }).toString()).toEqual( + '30x Arceuus graceful top' ); }); @@ -218,7 +214,7 @@ describe('Bank Parsers', () => { ); }); - test('parseQuantityAndItem', () => { + test.concurrent('parseQuantityAndItem', () => { expect(pQI('')).toEqual([]); expect(pQI(' ,,, ')).toEqual([]); expect(pQI('1.5k twisted bow')).toEqual([[get('Twisted bow')], 1500]); @@ -259,123 +255,6 @@ describe('Bank Parsers', () => { expect(pQI('1.5k*10 twisted bow', testBank)).toEqual([[get('Twisted bow')], 10_000 * 1.5]); }); - test('parseInputCostBank', () => { - const usersBank = new Bank() - .add('Coal', 100) - .add('Egg', 3) - .add('Feather', 600) - .add('Twisted bow', 6) - .add('Shark', 1) - .add('Rune sword') - .add('Fire cape'); - - // - const result = parseInputCostBank({ usersBank, inputStr: undefined, flags: {}, excludeItems: [] }); - expect(result.length).toEqual(0); - - // - const result2 = parseInputCostBank({ usersBank, inputStr: undefined, flags: { all: 'all' }, excludeItems: [] }); - expect(result2.length).toEqual(usersBank.length); - - // - const result3 = parseInputCostBank({ - usersBank, - inputStr: '1+1 egg, 5 feather, 1 manta ray', - flags: {}, - excludeItems: [] - }); - expect(result3.length).toEqual(2); - expect(result3.bank).toStrictEqual(new Bank().add('Egg', 2).add('Feather', 5).bank); - - // - const result4 = parseInputCostBank({ - usersBank, - inputStr: '#-1 egg, # feather, # manta ray, -1 watermelon, 0 fire rune, #*5 soul rune', - flags: {}, - excludeItems: [] - }); - expect(result4.length).toEqual(2); - expect(result4.bank).toStrictEqual(new Bank().add('Egg', 2).add('Feather', 600).bank); - - // - const result5 = parseInputCostBank({ - usersBank, - inputStr: `#-1 ${itemID('Egg')}, 1 ${itemID('Feather')}`, - flags: {}, - excludeItems: [] - }); - expect(result5.bank).toStrictEqual(new Bank().add('Egg', 2).add('Feather', 1).bank); - expect(result5.length).toEqual(2); - - // - const result6 = parseInputCostBank({ - usersBank, - inputStr: '1 Shark', - flags: { untradeables: 'untradeables' }, - excludeItems: [] - }); - expect(result6.bank).toStrictEqual(new Bank().bank); - expect(result6.length).toEqual(0); - - // - const result7 = parseInputCostBank({ - usersBank, - inputStr: '1 Shark, 5 Fire cape', - flags: { untradeables: 'untradeables' }, - excludeItems: [] - }); - expect(result7.bank).toStrictEqual(new Bank().add('Fire cape').bank); - expect(result7.length).toEqual(1); - - // - const result8 = parseInputCostBank({ - usersBank, - inputStr: '1 Shark, 5 Fire cape', - flags: { equippables: 'equippables' }, - excludeItems: [] - }); - expect(result8.bank).toStrictEqual(new Bank().add('Fire cape').bank); - expect(result8.length).toEqual(1); - - // - const result9 = parseInputCostBank({ - usersBank, - inputStr: undefined, - flags: { equippables: 'equippables' }, - excludeItems: [] - }); - expect(result9.bank).toStrictEqual(new Bank().add('Fire cape').add('Rune sword').add('Twisted bow', 6).bank); - expect(result9.length).toEqual(3); - - // - const result10 = parseInputCostBank({ - usersBank, - inputStr: undefined, - flags: { equippables: 'equippables', qty: '1' }, - excludeItems: [] - }); - expect(result10.bank).toStrictEqual(new Bank().add('Fire cape').add('Rune sword').add('Twisted bow').bank); - expect(result10.length).toEqual(3); - - // - const result11 = parseInputCostBank({ - usersBank, - inputStr: 'egg, feather', - flags: {}, - excludeItems: [] - }); - expect(result11.bank).toStrictEqual(new Bank().add('Feather', 600).add('Egg', 3).bank); - expect(result11.length).toEqual(2); - - if ( - [result, result2, result3, result4, result5, result6, result7, result8, result9, result10].some( - b => b.has('Cannonball') || b.has('Toolkit') || b.has(11_525) - ) - ) { - throw new Error('Result had a cannonball/toolkit'); - } - }); - test('edge cases', () => { const usersBank = new Bank().add('Coal', 100).add('Huge lamp', 3); diff --git a/tests/unit/sanity.test.ts b/tests/unit/sanity.test.ts index a990423a09c..78a1a273536 100644 --- a/tests/unit/sanity.test.ts +++ b/tests/unit/sanity.test.ts @@ -1,14 +1,12 @@ -import { Tame, tame_growth } from '@prisma/client'; +import { type Tame, tame_growth } from '@prisma/client'; import { Bank, Items, Monsters } from 'oldschooljs'; import { EquipmentSlot } from 'oldschooljs/dist/meta/types'; -import { assert, describe, expect, test } from 'vitest'; +import { describe, expect, test } from 'vitest'; -import { allMbTables, embTable, PMBTable, tmbTable, umbTable } from '../../src/lib/bsoOpenables'; +import { allMbTables } from '../../src/lib/bsoOpenables'; +import { allPetIDs } from '../../src/lib/data/CollectionsExport'; import Buyables from '../../src/lib/data/buyables/buyables'; -import { allPetIDs, masterCapesCL, toaCL } from '../../src/lib/data/CollectionsExport'; import { itemsToDelete } from '../../src/lib/deletedItems'; -import { dyedItems } from '../../src/lib/dyedItems'; -import { growablePets } from '../../src/lib/growablePets'; import { marketPriceOfBank } from '../../src/lib/marketPrices'; import killableMonsters from '../../src/lib/minions/data/killableMonsters'; import { Ignecarus } from '../../src/lib/minions/data/killableMonsters/custom/bosses/Ignecarus'; @@ -17,7 +15,6 @@ import KingGoldemar from '../../src/lib/minions/data/killableMonsters/custom/bos import { VasaMagus } from '../../src/lib/minions/data/killableMonsters/custom/bosses/VasaMagus'; import { allOpenables } from '../../src/lib/openables'; import { Gear } from '../../src/lib/structures/Gear'; -import { exponentialPercentScale, isSuperUntradeable, itemNameFromID } from '../../src/lib/util'; import getOSItem from '../../src/lib/util/getOSItem'; import itemID from '../../src/lib/util/itemID'; import itemIsTradeable from '../../src/lib/util/itemIsTradeable'; @@ -26,143 +23,12 @@ import { calculateMaximumTameFeedingLevelGain } from '../../src/lib/util/tameUti import { BingoTrophies } from '../../src/mahoji/lib/bingo/BingoManager'; describe('Sanity', () => { - test('santa hats should be tradeable', () => { - expect(itemIsTradeable(itemID('Black santa hat'))).toEqual(true); - expect(itemIsTradeable(itemID('Inverted santa hat'))).toEqual(true); - expect(itemIsTradeable(itemID('Santa hat'))).toEqual(true); - expect(itemIsTradeable(itemID('Coal'))).toEqual(true); - expect(itemIsTradeable(itemID('Golden partyhat'))).toEqual(true); - expect(itemIsTradeable(itemID('Rune pouch'))).toEqual(true); - expect(itemIsTradeable(itemID('Agility cape'))).toEqual(true); - expect(itemIsTradeable(itemID('Achievement diary cape'))).toEqual(true); - expect(itemIsTradeable(itemID('Crafting master cape'))).toEqual(false); - expect(itemIsTradeable(itemID('Infernal bulwark'))).toEqual(false); - }); - test('Growable pets cant come from mystery boxes', () => { - const allGrowablePets = growablePets.map(p => p.stages).flat(); - expect(allGrowablePets.every(growablePet => !PMBTable.allItems.includes(growablePet))).toEqual(true); - expect(allGrowablePets.every(growablePet => !allMbTables.includes(growablePet))).toEqual(true); - }); - test('isSuperUntradeable', () => { - expect(isSuperUntradeable(getOSItem('TzKal Cape'))).toEqual(true); - expect(isSuperUntradeable(getOSItem("TzKal-Zuk's skin"))).toEqual(true); - expect(isSuperUntradeable(getOSItem('Jal-MejJak'))).toEqual(true); - expect(isSuperUntradeable(getOSItem('Infernal slayer helmet'))).toEqual(true); - expect(isSuperUntradeable(getOSItem('Infernal slayer helmet(i)'))).toEqual(true); - expect(isSuperUntradeable(getOSItem('TzKal cape'))).toEqual(true); - expect(isSuperUntradeable(getOSItem('Head of TzKal Zuk'))).toEqual(true); - expect(isSuperUntradeable(getOSItem('Infernal bulwark'))).toEqual(true); - expect(isSuperUntradeable(getOSItem('Infernal core'))).toEqual(true); - expect(isSuperUntradeable(getOSItem('Seed pack'))).toEqual(true); - - for (const cape of masterCapesCL) { - expect(isSuperUntradeable(cape)).toEqual(true); - } - }); test('avas', () => { expect(new Gear({ cape: "Ava's assembler" }).hasEquipped("Ava's assembler")).toEqual(true); expect(new Gear({ cape: 'Assembler max cape' }).hasEquipped("Ava's assembler", true, true)).toEqual(true); expect(new Gear({ cape: "Combatant's cape" }).hasEquipped("Ava's assembler", true, true)).toEqual(true); }); - test('cant be dropped by mystery boxes', () => { - const shouldntBeIn = resolveItems([ - 'Coins', - 'Tester gift box', - 'Abyssal pouch', - 'Cob', - 'Runite stone spirit', - 'Coal stone spirit', - 'Frozen santa hat', - 'Flappy meal', - 'Seer', - 'Pretzel', - 'Smokey painting', - 'Festive present', - 'Smokey', - 'Pink partyhat', - 'Santa hat', - 'Dwarven ore', - '100 sided die', - 'Party horn', - 'Diamond crown', - 'Snappy the Turtle', - 'Liber tea', - 'Invention master cape', - 'Portable tanner', - 'Clue upgrader', - 'Justiciar armour set', - 'Justiciar legguards', - 'Justiciar chestguard', - 'Justiciar faceguard', - 'Accursed sceptre', - 'Masori assembler max cape', - ...toaCL - ]); - for (const i of shouldntBeIn) { - if (allMbTables.includes(i)) { - throw new Error(`${itemNameFromID(i)} is in the mystery box tables, but it shouldn't be.`); - } - } - }); - test('exclude certain openables from mystery boxes', () => { - // These items appear in some Openables but should still also appear in Mystery boxes: - const shouldBeIn = resolveItems([ - 'Coal', - 'Blacksmith helmet', - 'Blacksmith boots', - 'Blacksmith gloves', - 'Uncut sapphire', - 'Oak plank', - 'Pure essence', - 'Runite bolts', - 'Lava flower crown', - 'Purple flower crown' - ]); - // These items should all still excluded by the 'Openables' rule. Some items are also excluded by other means. - const shouldntBeIn = resolveItems([ - 'Christmas cracker', - 'White partyhat', - 'Corgi', - 'Beach ball', - 'Glass of bubbly', - 'Sparkler', - 'Liber tea', - 'Party music box', - '6 sided die', - 'Huge lamp', - 'Ancient hilt', - 'Nihil horn', - 'Zaryte vambraces', - 'Ancient godsword', - 'Seed pack', - 27_499, - 27_828, - 'Paint box', - 'Ruby Red paint can', - 'Scurry', - 'Trailblazer reloaded dragon trophy', - 'Trailblazer reloaded rune trophy', - 'Trailblazer reloaded adamant trophy', - 'Trailblazer reloaded mithril trophy', - 'Trailblazer reloaded steel trophy', - 'Trailblazer reloaded iron trophy', - 'Trailblazer reloaded bronze trophy' - ]); - for (const i of shouldntBeIn) { - if (allMbTables.includes(i)) { - console.error('wtf'); - throw new Error(`Item ${itemNameFromID(i)} shouldn't be in Mystery Boxes, but is.`); - } - } - for (const i of shouldBeIn) { - if (!allMbTables.includes(i)) { - console.error('wtf'); - throw new Error(`Item ${itemNameFromID(i)} should be in Mystery Boxes, but isn't.`); - } - } - expect(shouldBeIn.every(ss => allMbTables.includes(ss))).toEqual(true); - expect(shouldntBeIn.some(ss => allMbTables.includes(ss))).toEqual(false); - }); + test('custom monsters', () => { expect(killableMonsters.some(m => m.name === 'Frost Dragon')).toBeTruthy(); expect(killableMonsters.some(m => m.name === 'Sea Kraken')).toBeTruthy(); @@ -171,13 +37,7 @@ describe('Sanity', () => { expect(killableMonsters.some(i => i.id === id)).toEqual(false); } }); - test('fancy', () => { - expect(tmbTable.includes(itemID('Clothing Mystery Box'))).toEqual(false); - expect(umbTable.includes(itemID('Clothing Mystery Box'))).toEqual(false); - expect(tmbTable.includes(itemID('Swanky boots'))).toEqual(false); - expect(embTable.includes(itemID('Swanky boots'))).toEqual(false); - expect(umbTable.includes(itemID('Swanky boots'))).toEqual(false); - }); + test('misc', () => { expect(itemID('Phoenix')).toEqual(20_693); expect(itemID('Kalphite princess')).toEqual(12_647); @@ -244,7 +104,7 @@ describe('Sanity', () => { expect(itemID('Reward casket (master)')).toEqual(19_836); }); test('openables', () => { - let ids = new Set(); + const ids = new Set(); for (const openable of allOpenables) { if (getOSItem(openable.id) !== openable.openedItem) { throw new Error(`${openable.name} doesnt match`); @@ -255,25 +115,14 @@ describe('Sanity', () => { ids.add(openable.id); } }); - test('exponentialPercentScale', () => { - for (let i = 0; i < 100; i++) { - let num = exponentialPercentScale(i); - expect(num > 0 && num <= 100).toBeTruthy(); - } - expect(exponentialPercentScale(100)).toEqual(100); - }); + test('pharaohs sceptre', () => { const scep = getOSItem("Pharaoh's sceptre"); expect(scep.id).toEqual(9044); expect(scep.equipable).toEqual(true); expect(scep.equipment?.slot).toEqual(EquipmentSlot.Weapon); }); - test('all dyed items should be untradeable and not in boxes', () => { - for (const item of dyedItems.map(i => i.dyedVersions.map(t => t.item)).flat()) { - assert(!itemIsTradeable(item.id), `${item.name} should be super-untradeable`); - assert(!allMbTables.includes(item.id), `${item.name} shouldnt drop from boxes`); - } - }); + test("klik/divine shouldn't be openable", () => { for (const openable of allOpenables) { for (const name of ['Klik', 'Divine spirit shield']) { @@ -299,21 +148,7 @@ describe('Sanity', () => { } } }); - test('comp cape reqs', () => { - const items = [ - getOSItem('Completionist cape'), - getOSItem('Completionist cape (t)'), - getOSItem('Completionist hood') - ]; - for (const item of items) { - expect(item.equipment!.requirements?.agility).toEqual(120); - expect(item.equipment!.requirements?.attack).toEqual(120); - // @ts-ignore ignore - expect(item.equipment!.requirements?.divination).toEqual(120); - } - // @ts-ignore ignore - expect(getOSItem("Gatherer's cape").equipment!.requirements?.divination).toEqual(120); - }); + test('calculateMaximumTameFeedingLevelGain', () => { expect( calculateMaximumTameFeedingLevelGain({ @@ -323,18 +158,7 @@ describe('Sanity', () => { } as Tame) ).toEqual(14); }); - test('comp cape similar items', () => { - const gear = new Gear(); - gear.equip('Completionist cape'); - expect(gear.hasEquipped("Combatant's cape")).toEqual(true); - expect(gear.hasEquipped('Support cape')).toEqual(true); - - const gear2 = new Gear(); - gear2.equip('Completionist cape (t)'); - expect(gear2.hasEquipped("Combatant's cape")).toEqual(true); - expect(gear2.hasEquipped('Support cape')).toEqual(true); - }); test('market price of coins', () => { const b = new Bank().add('Coins', 66); expect(marketPriceOfBank(b)).toEqual(66); diff --git a/tests/unit/setup.ts b/tests/unit/setup.ts index 02314a59bc9..9d71430269b 100644 --- a/tests/unit/setup.ts +++ b/tests/unit/setup.ts @@ -1,11 +1,12 @@ import '../globalSetup'; +import { TSRedis } from '@oldschoolgg/toolkit/TSRedis'; import { vi } from 'vitest'; -import { globalConfig } from '../../src/lib/constants'; -import { MUserStats } from '../../src/lib/structures/MUserStats'; import { mockMUser, mockUserMap } from './utils'; +global.redis = new TSRedis({ mocked: true }); + vi.mock('../../src/lib/settings/prisma.ts', () => ({ __esModule: true, prisma: {} @@ -47,9 +48,6 @@ vi.mock('../../src/lib/patreon', async () => { }; }); -export const mockPatreonWebhookSecret = 'test'; -globalConfig.patreonWebhookSecret = mockPatreonWebhookSecret; - vi.mock('../../src/lib/settings/minigames.ts', async () => { const actual: any = await vi.importActual('../../src/lib/settings/minigames.ts'); return { @@ -57,10 +55,3 @@ vi.mock('../../src/lib/settings/minigames.ts', async () => { getMinigameEntity: async () => ({}) }; }); - -// @ts-ignore mock -MUserStats.fromID = async () => { - return new MUserStats({ - user_id: '' - } as any); -}; diff --git a/tests/unit/similarItems.test.ts b/tests/unit/similarItems.test.ts index b148c11d2d5..ee9f3b54363 100644 --- a/tests/unit/similarItems.test.ts +++ b/tests/unit/similarItems.test.ts @@ -1,11 +1,10 @@ -import { itemID } from 'oldschooljs/dist/util'; +import { itemID, resolveItems } from 'oldschooljs/dist/util'; import { describe, expect, test } from 'vitest'; import { gorajanWarriorOutfit } from '../../src/lib/data/CollectionsExport'; import { getSimilarItems } from '../../src/lib/data/similarItems'; import { Gear } from '../../src/lib/structures/Gear'; import { itemNameFromID } from '../../src/lib/util'; -import resolveItems from '../../src/lib/util/resolveItems'; describe('Gear', () => { const testGear = new Gear({ @@ -242,13 +241,13 @@ describe('Gear', () => { expect(getSimilarItems(itemID('Infernal max cape'))).toEqual([itemID('Infernal max cape')]); test('toa', () => { - let testGear = new Gear({ cape: 'Masori assembler max cape' }); + const testGear = new Gear({ cape: 'Masori assembler max cape' }); expect(testGear.hasEquipped("Ava's assembler")).toEqual(true); - let testGear2 = new Gear({ weapon: "Osmumten's fang (or)" }); + const testGear2 = new Gear({ weapon: "Osmumten's fang (or)" }); expect(testGear2.hasEquipped("Osmumten's fang")).toEqual(true); - let testGear3 = new Gear({ cape: 'Masori assembler' }); + const testGear3 = new Gear({ cape: 'Masori assembler' }); expect(testGear3.hasEquipped("Ava's assembler")).toEqual(true); - let testGear4 = new Gear({ cape: "Elidinis' ward (or)" }); + const testGear4 = new Gear({ cape: "Elidinis' ward (or)" }); expect(testGear4.hasEquipped("Elidinis' ward (f)")).toEqual(true); }); diff --git a/tests/unit/skillsMeetRequirements.test.ts b/tests/unit/skillsMeetRequirements.test.ts index 6509a611a81..f6cb50712cd 100644 --- a/tests/unit/skillsMeetRequirements.test.ts +++ b/tests/unit/skillsMeetRequirements.test.ts @@ -1,11 +1,11 @@ import { objectEntries } from 'e'; import { describe, expect, test } from 'vitest'; -import { Skills } from '../../src/lib/skilling/skills'; +import type { Skills } from '../../src/lib/skilling/skills'; import { convertLVLtoXP, skillsMeetRequirements } from '../../src/lib/util'; function convert(bank: Record) { - let newObj: Record = {}; + const newObj: Record = {}; for (const [key, val] of objectEntries(bank)) { newObj[key] = convertLVLtoXP(val); } diff --git a/tests/unit/slayer.test.ts b/tests/unit/slayer.test.ts index 8a7e7f7232c..72899cd489a 100644 --- a/tests/unit/slayer.test.ts +++ b/tests/unit/slayer.test.ts @@ -1,11 +1,25 @@ +import { writeFileSync } from 'node:fs'; + +import { objectEntries } from 'e'; import { describe, expect, test } from 'vitest'; +import { SlayerTaskUnlocksEnum } from '../../src/lib/slayer/slayerUnlocks'; import { allSlayerTasks } from '../../src/lib/slayer/tasks'; -describe('slayer.test', () => { - test('tasks', () => { +describe('Slayer', () => { + test('All slayer task monster lists should contain their main monster id', () => { for (const task of allSlayerTasks) { expect(task.monsters).toContain(task.monster.id); } }); + + test('Snapshot the values of the slayer unlocks enum', () => { + const copy = { ...SlayerTaskUnlocksEnum }; + for (const [key, value] of objectEntries(copy)) { + if (typeof value === 'string') { + delete copy[key]; + } + } + writeFileSync('./tests/unit/snapshots/slayerUnlocks.snapshot.json', `${JSON.stringify(copy, null, ' ')}\n`); + }); }); diff --git a/tests/unit/snapshots/bank.BSO.png b/tests/unit/snapshots/bank.BSO.png new file mode 100644 index 00000000000..f05ba0de488 Binary files /dev/null and b/tests/unit/snapshots/bank.BSO.png differ diff --git a/tests/unit/snapshots/bank.OSB.png b/tests/unit/snapshots/bank.OSB.png new file mode 100644 index 00000000000..83bbe180cf6 Binary files /dev/null and b/tests/unit/snapshots/bank.OSB.png differ diff --git a/tests/unit/snapshots/banksnapshots.test.ts.snap b/tests/unit/snapshots/banksnapshots.test.ts.snap index 66c1a722a1f..ebae1572aae 100644 --- a/tests/unit/snapshots/banksnapshots.test.ts.snap +++ b/tests/unit/snapshots/banksnapshots.test.ts.snap @@ -59,7 +59,7 @@ exports[`BSO Buyables 1`] = ` "feather", ], "gpCost": 50, - "ironmanPrice": 3, + "ironmanPrice": 4, "itemCost": Bank { "bank": {}, "frozen": false, @@ -965,6 +965,20 @@ exports[`BSO Buyables 1`] = ` "outputItems": undefined, "qpRequired": 172, }, + { + "gpCost": 5000, + "ironmanPrice": 500, + "itemCost": Bank { + "bank": {}, + "frozen": false, + }, + "name": "Lockpick", + "outputItems": undefined, + "skillsNeeded": { + "agility": 50, + "thieving": 50, + }, + }, { "itemCost": Bank { "bank": { @@ -4303,6 +4317,16 @@ exports[`BSO Buyables 1`] = ` "name": "Easter crate key (s5)", "outputItems": undefined, }, + { + "gpCost": 12000000, + "ironmanPrice": 1200000, + "itemCost": Bank { + "bank": {}, + "frozen": false, + }, + "name": "Birthday crate key (s6)", + "outputItems": undefined, + }, { "customReq": [Function], "gpCost": 1000000, @@ -9922,6 +9946,24 @@ exports[`BSO Creatables 1`] = ` "frozen": false, }, }, + { + "GPCost": 50000, + "cantHaveItems": undefined, + "inputItems": Bank { + "bank": { + "12783": 1, + "2572": 1, + }, + "frozen": false, + }, + "name": "Ring of wealth (i)", + "outputItems": Bank { + "bank": { + "12785": 1, + }, + "frozen": false, + }, + }, { "cantHaveItems": undefined, "inputItems": Bank { @@ -10234,6 +10276,42 @@ exports[`BSO Creatables 1`] = ` "frozen": false, }, }, + { + "cantHaveItems": undefined, + "inputItems": Bank { + "bank": { + "28924": 150000, + "28947": 1, + }, + "frozen": false, + }, + "name": "Blessed dizana's quiver", + "outputItems": Bank { + "bank": { + "28955": 1, + }, + "frozen": false, + }, + }, + { + "cantHaveItems": undefined, + "inputItems": Bank { + "bank": { + "13280": 1, + "13281": 1, + "28955": 1, + }, + "frozen": false, + }, + "name": "Dizana's max cape", + "outputItems": Bank { + "bank": { + "28902": 1, + "28904": 1, + }, + "frozen": false, + }, + }, { "cantHaveItems": undefined, "inputItems": Bank { @@ -11352,6 +11430,23 @@ exports[`BSO Creatables 1`] = ` "frozen": false, }, }, + { + "cantHaveItems": undefined, + "inputItems": Bank { + "bank": { + "28947": 1, + }, + "frozen": false, + }, + "name": "Revert Dizana's quiver (uncharged)", + "noCl": true, + "outputItems": Bank { + "bank": { + "28924": 4000, + }, + "frozen": false, + }, + }, { "QPRequired": 150, "cantHaveItems": undefined, @@ -19689,10 +19784,7 @@ exports[`BSO Creatables 1`] = ` }, { "cantHaveItems": undefined, - "inputItems": Bank { - "bank": {}, - "frozen": false, - }, + "inputItems": "function", "name": "Vasa cloak", "outputItems": Bank { "bank": { @@ -23785,10 +23877,7 @@ exports[`BSO Creatables 1`] = ` }, { "cantHaveItems": undefined, - "inputItems": Bank { - "bank": {}, - "frozen": false, - }, + "inputItems": "function", "name": "Divine water", "outputItems": Bank { "bank": { @@ -23799,10 +23888,7 @@ exports[`BSO Creatables 1`] = ` }, { "cantHaveItems": undefined, - "inputItems": Bank { - "bank": {}, - "frozen": false, - }, + "inputItems": "function", "name": "Divine water (Bones)", "outputItems": Bank { "bank": { @@ -23813,10 +23899,7 @@ exports[`BSO Creatables 1`] = ` }, { "cantHaveItems": undefined, - "inputItems": Bank { - "bank": {}, - "frozen": false, - }, + "inputItems": "function", "name": "Divine water (Big bones)", "outputItems": Bank { "bank": { @@ -23827,10 +23910,18 @@ exports[`BSO Creatables 1`] = ` }, { "cantHaveItems": undefined, - "inputItems": Bank { - "bank": {}, + "inputItems": "function", + "name": "Divine water (Jogre bones)", + "outputItems": Bank { + "bank": { + "50599": 1, + }, "frozen": false, }, + }, + { + "cantHaveItems": undefined, + "inputItems": "function", "name": "Divine water (Babydragon bones)", "outputItems": Bank { "bank": { @@ -23841,10 +23932,7 @@ exports[`BSO Creatables 1`] = ` }, { "cantHaveItems": undefined, - "inputItems": Bank { - "bank": {}, - "frozen": false, - }, + "inputItems": "function", "name": "Divine water (Dragon bones)", "outputItems": Bank { "bank": { @@ -23855,10 +23943,7 @@ exports[`BSO Creatables 1`] = ` }, { "cantHaveItems": undefined, - "inputItems": Bank { - "bank": {}, - "frozen": false, - }, + "inputItems": "function", "name": "Divine water (Wyrm bones)", "outputItems": Bank { "bank": { @@ -23869,10 +23954,7 @@ exports[`BSO Creatables 1`] = ` }, { "cantHaveItems": undefined, - "inputItems": Bank { - "bank": {}, - "frozen": false, - }, + "inputItems": "function", "name": "Divine water (Wyvern bones)", "outputItems": Bank { "bank": { @@ -23883,10 +23965,7 @@ exports[`BSO Creatables 1`] = ` }, { "cantHaveItems": undefined, - "inputItems": Bank { - "bank": {}, - "frozen": false, - }, + "inputItems": "function", "name": "Divine water (Drake bones)", "outputItems": Bank { "bank": { @@ -23897,10 +23976,7 @@ exports[`BSO Creatables 1`] = ` }, { "cantHaveItems": undefined, - "inputItems": Bank { - "bank": {}, - "frozen": false, - }, + "inputItems": "function", "name": "Divine water (Lava dragon bones)", "outputItems": Bank { "bank": { @@ -23911,10 +23987,7 @@ exports[`BSO Creatables 1`] = ` }, { "cantHaveItems": undefined, - "inputItems": Bank { - "bank": {}, - "frozen": false, - }, + "inputItems": "function", "name": "Divine water (Hydra bones)", "outputItems": Bank { "bank": { @@ -23925,10 +23998,7 @@ exports[`BSO Creatables 1`] = ` }, { "cantHaveItems": undefined, - "inputItems": Bank { - "bank": {}, - "frozen": false, - }, + "inputItems": "function", "name": "Divine water (Dagannoth bones)", "outputItems": Bank { "bank": { @@ -23939,10 +24009,7 @@ exports[`BSO Creatables 1`] = ` }, { "cantHaveItems": undefined, - "inputItems": Bank { - "bank": {}, - "frozen": false, - }, + "inputItems": "function", "name": "Divine water (Superior dragon bones)", "outputItems": Bank { "bank": { @@ -23953,10 +24020,7 @@ exports[`BSO Creatables 1`] = ` }, { "cantHaveItems": undefined, - "inputItems": Bank { - "bank": {}, - "frozen": false, - }, + "inputItems": "function", "name": "Divine water (Abyssal dragon bones)", "outputItems": Bank { "bank": { @@ -23967,10 +24031,7 @@ exports[`BSO Creatables 1`] = ` }, { "cantHaveItems": undefined, - "inputItems": Bank { - "bank": {}, - "frozen": false, - }, + "inputItems": "function", "name": "Divine water (Frost dragon bones)", "outputItems": Bank { "bank": { @@ -23981,10 +24042,7 @@ exports[`BSO Creatables 1`] = ` }, { "cantHaveItems": undefined, - "inputItems": Bank { - "bank": {}, - "frozen": false, - }, + "inputItems": "function", "name": "Divine water (Royal dragon bones)", "outputItems": Bank { "bank": { @@ -24027,7 +24085,7 @@ exports[`BSO Creatables 1`] = ` "frozen": false, }, "requiredSlayerUnlocks": [ - 54, + 56, ], }, { @@ -24048,7 +24106,7 @@ exports[`BSO Creatables 1`] = ` "frozen": false, }, "requiredSlayerUnlocks": [ - 54, + 56, ], }, { @@ -24069,7 +24127,7 @@ exports[`BSO Creatables 1`] = ` "frozen": false, }, "requiredSlayerUnlocks": [ - 54, + 56, ], }, { @@ -24090,7 +24148,7 @@ exports[`BSO Creatables 1`] = ` "frozen": false, }, "requiredSlayerUnlocks": [ - 54, + 56, ], }, { @@ -24111,7 +24169,7 @@ exports[`BSO Creatables 1`] = ` "frozen": false, }, "requiredSlayerUnlocks": [ - 54, + 56, ], }, { @@ -24132,7 +24190,7 @@ exports[`BSO Creatables 1`] = ` "frozen": false, }, "requiredSlayerUnlocks": [ - 54, + 56, ], }, { @@ -24153,7 +24211,7 @@ exports[`BSO Creatables 1`] = ` "frozen": false, }, "requiredSlayerUnlocks": [ - 54, + 56, ], }, { @@ -24174,7 +24232,7 @@ exports[`BSO Creatables 1`] = ` "frozen": false, }, "requiredSlayerUnlocks": [ - 54, + 56, ], }, { @@ -24195,7 +24253,7 @@ exports[`BSO Creatables 1`] = ` "frozen": false, }, "requiredSlayerUnlocks": [ - 54, + 56, ], }, { @@ -24216,7 +24274,7 @@ exports[`BSO Creatables 1`] = ` "frozen": false, }, "requiredSlayerUnlocks": [ - 54, + 56, ], }, { @@ -24237,7 +24295,7 @@ exports[`BSO Creatables 1`] = ` "frozen": false, }, "requiredSlayerUnlocks": [ - 54, + 56, ], }, { @@ -24258,7 +24316,7 @@ exports[`BSO Creatables 1`] = ` "frozen": false, }, "requiredSlayerUnlocks": [ - 54, + 56, ], }, { @@ -24279,7 +24337,7 @@ exports[`BSO Creatables 1`] = ` "frozen": false, }, "requiredSlayerUnlocks": [ - 54, + 56, ], }, { @@ -24300,7 +24358,7 @@ exports[`BSO Creatables 1`] = ` "frozen": false, }, "requiredSlayerUnlocks": [ - 54, + 56, ], }, { @@ -24321,7 +24379,7 @@ exports[`BSO Creatables 1`] = ` "frozen": false, }, "requiredSlayerUnlocks": [ - 54, + 56, ], }, { @@ -24342,7 +24400,7 @@ exports[`BSO Creatables 1`] = ` "frozen": false, }, "requiredSlayerUnlocks": [ - 54, + 56, ], }, { @@ -24515,10 +24573,7 @@ exports[`BSO Creatables 1`] = ` }, "name": "Revert completionist cape", "noCl": true, - "outputItems": Bank { - "bank": {}, - "frozen": false, - }, + "outputItems": "function", }, { "cantHaveItems": undefined, @@ -26179,10 +26234,7 @@ exports[`BSO Creatables 1`] = ` }, "name": "Revert Support cape", "noCl": true, - "outputItems": Bank { - "bank": {}, - "frozen": false, - }, + "outputItems": "function", }, { "cantHaveItems": undefined, @@ -26194,10 +26246,7 @@ exports[`BSO Creatables 1`] = ` }, "name": "Revert Gatherer's cape", "noCl": true, - "outputItems": Bank { - "bank": {}, - "frozen": false, - }, + "outputItems": "function", }, { "cantHaveItems": undefined, @@ -26209,10 +26258,7 @@ exports[`BSO Creatables 1`] = ` }, "name": "Revert Combatant's cape", "noCl": true, - "outputItems": Bank { - "bank": {}, - "frozen": false, - }, + "outputItems": "function", }, { "cantHaveItems": undefined, @@ -26224,10 +26270,7 @@ exports[`BSO Creatables 1`] = ` }, "name": "Revert Artisan's cape", "noCl": true, - "outputItems": Bank { - "bank": {}, - "frozen": false, - }, + "outputItems": "function", }, { "cantHaveItems": undefined, @@ -30040,7 +30083,7 @@ exports[`BSO Creatables 1`] = ` }, "frozen": false, }, - "name": "Revert Elite void robe (or))", + "name": "Revert Elite void robe (or)", "noCl": true, "outputItems": Bank { "bank": { @@ -31310,7 +31353,7 @@ exports[`BSO Creatables 1`] = ` "cantHaveItems": undefined, "inputItems": Bank { "bank": { - "26807": 1, + "26809": 1, }, "frozen": false, }, @@ -31318,7 +31361,7 @@ exports[`BSO Creatables 1`] = ` "noCl": true, "outputItems": Bank { "bank": { - "26809": 1, + "26807": 1, }, "frozen": false, }, @@ -31327,7 +31370,7 @@ exports[`BSO Creatables 1`] = ` "cantHaveItems": undefined, "inputItems": Bank { "bank": { - "26807": 1, + "26811": 1, }, "frozen": false, }, @@ -31335,7 +31378,7 @@ exports[`BSO Creatables 1`] = ` "noCl": true, "outputItems": Bank { "bank": { - "26811": 1, + "26807": 1, }, "frozen": false, }, @@ -31344,7 +31387,7 @@ exports[`BSO Creatables 1`] = ` "cantHaveItems": undefined, "inputItems": Bank { "bank": { - "26809": 1, + "26807": 1, }, "frozen": false, }, @@ -31352,7 +31395,7 @@ exports[`BSO Creatables 1`] = ` "noCl": true, "outputItems": Bank { "bank": { - "26807": 1, + "26809": 1, }, "frozen": false, }, @@ -31361,7 +31404,7 @@ exports[`BSO Creatables 1`] = ` "cantHaveItems": undefined, "inputItems": Bank { "bank": { - "26809": 1, + "26811": 1, }, "frozen": false, }, @@ -31369,7 +31412,7 @@ exports[`BSO Creatables 1`] = ` "noCl": true, "outputItems": Bank { "bank": { - "26811": 1, + "26809": 1, }, "frozen": false, }, @@ -31378,7 +31421,7 @@ exports[`BSO Creatables 1`] = ` "cantHaveItems": undefined, "inputItems": Bank { "bank": { - "26811": 1, + "26807": 1, }, "frozen": false, }, @@ -31386,7 +31429,7 @@ exports[`BSO Creatables 1`] = ` "noCl": true, "outputItems": Bank { "bank": { - "26807": 1, + "26811": 1, }, "frozen": false, }, @@ -31395,7 +31438,7 @@ exports[`BSO Creatables 1`] = ` "cantHaveItems": undefined, "inputItems": Bank { "bank": { - "26811": 1, + "26809": 1, }, "frozen": false, }, @@ -31403,7 +31446,7 @@ exports[`BSO Creatables 1`] = ` "noCl": true, "outputItems": Bank { "bank": { - "26809": 1, + "26811": 1, }, "frozen": false, }, diff --git a/tests/unit/snapshots/chart.bar.kmb.json b/tests/unit/snapshots/chart.bar.kmb.json new file mode 100644 index 00000000000..313528fe146 --- /dev/null +++ b/tests/unit/snapshots/chart.bar.kmb.json @@ -0,0 +1,66 @@ +{ + "encoded": "{\"chart\":{\"type\":\"bar\"},\"title\":{\"text\":\"bar kmb title\"},\"series\":[{\"name\":\"bar kmb title\",\"data\":[{\"x\":\"Twisted bow\",\"y\":5000000000,\"fillColor\":\"#ffee65\"},{\"x\":\"Egg\",\"y\":1500000000,\"fillColor\":\"#87bc45\"},{\"x\":\"Cat\",\"y\":500000000,\"fillColor\":\"#bdcf32\"},{\"x\":\"Dog\",\"y\":2500000000,\"fillColor\":\"#27aeef\"},{\"x\":\"Trout\",\"y\":4500000000,\"fillColor\":\"#e60049\"}]}],\"xaxis\":{\"categories\":[\"Twisted bow\",\"Egg\",\"Cat\",\"Dog\",\"Trout\"]},\"dataLabels\":{\"enabled\":true,\"style\":{\"colors\":[\"#000\"]},\"formatter\":(v) => {\n if (v > 999999999 || v < -999999999) {\n return `${Math.round(v / 1e9)}b`;\n } else if (v > 999999 || v < -999999) {\n return `${Math.round(v / 1e6)}m`;\n } else if (v > 999 || v < -999) {\n return `${Math.round(v / 1e3)}k`;\n }\n return Math.round(v);\n }},\"yaxis\":{\"labels\":{\"formatter\":(v) => {\n if (v > 999999999 || v < -999999999) {\n return `${Math.round(v / 1e9)}b`;\n } else if (v > 999999 || v < -999999) {\n return `${Math.round(v / 1e6)}m`;\n } else if (v > 999 || v < -999) {\n return `${Math.round(v / 1e3)}k`;\n }\n return Math.round(v);\n }}},\"plotOptions\":{\"bar\":{\"dataLabels\":{\"position\":\"top\"}}}}", + "config": { + "chart": { + "type": "bar" + }, + "title": { + "text": "bar kmb title" + }, + "series": [ + { + "name": "bar kmb title", + "data": [ + { + "x": "Twisted bow", + "y": 5000000000, + "fillColor": "#ffee65" + }, + { + "x": "Egg", + "y": 1500000000, + "fillColor": "#87bc45" + }, + { + "x": "Cat", + "y": 500000000, + "fillColor": "#bdcf32" + }, + { + "x": "Dog", + "y": 2500000000, + "fillColor": "#27aeef" + }, + { + "x": "Trout", + "y": 4500000000, + "fillColor": "#e60049" + } + ] + } + ], + "xaxis": { + "categories": ["Twisted bow", "Egg", "Cat", "Dog", "Trout"] + }, + "dataLabels": { + "enabled": true, + "style": { + "colors": ["#000"] + }, + "formatter": "FORMATTER" + }, + "yaxis": { + "labels": { + "formatter": "FORMATTER" + } + }, + "plotOptions": { + "bar": { + "dataLabels": { + "position": "top" + } + } + } + }, + "url": "https://quickchart.io/apex-charts/render?config=%7B%22chart%22%3A%7B%22type%22%3A%22bar%22%7D%2C%22title%22%3A%7B%22text%22%3A%22bar%20kmb%20title%22%7D%2C%22series%22%3A%5B%7B%22name%22%3A%22bar%20kmb%20title%22%2C%22data%22%3A%5B%7B%22x%22%3A%22Twisted%20bow%22%2C%22y%22%3A5000000000%2C%22fillColor%22%3A%22%23ffee65%22%7D%2C%7B%22x%22%3A%22Egg%22%2C%22y%22%3A1500000000%2C%22fillColor%22%3A%22%2387bc45%22%7D%2C%7B%22x%22%3A%22Cat%22%2C%22y%22%3A500000000%2C%22fillColor%22%3A%22%23bdcf32%22%7D%2C%7B%22x%22%3A%22Dog%22%2C%22y%22%3A2500000000%2C%22fillColor%22%3A%22%2327aeef%22%7D%2C%7B%22x%22%3A%22Trout%22%2C%22y%22%3A4500000000%2C%22fillColor%22%3A%22%23e60049%22%7D%5D%7D%5D%2C%22xaxis%22%3A%7B%22categories%22%3A%5B%22Twisted%20bow%22%2C%22Egg%22%2C%22Cat%22%2C%22Dog%22%2C%22Trout%22%5D%7D%2C%22dataLabels%22%3A%7B%22enabled%22%3Atrue%2C%22style%22%3A%7B%22colors%22%3A%5B%22%23000%22%5D%7D%2C%22formatter%22%3A(v)%20%3D%3E%20%7B%0A%20%20%20%20%20%20if%20(v%20%3E%20999999999%20%7C%7C%20v%20%3C%20-999999999)%20%7B%0A%20%20%20%20%20%20%20%20return%20%60%24%7BMath.round(v%20%2F%201e9)%7Db%60%3B%0A%20%20%20%20%20%20%7D%20else%20if%20(v%20%3E%20999999%20%7C%7C%20v%20%3C%20-999999)%20%7B%0A%20%20%20%20%20%20%20%20return%20%60%24%7BMath.round(v%20%2F%201e6)%7Dm%60%3B%0A%20%20%20%20%20%20%7D%20else%20if%20(v%20%3E%20999%20%7C%7C%20v%20%3C%20-999)%20%7B%0A%20%20%20%20%20%20%20%20return%20%60%24%7BMath.round(v%20%2F%201e3)%7Dk%60%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20return%20Math.round(v)%3B%0A%20%20%20%20%7D%7D%2C%22yaxis%22%3A%7B%22labels%22%3A%7B%22formatter%22%3A(v)%20%3D%3E%20%7B%0A%20%20%20%20%20%20if%20(v%20%3E%20999999999%20%7C%7C%20v%20%3C%20-999999999)%20%7B%0A%20%20%20%20%20%20%20%20return%20%60%24%7BMath.round(v%20%2F%201e9)%7Db%60%3B%0A%20%20%20%20%20%20%7D%20else%20if%20(v%20%3E%20999999%20%7C%7C%20v%20%3C%20-999999)%20%7B%0A%20%20%20%20%20%20%20%20return%20%60%24%7BMath.round(v%20%2F%201e6)%7Dm%60%3B%0A%20%20%20%20%20%20%7D%20else%20if%20(v%20%3E%20999%20%7C%7C%20v%20%3C%20-999)%20%7B%0A%20%20%20%20%20%20%20%20return%20%60%24%7BMath.round(v%20%2F%201e3)%7Dk%60%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20return%20Math.round(v)%3B%0A%20%20%20%20%7D%7D%7D%2C%22plotOptions%22%3A%7B%22bar%22%3A%7B%22dataLabels%22%3A%7B%22position%22%3A%22top%22%7D%7D%7D%7D" +} diff --git a/tests/unit/snapshots/chart.bar.kmb.png b/tests/unit/snapshots/chart.bar.kmb.png new file mode 100644 index 00000000000..6308213755e Binary files /dev/null and b/tests/unit/snapshots/chart.bar.kmb.png differ diff --git a/tests/unit/snapshots/chart.bar.percent.json b/tests/unit/snapshots/chart.bar.percent.json new file mode 100644 index 00000000000..787b1b4cdbd --- /dev/null +++ b/tests/unit/snapshots/chart.bar.percent.json @@ -0,0 +1,53 @@ +{ + "encoded": "{\"chart\":{\"type\":\"bar\"},\"title\":{\"text\":\"bar percent title\"},\"series\":[{\"name\":\"bar percent title\",\"data\":[{\"x\":\"Magna\",\"y\":55,\"fillColor\":\"#7eb0d5\"},{\"x\":\"Cyr\",\"y\":45,\"fillColor\":\"#e60049\"}]}],\"xaxis\":{\"categories\":[\"Magna\",\"Cyr\"]},\"dataLabels\":{\"enabled\":true,\"style\":{\"colors\":[\"#000\"]},\"formatter\":(v) => `${v}%`},\"yaxis\":{\"labels\":{\"formatter\":(v) => `${v}%`},\"min\":0,\"max\":100},\"plotOptions\":{\"bar\":{\"dataLabels\":{\"position\":\"top\"}}}}", + "config": { + "chart": { + "type": "bar" + }, + "title": { + "text": "bar percent title" + }, + "series": [ + { + "name": "bar percent title", + "data": [ + { + "x": "Magna", + "y": 55, + "fillColor": "#7eb0d5" + }, + { + "x": "Cyr", + "y": 45, + "fillColor": "#e60049" + } + ] + } + ], + "xaxis": { + "categories": ["Magna", "Cyr"] + }, + "dataLabels": { + "enabled": true, + "style": { + "colors": ["#000"] + }, + "formatter": "FORMATTER" + }, + "yaxis": { + "labels": { + "formatter": "FORMATTER" + }, + "min": 0, + "max": 100 + }, + "plotOptions": { + "bar": { + "dataLabels": { + "position": "top" + } + } + } + }, + "url": "https://quickchart.io/apex-charts/render?config=%7B%22chart%22%3A%7B%22type%22%3A%22bar%22%7D%2C%22title%22%3A%7B%22text%22%3A%22bar%20percent%20title%22%7D%2C%22series%22%3A%5B%7B%22name%22%3A%22bar%20percent%20title%22%2C%22data%22%3A%5B%7B%22x%22%3A%22Magna%22%2C%22y%22%3A55%2C%22fillColor%22%3A%22%237eb0d5%22%7D%2C%7B%22x%22%3A%22Cyr%22%2C%22y%22%3A45%2C%22fillColor%22%3A%22%23e60049%22%7D%5D%7D%5D%2C%22xaxis%22%3A%7B%22categories%22%3A%5B%22Magna%22%2C%22Cyr%22%5D%7D%2C%22dataLabels%22%3A%7B%22enabled%22%3Atrue%2C%22style%22%3A%7B%22colors%22%3A%5B%22%23000%22%5D%7D%2C%22formatter%22%3A(v)%20%3D%3E%20%60%24%7Bv%7D%25%60%7D%2C%22yaxis%22%3A%7B%22labels%22%3A%7B%22formatter%22%3A(v)%20%3D%3E%20%60%24%7Bv%7D%25%60%7D%2C%22min%22%3A0%2C%22max%22%3A100%7D%2C%22plotOptions%22%3A%7B%22bar%22%3A%7B%22dataLabels%22%3A%7B%22position%22%3A%22top%22%7D%7D%7D%7D" +} diff --git a/tests/unit/snapshots/chart.bar.percent.png b/tests/unit/snapshots/chart.bar.percent.png new file mode 100644 index 00000000000..0b1c116e3d0 Binary files /dev/null and b/tests/unit/snapshots/chart.bar.percent.png differ diff --git a/tests/unit/snapshots/chart.line.kmb.json b/tests/unit/snapshots/chart.line.kmb.json new file mode 100644 index 00000000000..9cb0f223adb --- /dev/null +++ b/tests/unit/snapshots/chart.line.kmb.json @@ -0,0 +1,59 @@ +{ + "encoded": "{\"chart\":{\"type\":\"line\"},\"title\":{\"text\":\"line kmb title\"},\"series\":[{\"name\":\"line kmb title\",\"data\":[{\"x\":\"Twisted bow\",\"y\":5000000000,\"fillColor\":\"#ffee65\"},{\"x\":\"Egg\",\"y\":1500000000,\"fillColor\":\"#87bc45\"},{\"x\":\"Cat\",\"y\":500000000,\"fillColor\":\"#bdcf32\"},{\"x\":\"Dog\",\"y\":2500000000,\"fillColor\":\"#27aeef\"},{\"x\":\"Trout\",\"y\":4500000000,\"fillColor\":\"#e60049\"}]}],\"xaxis\":{\"categories\":[\"Twisted bow\",\"Egg\",\"Cat\",\"Dog\",\"Trout\"]},\"dataLabels\":{\"enabled\":true,\"style\":{\"colors\":[\"#000\"]},\"formatter\":(v) => {\n if (v > 999999999 || v < -999999999) {\n return `${Math.round(v / 1e9)}b`;\n } else if (v > 999999 || v < -999999) {\n return `${Math.round(v / 1e6)}m`;\n } else if (v > 999 || v < -999) {\n return `${Math.round(v / 1e3)}k`;\n }\n return Math.round(v);\n }},\"yaxis\":{\"labels\":{\"formatter\":(v) => {\n if (v > 999999999 || v < -999999999) {\n return `${Math.round(v / 1e9)}b`;\n } else if (v > 999999 || v < -999999) {\n return `${Math.round(v / 1e6)}m`;\n } else if (v > 999 || v < -999) {\n return `${Math.round(v / 1e3)}k`;\n }\n return Math.round(v);\n }}}}", + "config": { + "chart": { + "type": "line" + }, + "title": { + "text": "line kmb title" + }, + "series": [ + { + "name": "line kmb title", + "data": [ + { + "x": "Twisted bow", + "y": 5000000000, + "fillColor": "#ffee65" + }, + { + "x": "Egg", + "y": 1500000000, + "fillColor": "#87bc45" + }, + { + "x": "Cat", + "y": 500000000, + "fillColor": "#bdcf32" + }, + { + "x": "Dog", + "y": 2500000000, + "fillColor": "#27aeef" + }, + { + "x": "Trout", + "y": 4500000000, + "fillColor": "#e60049" + } + ] + } + ], + "xaxis": { + "categories": ["Twisted bow", "Egg", "Cat", "Dog", "Trout"] + }, + "dataLabels": { + "enabled": true, + "style": { + "colors": ["#000"] + }, + "formatter": "FORMATTER" + }, + "yaxis": { + "labels": { + "formatter": "FORMATTER" + } + } + }, + "url": "https://quickchart.io/apex-charts/render?config=%7B%22chart%22%3A%7B%22type%22%3A%22line%22%7D%2C%22title%22%3A%7B%22text%22%3A%22line%20kmb%20title%22%7D%2C%22series%22%3A%5B%7B%22name%22%3A%22line%20kmb%20title%22%2C%22data%22%3A%5B%7B%22x%22%3A%22Twisted%20bow%22%2C%22y%22%3A5000000000%2C%22fillColor%22%3A%22%23ffee65%22%7D%2C%7B%22x%22%3A%22Egg%22%2C%22y%22%3A1500000000%2C%22fillColor%22%3A%22%2387bc45%22%7D%2C%7B%22x%22%3A%22Cat%22%2C%22y%22%3A500000000%2C%22fillColor%22%3A%22%23bdcf32%22%7D%2C%7B%22x%22%3A%22Dog%22%2C%22y%22%3A2500000000%2C%22fillColor%22%3A%22%2327aeef%22%7D%2C%7B%22x%22%3A%22Trout%22%2C%22y%22%3A4500000000%2C%22fillColor%22%3A%22%23e60049%22%7D%5D%7D%5D%2C%22xaxis%22%3A%7B%22categories%22%3A%5B%22Twisted%20bow%22%2C%22Egg%22%2C%22Cat%22%2C%22Dog%22%2C%22Trout%22%5D%7D%2C%22dataLabels%22%3A%7B%22enabled%22%3Atrue%2C%22style%22%3A%7B%22colors%22%3A%5B%22%23000%22%5D%7D%2C%22formatter%22%3A(v)%20%3D%3E%20%7B%0A%20%20%20%20%20%20if%20(v%20%3E%20999999999%20%7C%7C%20v%20%3C%20-999999999)%20%7B%0A%20%20%20%20%20%20%20%20return%20%60%24%7BMath.round(v%20%2F%201e9)%7Db%60%3B%0A%20%20%20%20%20%20%7D%20else%20if%20(v%20%3E%20999999%20%7C%7C%20v%20%3C%20-999999)%20%7B%0A%20%20%20%20%20%20%20%20return%20%60%24%7BMath.round(v%20%2F%201e6)%7Dm%60%3B%0A%20%20%20%20%20%20%7D%20else%20if%20(v%20%3E%20999%20%7C%7C%20v%20%3C%20-999)%20%7B%0A%20%20%20%20%20%20%20%20return%20%60%24%7BMath.round(v%20%2F%201e3)%7Dk%60%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20return%20Math.round(v)%3B%0A%20%20%20%20%7D%7D%2C%22yaxis%22%3A%7B%22labels%22%3A%7B%22formatter%22%3A(v)%20%3D%3E%20%7B%0A%20%20%20%20%20%20if%20(v%20%3E%20999999999%20%7C%7C%20v%20%3C%20-999999999)%20%7B%0A%20%20%20%20%20%20%20%20return%20%60%24%7BMath.round(v%20%2F%201e9)%7Db%60%3B%0A%20%20%20%20%20%20%7D%20else%20if%20(v%20%3E%20999999%20%7C%7C%20v%20%3C%20-999999)%20%7B%0A%20%20%20%20%20%20%20%20return%20%60%24%7BMath.round(v%20%2F%201e6)%7Dm%60%3B%0A%20%20%20%20%20%20%7D%20else%20if%20(v%20%3E%20999%20%7C%7C%20v%20%3C%20-999)%20%7B%0A%20%20%20%20%20%20%20%20return%20%60%24%7BMath.round(v%20%2F%201e3)%7Dk%60%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20return%20Math.round(v)%3B%0A%20%20%20%20%7D%7D%7D%7D" +} diff --git a/tests/unit/snapshots/chart.line.kmb.png b/tests/unit/snapshots/chart.line.kmb.png new file mode 100644 index 00000000000..6a3670850fa Binary files /dev/null and b/tests/unit/snapshots/chart.line.kmb.png differ diff --git a/tests/unit/snapshots/chart.line.percent.json b/tests/unit/snapshots/chart.line.percent.json new file mode 100644 index 00000000000..04cdfe1e660 --- /dev/null +++ b/tests/unit/snapshots/chart.line.percent.json @@ -0,0 +1,46 @@ +{ + "encoded": "{\"chart\":{\"type\":\"line\"},\"title\":{\"text\":\"line percent title\"},\"series\":[{\"name\":\"line percent title\",\"data\":[{\"x\":\"Magna\",\"y\":55,\"fillColor\":\"#7eb0d5\"},{\"x\":\"Cyr\",\"y\":45,\"fillColor\":\"#e60049\"}]}],\"xaxis\":{\"categories\":[\"Magna\",\"Cyr\"]},\"dataLabels\":{\"enabled\":true,\"style\":{\"colors\":[\"#000\"]},\"formatter\":(v) => `${v}%`},\"yaxis\":{\"labels\":{\"formatter\":(v) => `${v}%`},\"min\":0,\"max\":100}}", + "config": { + "chart": { + "type": "line" + }, + "title": { + "text": "line percent title" + }, + "series": [ + { + "name": "line percent title", + "data": [ + { + "x": "Magna", + "y": 55, + "fillColor": "#7eb0d5" + }, + { + "x": "Cyr", + "y": 45, + "fillColor": "#e60049" + } + ] + } + ], + "xaxis": { + "categories": ["Magna", "Cyr"] + }, + "dataLabels": { + "enabled": true, + "style": { + "colors": ["#000"] + }, + "formatter": "FORMATTER" + }, + "yaxis": { + "labels": { + "formatter": "FORMATTER" + }, + "min": 0, + "max": 100 + } + }, + "url": "https://quickchart.io/apex-charts/render?config=%7B%22chart%22%3A%7B%22type%22%3A%22line%22%7D%2C%22title%22%3A%7B%22text%22%3A%22line%20percent%20title%22%7D%2C%22series%22%3A%5B%7B%22name%22%3A%22line%20percent%20title%22%2C%22data%22%3A%5B%7B%22x%22%3A%22Magna%22%2C%22y%22%3A55%2C%22fillColor%22%3A%22%237eb0d5%22%7D%2C%7B%22x%22%3A%22Cyr%22%2C%22y%22%3A45%2C%22fillColor%22%3A%22%23e60049%22%7D%5D%7D%5D%2C%22xaxis%22%3A%7B%22categories%22%3A%5B%22Magna%22%2C%22Cyr%22%5D%7D%2C%22dataLabels%22%3A%7B%22enabled%22%3Atrue%2C%22style%22%3A%7B%22colors%22%3A%5B%22%23000%22%5D%7D%2C%22formatter%22%3A(v)%20%3D%3E%20%60%24%7Bv%7D%25%60%7D%2C%22yaxis%22%3A%7B%22labels%22%3A%7B%22formatter%22%3A(v)%20%3D%3E%20%60%24%7Bv%7D%25%60%7D%2C%22min%22%3A0%2C%22max%22%3A100%7D%7D" +} diff --git a/tests/unit/snapshots/chart.line.percent.png b/tests/unit/snapshots/chart.line.percent.png new file mode 100644 index 00000000000..07472579485 Binary files /dev/null and b/tests/unit/snapshots/chart.line.percent.png differ diff --git a/tests/unit/snapshots/chatHead.BSO.png b/tests/unit/snapshots/chatHead.BSO.png new file mode 100644 index 00000000000..38567d0861b Binary files /dev/null and b/tests/unit/snapshots/chatHead.BSO.png differ diff --git a/tests/unit/snapshots/chatHead.OSB.png b/tests/unit/snapshots/chatHead.OSB.png new file mode 100644 index 00000000000..38567d0861b Binary files /dev/null and b/tests/unit/snapshots/chatHead.OSB.png differ diff --git a/tests/unit/snapshots/cl.BSO.png b/tests/unit/snapshots/cl.BSO.png new file mode 100644 index 00000000000..9ebfff022ae Binary files /dev/null and b/tests/unit/snapshots/cl.BSO.png differ diff --git a/tests/unit/snapshots/cl.OSB.png b/tests/unit/snapshots/cl.OSB.png new file mode 100644 index 00000000000..ba03eddb79f Binary files /dev/null and b/tests/unit/snapshots/cl.OSB.png differ diff --git a/tests/unit/snapshots/clsnapshots.test.ts.snap b/tests/unit/snapshots/clsnapshots.test.ts.snap index de5af7c1dce..20eee08aa2a 100644 --- a/tests/unit/snapshots/clsnapshots.test.ts.snap +++ b/tests/unit/snapshots/clsnapshots.test.ts.snap @@ -6,14 +6,15 @@ Achievement Diary (48) Aerial Fishing (9) Akumu (4) Alchemical Hydra (11) -All Pets (57) -All Pets (57) +All Pets (58) +All Pets (58) Balthazar's Big Bonanza (26) Barbarian Assault (11) Barrows Chests (25) Baxtorian Bathhouses (4) Beginner Treasure Trails (16) Birthday crate (s2) (20) +Birthday crate (s6) (26) Brimhaven Agility Arena (8) Bryophyta (1) BSO Birthday 2022 (4) @@ -40,7 +41,7 @@ Cooking (28) Corporeal Beast (8) Crafting (175) Crazy archaeologist (3) -Creatables (750) +Creatables (754) Creature Creation (7) Custom Pets (47) Custom Pets (Discontinued) (20) @@ -70,6 +71,7 @@ Fishing Trawler (4) Fist of Guthix (6) Fletching (145) Forestry (23) +Fortis Colosseum (9) Fossil Island Notes (10) General Graardor (8) Giant Mole (3) @@ -98,7 +100,7 @@ Implings (16) Invention (22) K'ril Tsutsaroth (8) Kalphite King (8) -Kalphite Queen (5) +Kalphite Queen (6) Kibble (3) King Black Dragon (4) King Goldemar (4) @@ -145,7 +147,7 @@ Shooting Stars (2) Skilling Misc (41) Skilling Pets (8) Skotizo (6) -Slayer (80) +Slayer (83) Slayer Masks/Helms (32) Smithing (200) Solis (3) @@ -758,6 +760,9 @@ Cyclops head Daemonheim agility pass Dagannoth mask Dagannoth slayer helm +Dagon'hai hat +Dagon'hai robe bottom +Dagon'hai robe top Dark acorn Dark beast mask Dark beast slayer helm @@ -812,6 +817,7 @@ Diviner's handwear Diviner's headwear Diviner's legwear Diviner's robe +Dizana's quiver (uncharged) Doopy Double ammo mould Doug @@ -889,6 +895,7 @@ Dwarven toolkit Eagle egg Earth warrior champion scroll Echo +Echo crystal Ectoplasmator Ecumenical key Elder chaos hood @@ -1921,6 +1928,7 @@ Smiths trousers Smiths tunic Smoke battlestaff Smoke quartz +Smol heredit Smolcano Smouldering stone Soaked page @@ -1974,6 +1982,10 @@ Studded chaps (g) Studded chaps (t) Sturdy beehive parts Sun-metal scraps +Sunfire fanatic chausses +Sunfire fanatic cuirass +Sunfire fanatic helm +Sunfire splinters Superior bonecrusher Superior dwarf multicannon Superior inferno adze @@ -2010,6 +2022,7 @@ Toktz-xil-ul Tombshroom spore Tome of fire Tome of water (empty) +Tonalztics of ralos (uncharged) Top hat Top of sceptre Torag's hammers diff --git a/tests/unit/snapshots/cox.BSO.png b/tests/unit/snapshots/cox.BSO.png new file mode 100644 index 00000000000..30538f5bb01 Binary files /dev/null and b/tests/unit/snapshots/cox.BSO.png differ diff --git a/tests/unit/snapshots/cox.OSB.png b/tests/unit/snapshots/cox.OSB.png new file mode 100644 index 00000000000..c3821555547 Binary files /dev/null and b/tests/unit/snapshots/cox.OSB.png differ diff --git a/tests/unit/snapshots/images-test-ts-tests-unit-images-test-ts-images-chart-image-1-snap.png b/tests/unit/snapshots/images-test-ts-tests-unit-images-test-ts-images-chart-image-1-snap.png deleted file mode 100644 index 10bfb5ae32f..00000000000 Binary files a/tests/unit/snapshots/images-test-ts-tests-unit-images-test-ts-images-chart-image-1-snap.png and /dev/null differ diff --git a/tests/unit/snapshots/images-test-ts-tests-unit-images-test-ts-images-chat-heads-1-snap.png b/tests/unit/snapshots/images-test-ts-tests-unit-images-test-ts-images-chat-heads-1-snap.png deleted file mode 100644 index 36fdf927b56..00000000000 Binary files a/tests/unit/snapshots/images-test-ts-tests-unit-images-test-ts-images-chat-heads-1-snap.png and /dev/null differ diff --git a/tests/unit/snapshots/images-test-ts-tests-unit-images-test-ts-images-poh-image-1-snap.png b/tests/unit/snapshots/images-test-ts-tests-unit-images-test-ts-images-poh-image-1-snap.png deleted file mode 100644 index 296a7f90731..00000000000 Binary files a/tests/unit/snapshots/images-test-ts-tests-unit-images-test-ts-images-poh-image-1-snap.png and /dev/null differ diff --git a/tests/unit/snapshots/itemSwitches.BSO.json b/tests/unit/snapshots/itemSwitches.BSO.json new file mode 100644 index 00000000000..5ea6ab447e6 --- /dev/null +++ b/tests/unit/snapshots/itemSwitches.BSO.json @@ -0,0 +1,18 @@ +[ + { + "from": "Bellator ring [25488]", + "to": "Bellator ring [28316]" + }, + { + "from": "Magus ring [25486]", + "to": "Magus ring [28313]" + }, + { + "from": "Venator ring [25487]", + "to": "Venator ring [28310]" + }, + { + "from": "Ultor ring [25485]", + "to": "Ultor ring [28307]" + } +] diff --git a/tests/unit/snapshots/itemSwitches.OSB.json b/tests/unit/snapshots/itemSwitches.OSB.json new file mode 100644 index 00000000000..5ea6ab447e6 --- /dev/null +++ b/tests/unit/snapshots/itemSwitches.OSB.json @@ -0,0 +1,18 @@ +[ + { + "from": "Bellator ring [25488]", + "to": "Bellator ring [28316]" + }, + { + "from": "Magus ring [25486]", + "to": "Magus ring [28313]" + }, + { + "from": "Venator ring [25487]", + "to": "Venator ring [28310]" + }, + { + "from": "Ultor ring [25485]", + "to": "Ultor ring [28307]" + } +] diff --git a/tests/unit/snapshots/poh.BSO.png b/tests/unit/snapshots/poh.BSO.png new file mode 100644 index 00000000000..60a8405ec03 Binary files /dev/null and b/tests/unit/snapshots/poh.BSO.png differ diff --git a/tests/unit/snapshots/poh.OSB.png b/tests/unit/snapshots/poh.OSB.png new file mode 100644 index 00000000000..60a8405ec03 Binary files /dev/null and b/tests/unit/snapshots/poh.OSB.png differ diff --git a/tests/unit/snapshots/slayerUnlocks.snapshot.json b/tests/unit/snapshots/slayerUnlocks.snapshot.json new file mode 100644 index 00000000000..5c9836de704 --- /dev/null +++ b/tests/unit/snapshots/slayerUnlocks.snapshot.json @@ -0,0 +1,57 @@ +{ + "MalevolentMasquerade": 2, + "RingBling": 3, + "SeeingRed": 4, + "IHopeYouMithMe": 5, + "WatchTheBirdie": 6, + "HotStuff": 7, + "ReptileGotRipped": 8, + "LikeABoss": 9, + "BiggerAndBadder": 10, + "KingBlackBonnet": 11, + "KalphiteKhat": 12, + "UnholyHelmet": 13, + "DarkMantle": 14, + "UndeadHead": 15, + "UseMoreHead": 16, + "TwistedVision": 17, + "StopTheWyvern": 18, + "Basilocked": 19, + "ActualVampyreSlayer": 20, + "NeedMoreDarkness": 22, + "AnkouVeryMuch": 23, + "SuqANotherOne": 24, + "FireAndDarkness": 25, + "PedalToTheMetals": 26, + "IReallyMithYou": 27, + "AdamindSomeMore": 28, + "RUUUUUNE": 29, + "SpiritualFervour": 30, + "BirdsOfAFeather": 31, + "GreaterChallenge": 32, + "ItsDarkInHere": 33, + "BleedMeDry": 34, + "SmellYaLater": 35, + "Horrorific": 36, + "ToDustYouShallReturn": 37, + "WyverNotherOne": 38, + "GetSmashed": 39, + "NechsPlease": 40, + "AugmentMyAbbies": 41, + "KrackOn": 42, + "GetScabarightOnIt": 43, + "WyverNotherTwo": 44, + "Basilonger": 45, + "MoreAtStake": 46, + "SlayerRing": 48, + "HerbSack": 49, + "RunePouch": 50, + "DoubleTrouble": 51, + "BroaderFletching": 52, + "SizeMatters": 53, + "BlockAndRoll": 54, + "PoreDecisions": 55, + "Maskuerade": 56, + "IWildyMoreSlayer": 200, + "Revenenenenenants": 201 +} diff --git a/tests/unit/snapshots/toa.BSO.png b/tests/unit/snapshots/toa.BSO.png new file mode 100644 index 00000000000..7d230d6cee0 Binary files /dev/null and b/tests/unit/snapshots/toa.BSO.png differ diff --git a/tests/unit/snapshots/toa.OSB.png b/tests/unit/snapshots/toa.OSB.png new file mode 100644 index 00000000000..7029b55eb1a Binary files /dev/null and b/tests/unit/snapshots/toa.OSB.png differ diff --git a/tests/unit/trophies.bso.test.ts b/tests/unit/trophies.bso.test.ts index 3a065237804..01733a609c5 100644 --- a/tests/unit/trophies.bso.test.ts +++ b/tests/unit/trophies.bso.test.ts @@ -1,10 +1,10 @@ import { expect, test } from 'vitest'; import { allMbTables } from '../../src/lib/bsoOpenables'; -import { allTrophyItems } from '../../src/lib/data/trophies'; +import { allTrophyItems } from '../../src/lib/data/itemAliases'; import getOSItem from '../../src/lib/util/getOSItem'; import itemIsTradeable from '../../src/lib/util/itemIsTradeable'; -import { resolveOSItems } from '../../src/lib/util/resolveItems'; +import resolveItems from '../../src/lib/util/resolveItems'; test('trophies', async () => { expect(getOSItem('BSO dragon trophy')).toMatchObject({ id: 24_372 }); @@ -16,7 +16,7 @@ test('trophies', async () => { expect(getOSItem('Placeholder dragon trophy')).toMatchObject({ id: 26_515 }); expect(getOSItem('Placeholder bronze trophy')).toMatchObject({ id: 26_503 }); - for (const trophy of resolveOSItems(allTrophyItems)) { + for (const trophy of resolveItems(allTrophyItems).map(getOSItem)) { expect(itemIsTradeable(trophy.id)).toEqual(false); expect(trophy.customItemData?.isSuperUntradeable).toEqual(true); expect(allMbTables.includes(trophy.id)).toEqual(false); diff --git a/tests/unit/trophies.test.ts b/tests/unit/trophies.test.ts index cdab8e36c19..53b5bad0192 100644 --- a/tests/unit/trophies.test.ts +++ b/tests/unit/trophies.test.ts @@ -1,6 +1,6 @@ import { expect, test } from 'vitest'; -import { allTrophyItems } from '../../src/lib/data/trophies'; +import { allTrophyItems } from '../../src/lib/data/itemAliases'; import getOSItem from '../../src/lib/util/getOSItem'; import itemIsTradeable from '../../src/lib/util/itemIsTradeable'; diff --git a/tests/unit/tsconfig.json b/tests/unit/tsconfig.json index 1ed63529c0f..47325df54ae 100644 --- a/tests/unit/tsconfig.json +++ b/tests/unit/tsconfig.json @@ -1,5 +1,8 @@ { "extends": "../../tsconfig.base.json", "include": [".", "../../src/**/*"], + "compilerOptions": { + "noEmit": true + }, "references": [{ "path": "../../src" }] } diff --git a/tests/unit/util.getUserBestGearFromBank.test.ts b/tests/unit/util.getUserBestGearFromBank.test.ts index 0c1b86cb755..03a1cf254e7 100644 --- a/tests/unit/util.getUserBestGearFromBank.test.ts +++ b/tests/unit/util.getUserBestGearFromBank.test.ts @@ -2,10 +2,10 @@ import { Bank } from 'oldschooljs'; import { convertLVLtoXP } from 'oldschooljs/dist/util'; import { describe, expect, test } from 'vitest'; -import { GearSetup, GearStat } from '../../src/lib/gear/types'; +import { type GearSetup, GearStat } from '../../src/lib/gear/types'; import getUserBestGearFromBank from '../../src/lib/minions/functions/getUserBestGearFromBank'; import { Gear } from '../../src/lib/structures/Gear'; -import { Skills } from '../../src/lib/types'; +import type { Skills } from '../../src/lib/types'; import itemID from '../../src/lib/util/itemID'; const userBank = new Bank({ diff --git a/tests/unit/util.test.ts b/tests/unit/util.test.ts index b63ac1cdec5..c65c4c3acbf 100644 --- a/tests/unit/util.test.ts +++ b/tests/unit/util.test.ts @@ -30,7 +30,7 @@ describe('util', () => { expect(getOSItem('20997').id).toEqual(20_997); expect(getOSItem('3rd age platebody').id).toEqual(10_348); - expect(() => getOSItem('Non-existant item')).toThrowError("Non-existant item doesn't exist."); + expect(() => getOSItem('Non-existant item')).toThrowError('Item Non-existant item not found.'); }); test('getUserFoodFromBank', () => { @@ -38,7 +38,7 @@ describe('util', () => { ({ bank: b, skillLevel: () => 99 - } as any as MUser); + }) as any as MUser; expect( getUserFoodFromBank({ user: fakeUser(new Bank().add('Shark')), totalHealingNeeded: 500, favoriteFood: [] }) ).toStrictEqual(false); @@ -80,7 +80,7 @@ describe('util', () => { }); test('sanitizeBank', () => { - let buggyBank = new Bank(); + const buggyBank = new Bank(); buggyBank.bank[1] = -1; buggyBank.bank[2] = 0; sanitizeBank(buggyBank); @@ -94,7 +94,7 @@ describe('util', () => { test('sellPriceOfItem', () => { const item = getOSItem('Dragon pickaxe'); const { price } = item; - let expected = reduceNumByPercent(price, 25); + const expected = reduceNumByPercent(price, 25); expect(sellPriceOfItem(item)).toEqual({ price: expected, basePrice: price }); expect(sellPriceOfItem(getOSItem('Yellow square'))).toEqual({ price: 0, basePrice: 0 }); @@ -108,9 +108,9 @@ describe('util', () => { const item = getOSItem('Dragon pickaxe'); const { cost } = item; - let expectedOneQty = + const expectedOneQty = (((0.4 - 0.015 * Math.min(1 - 1, 10)) * Math.min(1, 11) + Math.max(1 - 11, 0) * 0.1) * cost) / 1; - let expectedTwentytwoQty = + const expectedTwentytwoQty = (((0.4 - 0.015 * Math.min(22 - 1, 10)) * Math.min(22, 11) + Math.max(22 - 11, 0) * 0.1) * cost) / 22; expect(sellStorePriceOfItem(item, 1)).toEqual({ price: expectedOneQty, basePrice: cost }); expect(sellStorePriceOfItem(item, 22)).toEqual({ price: expectedTwentytwoQty, basePrice: cost }); @@ -121,9 +121,9 @@ describe('util', () => { const item = getOSItem('Dragon pickaxe'); const { cost } = item; - let expectedOneQty = + const expectedOneQty = (((0.4 - 0.015 * Math.min(1 - 1, 10)) * Math.min(1, 11) + Math.max(1 - 11, 0) * 0.1) * cost) / 1; - let expectedTwentytwoQty = + const expectedTwentytwoQty = (((0.4 - 0.015 * Math.min(22 - 1, 10)) * Math.min(22, 11) + Math.max(22 - 11, 0) * 0.1) * cost) / 22; expect(sellStorePriceOfItem(item, 1)).toEqual({ price: expectedOneQty, basePrice: cost }); expect(sellStorePriceOfItem(item, 22)).toEqual({ price: expectedTwentytwoQty, basePrice: cost }); @@ -139,7 +139,7 @@ describe('util', () => { test('skillingPetRateFunction', () => { let testUser = mockMUser({ skills_agility: convertLVLtoXP(30) - }); + }) as any as MUser; const baseDropRate = 300_000; // Lvl 30 const dropRateLvl30 = Math.floor((baseDropRate - 30 * 25) / 1); @@ -147,7 +147,7 @@ describe('util', () => { // Lvl 99 testUser = mockMUser({ skills_agility: convertLVLtoXP(99) - }); + }) as any as MUser; const dropRateLvl99 = Math.floor((baseDropRate - 99 * 25) / 1); expect(skillingPetDropRate(testUser, SkillsEnum.Agility, baseDropRate).petDropRate).toEqual(dropRateLvl99); // Lvl 120 (BSO) and 5B xp diff --git a/tests/unit/utils.ts b/tests/unit/utils.ts index 223b4e51b48..94ae6cdbb16 100644 --- a/tests/unit/utils.ts +++ b/tests/unit/utils.ts @@ -1,16 +1,26 @@ -import { Prisma, User } from '@prisma/client'; -import { CommandResponse } from 'mahoji/dist/lib/structures/ICommand'; +import type { CommandResponse } from '@oldschoolgg/toolkit'; +import type { Prisma, User } from '@prisma/client'; import murmurhash from 'murmurhash'; import { Bank } from 'oldschooljs'; import { convertLVLtoXP } from 'oldschooljs/dist/util'; import { expect } from 'vitest'; -import { BitField } from '../../src/lib/constants'; -import type { GearSetup } from '../../src/lib/gear/types'; import { MUserClass } from '../../src/lib/MUser'; -import { filterGearSetup, Gear, PartialGearSetup } from '../../src/lib/structures/Gear'; +import type { BitField } from '../../src/lib/constants'; +import type { GearSetup } from '../../src/lib/gear/types'; +import type { PartialGearSetup } from '../../src/lib/structures/Gear'; +import { Gear, constructGearSetup } from '../../src/lib/structures/Gear'; import type { OSBMahojiCommand } from '../../src/mahoji/lib/util'; +function filterGearSetup(gear: undefined | null | GearSetup | PartialGearSetup): GearSetup | undefined { + const filteredGear = !gear + ? undefined + : typeof gear.ammo === 'undefined' || typeof gear.ammo === 'string' + ? constructGearSetup(gear as PartialGearSetup) + : (gear as GearSetup); + return filteredGear; +} + interface MockUserArgs { bank?: Bank; cl?: Bank; @@ -26,16 +36,14 @@ interface MockUserArgs { skills_prayer?: number; skills_fishing?: number; GP?: number; - premium_balance_tier?: number; - premium_balance_expiry_date?: number; bitfield?: BitField[]; id?: string; } -export const mockUser = (overrides?: MockUserArgs): User => { +const mockUser = (overrides?: MockUserArgs): User => { const gearMelee = filterGearSetup(overrides?.meleeGear); const cl = new Bank().add(overrides?.cl ?? {}); - return { + const r = { cl, gear_fashion: new Gear().raw() as Prisma.JsonValue, gear_mage: new Gear().raw() as Prisma.JsonValue, @@ -72,35 +80,23 @@ export const mockUser = (overrides?: MockUserArgs): User => { skills_dungeoneering: 0, skills_invention: 0, skills_hitpoints: overrides?.skills_hitpoints ?? convertLVLtoXP(10), - GP: overrides?.GP, - premium_balance_tier: overrides?.premium_balance_tier, - premium_balance_expiry_date: overrides?.premium_balance_expiry_date, - ironman_alts: [], + GP: overrides?.GP ?? 0, bitfield: overrides?.bitfield ?? [], username: 'Magnaboy', QP: overrides?.QP ?? 0, sacrificedValue: 0, id: overrides?.id ?? '', disabled_inventions: [], + skills_divination: 0, monsterScores: {}, - skills_divination: 0 + badges: [] } as unknown as User; -}; -class TestMUser extends MUserClass { - // @ts-expect-error Mock - public readonly rawUsername = 'test'; - - // @ts-expect-error Mock - async fetchStats() { - return { - monster_scores: {} - }; - } -} + return r; +}; export const mockMUser = (overrides?: MockUserArgs) => { - const user = new TestMUser(mockUser(overrides)); + const user = new MUserClass(mockUser(overrides)); return user; }; @@ -122,7 +118,10 @@ export async function testRunCmd({ Math.random = () => 0.5; const hash = murmurhash(JSON.stringify({ name: cmd.name, opts, user })).toString(); const mockedUser = mockMUser({ id: hash, ...user }); - mockUserMap.set(hash, mockedUser); + if (mockedUser.GP === null || Number.isNaN(mockedUser.GP) || mockedUser.GP < 0 || mockedUser.GP === undefined) { + throw new Error(`Invalid GP for user ${hash}`); + } + mockUserMap.set(hash, mockedUser as any as MUser); const options: any = { user: mockedUser.user, channelID: '1234', diff --git a/tsconfig.base.json b/tsconfig.base.json index 15aca11e526..d701f80b926 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1,10 +1,28 @@ { - "extends": "@oldschoolgg/ts-config", + "compileOnSave": true, "compilerOptions": { - "allowJs": true, - "checkJs": false, + "allowSyntheticDefaultImports": true, + "alwaysStrict": true, + "declaration": true, + "declarationMap": true, + "incremental": true, + "lib": ["ES2023"], + "module": "node16", + "moduleResolution": "node16", + "newLine": "lf", + "noFallthroughCasesInSwitch": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "pretty": true, + "removeComments": false, + "resolveJsonModule": true, + "sourceMap": true, + "strict": true, + "target": "ES2022", + "useDefineForClassFields": true, "skipLibCheck": true, "types": ["node", "node-fetch", "discord.js"], - "esModuleInterop": true + "esModuleInterop": true, + "skipDefaultLibCheck": true } } diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json deleted file mode 100644 index 0e75561af7e..00000000000 --- a/tsconfig.eslint.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "./tsconfig.base.json", - "compilerOptions": { - "allowJs": true, - "checkJs": true - }, - "include": ["src", "tests", "*.ts", "*.mts"] -} diff --git a/vitest.integration.config.mts b/vitest.integration.config.mts index 6b77ff01405..91dc5c1bc96 100644 --- a/vitest.integration.config.mts +++ b/vitest.integration.config.mts @@ -1,20 +1,23 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ - test: { - name: 'Old School Bot - Integration', - include: ['tests/integration/**/*.test.ts'], - setupFiles: 'tests/integration/setup.ts', - coverage: { - provider: 'v8', - reporter: 'text', - include: ['src/lib/MUser.ts'] - }, - testTimeout: 30_000, - bail: 1, - maxConcurrency: 1, - maxWorkers: 1, - minWorkers: 1, - pool: 'forks' - } + test: { + name: 'Old School Bot - Integration', + include: ['tests/integration/**/*.test.ts'], + setupFiles: 'tests/integration/setup.ts', + coverage: { + provider: 'v8', + reporter: 'text' + }, + testTimeout: 30_000, + bail: 1, + pool: 'forks', + maxConcurrency: 5, + poolOptions: { + forks: { + maxForks: 10, + minForks: 10 + } + } + } }); diff --git a/vitest.unit.config.mts b/vitest.unit.config.mts index 4d8e0dce89d..76d7b34b420 100644 --- a/vitest.unit.config.mts +++ b/vitest.unit.config.mts @@ -3,16 +3,25 @@ import { basename, dirname, join } from 'node:path'; import { defineConfig } from 'vitest/config'; export default defineConfig({ - test: { - name: 'Old School Bot - Unit', - include: ['tests/unit/**/*.test.ts'], - coverage: { - provider: 'v8', - reporter: 'html', - include: ['src/lib/structures/Gear.ts', 'src/lib/util/parseStringBank.ts', 'src/lib/util/equipMulti.ts'] - }, - setupFiles: 'tests/unit/setup.ts', - resolveSnapshotPath: (testPath, extension) => - join(join(dirname(testPath), 'snapshots'), `${basename(testPath)}${extension}`) - } + test: { + name: 'Old School Bot - Unit', + include: ['tests/unit/**/*.test.ts'], + coverage: { + provider: 'v8', + reporter: 'text', + include: ['src/lib/structures/Gear.ts', 'src/lib/util/parseStringBank.ts', 'src/lib/util/equipMulti.ts'] + }, + setupFiles: 'tests/unit/setup.ts', + resolveSnapshotPath: (testPath, extension) => + join(join(dirname(testPath), 'snapshots'), `${basename(testPath)}${extension}`), + slowTestThreshold: 0, + isolate: false, + poolOptions: { + threads: { + minThreads: 10, + maxThreads: 20, + singleThread: true + } + } + } }); diff --git a/yarn.lock b/yarn.lock index cea7ae1625a..5dfb8015f6a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1,5633 +1,4974 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@ampproject/remapping@^2.2.1": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" - integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== - dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" - -"@babel/code-frame@^7.0.0": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb" - integrity sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw== - dependencies: - "@babel/highlight" "^7.14.5" - -"@babel/code-frame@^7.12.13": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.5.tgz#234d98e1551960604f1246e6475891a570ad5658" - integrity sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ== - dependencies: - "@babel/highlight" "^7.22.5" - -"@babel/helper-string-parser@^7.23.4": - version "7.23.4" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83" - integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ== - -"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" - integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== - -"@babel/helper-validator-identifier@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" - integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== - -"@babel/helper-validator-identifier@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193" - integrity sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ== - -"@babel/highlight@^7.14.5": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" - integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== - dependencies: - "@babel/helper-validator-identifier" "^7.18.6" - chalk "^2.0.0" - js-tokens "^4.0.0" - -"@babel/highlight@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.5.tgz#aa6c05c5407a67ebce408162b7ede789b4d22031" - integrity sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw== - dependencies: - "@babel/helper-validator-identifier" "^7.22.5" - chalk "^2.0.0" - js-tokens "^4.0.0" - -"@babel/parser@^7.21.8": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.5.tgz#4a4d5ab4315579e5398a82dcf636ca80c3392790" - integrity sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg== - -"@babel/parser@^7.23.6": - version "7.23.9" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.9.tgz#7b903b6149b0f8fa7ad564af646c4c38a77fc44b" - integrity sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA== - -"@babel/runtime@^7.21.0": - version "7.23.9" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.9.tgz#47791a15e4603bb5f905bc0753801cf21d6345f7" - integrity sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw== - dependencies: - regenerator-runtime "^0.14.0" - -"@babel/runtime@^7.24.4": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.5.tgz#230946857c053a36ccc66e1dd03b17dd0c4ed02c" - integrity sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g== - dependencies: - regenerator-runtime "^0.14.0" - -"@babel/types@^7.23.6": - version "7.23.9" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.9.tgz#1dd7b59a9a2b5c87f8b41e52770b5ecbf492e002" - integrity sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q== - dependencies: - "@babel/helper-string-parser" "^7.23.4" - "@babel/helper-validator-identifier" "^7.22.20" - to-fast-properties "^2.0.0" - -"@bcoe/v8-coverage@^0.2.3": - version "0.2.3" - resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" - integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== - -"@dependents/detective-less@^4.1.0": - version "4.1.0" - resolved "https://registry.yarnpkg.com/@dependents/detective-less/-/detective-less-4.1.0.tgz#4a979ee7a6a79eb33602862d6a1263e30f98002e" - integrity sha512-KrkT6qO5NxqNfy68sBl6CTSoJ4SNDIS5iQArkibhlbGU4LaDukZ3q2HIkh8aUKDio6o4itU4xDR7t82Y2eP1Bg== - dependencies: - gonzales-pe "^4.3.0" - node-source-walk "^6.0.1" - -"@discordjs/builders@^1.7.0": - version "1.7.0" - resolved "https://registry.yarnpkg.com/@discordjs/builders/-/builders-1.7.0.tgz#e2478c7e55b0f4c40837edb8f102bce977323a37" - integrity sha512-GDtbKMkg433cOZur8Dv6c25EHxduNIBsxeHrsRoIM8+AwmEZ8r0tEpckx/sHwTLwQPOF3e2JWloZh9ofCaMfAw== - dependencies: - "@discordjs/formatters" "^0.3.3" - "@discordjs/util" "^1.0.2" - "@sapphire/shapeshift" "^3.9.3" - discord-api-types "0.37.61" - fast-deep-equal "^3.1.3" - ts-mixer "^6.0.3" - tslib "^2.6.2" - -"@discordjs/collection@1.5.3": - version "1.5.3" - resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-1.5.3.tgz#5a1250159ebfff9efa4f963cfa7e97f1b291be18" - integrity sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ== - -"@discordjs/collection@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-2.0.0.tgz#409b80c74eb8486cc4ee6a9b83426aaff1380f8c" - integrity sha512-YTWIXLrf5FsrLMycpMM9Q6vnZoR/lN2AWX23/Cuo8uOOtS8eHB2dyQaaGnaF8aZPYnttf2bkLMcXn/j6JUOi3w== - -"@discordjs/formatters@^0.3.3": - version "0.3.3" - resolved "https://registry.yarnpkg.com/@discordjs/formatters/-/formatters-0.3.3.tgz#b16fdd79bb819680ab7e519193004e9dc124a749" - integrity sha512-wTcI1Q5cps1eSGhl6+6AzzZkBBlVrBdc9IUhJbijRgVjCNIIIZPgqnUj3ntFODsHrdbGU8BEG9XmDQmgEEYn3w== - dependencies: - discord-api-types "0.37.61" - -"@discordjs/rest@^2.1.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@discordjs/rest/-/rest-2.2.0.tgz#f4ec00d3faff965c00a879b7e87bb4b6f4446966" - integrity sha512-nXm9wT8oqrYFRMEqTXQx9DUTeEtXUDMmnUKIhZn6O2EeDY9VCdwj23XCPq7fkqMPKdF7ldAfeVKyxxFdbZl59A== - dependencies: - "@discordjs/collection" "^2.0.0" - "@discordjs/util" "^1.0.2" - "@sapphire/async-queue" "^1.5.0" - "@sapphire/snowflake" "^3.5.1" - "@vladfrangu/async_event_emitter" "^2.2.2" - discord-api-types "0.37.61" - magic-bytes.js "^1.5.0" - tslib "^2.6.2" - undici "5.27.2" - -"@discordjs/util@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@discordjs/util/-/util-1.0.2.tgz#dc1896d764452b1bd9707eb9aa99ccfbb30bd1c0" - integrity sha512-IRNbimrmfb75GMNEjyznqM1tkI7HrZOf14njX7tCAAUetyZM1Pr8hX/EK2lxBCOgWDRmigbp24fD1hdMfQK5lw== - -"@discordjs/ws@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@discordjs/ws/-/ws-1.0.2.tgz#3933b12d4686aabf6a95dfe5fb6e744342a661d1" - integrity sha512-+XI82Rm2hKnFwAySXEep4A7Kfoowt6weO6381jgW+wVdTpMS/56qCvoXyFRY0slcv7c/U8My2PwIB2/wEaAh7Q== - dependencies: - "@discordjs/collection" "^2.0.0" - "@discordjs/rest" "^2.1.0" - "@discordjs/util" "^1.0.2" - "@sapphire/async-queue" "^1.5.0" - "@types/ws" "^8.5.9" - "@vladfrangu/async_event_emitter" "^2.2.2" - discord-api-types "0.37.61" - tslib "^2.6.2" - ws "^8.14.2" - -"@esbuild/aix-ppc64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537" - integrity sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g== - -"@esbuild/android-arm64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz#db1c9202a5bc92ea04c7b6840f1bbe09ebf9e6b9" - integrity sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg== - -"@esbuild/android-arm@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.20.2.tgz#3b488c49aee9d491c2c8f98a909b785870d6e995" - integrity sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w== - -"@esbuild/android-x64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.20.2.tgz#3b1628029e5576249d2b2d766696e50768449f98" - integrity sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg== - -"@esbuild/darwin-arm64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz#6e8517a045ddd86ae30c6608c8475ebc0c4000bb" - integrity sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA== - -"@esbuild/darwin-x64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz#90ed098e1f9dd8a9381695b207e1cff45540a0d0" - integrity sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA== - -"@esbuild/freebsd-arm64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz#d71502d1ee89a1130327e890364666c760a2a911" - integrity sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw== - -"@esbuild/freebsd-x64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz#aa5ea58d9c1dd9af688b8b6f63ef0d3d60cea53c" - integrity sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw== - -"@esbuild/linux-arm64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz#055b63725df678379b0f6db9d0fa85463755b2e5" - integrity sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A== - -"@esbuild/linux-arm@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz#76b3b98cb1f87936fbc37f073efabad49dcd889c" - integrity sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg== - -"@esbuild/linux-ia32@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz#c0e5e787c285264e5dfc7a79f04b8b4eefdad7fa" - integrity sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig== - -"@esbuild/linux-loong64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz#a6184e62bd7cdc63e0c0448b83801001653219c5" - integrity sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ== - -"@esbuild/linux-mips64el@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz#d08e39ce86f45ef8fc88549d29c62b8acf5649aa" - integrity sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA== - -"@esbuild/linux-ppc64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz#8d252f0b7756ffd6d1cbde5ea67ff8fd20437f20" - integrity sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg== - -"@esbuild/linux-riscv64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz#19f6dcdb14409dae607f66ca1181dd4e9db81300" - integrity sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg== - -"@esbuild/linux-s390x@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz#3c830c90f1a5d7dd1473d5595ea4ebb920988685" - integrity sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ== - -"@esbuild/linux-x64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz#86eca35203afc0d9de0694c64ec0ab0a378f6fff" - integrity sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw== - -"@esbuild/netbsd-x64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz#e771c8eb0e0f6e1877ffd4220036b98aed5915e6" - integrity sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ== - -"@esbuild/openbsd-x64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz#9a795ae4b4e37e674f0f4d716f3e226dd7c39baf" - integrity sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ== - -"@esbuild/sunos-x64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz#7df23b61a497b8ac189def6e25a95673caedb03f" - integrity sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w== - -"@esbuild/win32-arm64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz#f1ae5abf9ca052ae11c1bc806fb4c0f519bacf90" - integrity sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ== - -"@esbuild/win32-ia32@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz#241fe62c34d8e8461cd708277813e1d0ba55ce23" - integrity sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ== - -"@esbuild/win32-x64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz#9c907b21e30a52db959ba4f80bb01a0cc403d5cc" - integrity sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ== - -"@eslint-community/eslint-utils@^4.2.0": - version "4.3.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.3.0.tgz#a556790523a351b4e47e9d385f47265eaaf9780a" - integrity sha512-v3oplH6FYCULtFuCeqyuTd9D2WKO937Dxdq+GmHOLL72TTRriLxz2VLlNfkZRsvj6PKnOPAtuT6dwrs/pA5DvA== - dependencies: - eslint-visitor-keys "^3.3.0" - -"@eslint-community/regexpp@^4.6.1": - version "4.10.0" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" - integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== - -"@eslint/eslintrc@^1.4.1": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.4.1.tgz#af58772019a2d271b7e2d4c23ff4ddcba3ccfb3e" - integrity sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA== - dependencies: - ajv "^6.12.4" - debug "^4.3.2" - espree "^9.4.0" - globals "^13.19.0" - ignore "^5.2.0" - import-fresh "^3.2.1" - js-yaml "^4.1.0" - minimatch "^3.1.2" - strip-json-comments "^3.1.1" - -"@eslint/eslintrc@^2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" - integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== - dependencies: - ajv "^6.12.4" - debug "^4.3.2" - espree "^9.6.0" - globals "^13.19.0" - ignore "^5.2.0" - import-fresh "^3.2.1" - js-yaml "^4.1.0" - minimatch "^3.1.2" - strip-json-comments "^3.1.1" - -"@eslint/js@8.57.0": - version "8.57.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" - integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== - -"@fastify/ajv-compiler@^3.5.0": - version "3.5.0" - resolved "https://registry.yarnpkg.com/@fastify/ajv-compiler/-/ajv-compiler-3.5.0.tgz#459bff00fefbf86c96ec30e62e933d2379e46670" - integrity sha512-ebbEtlI7dxXF5ziNdr05mOY8NnDiPB1XvAlLHctRt/Rc+C3LCOVW5imUVX+mhvUhnNzmPBHewUkOFgGlCxgdAA== - dependencies: - ajv "^8.11.0" - ajv-formats "^2.1.1" - fast-uri "^2.0.0" - -"@fastify/busboy@^2.0.0": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d" - integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA== - -"@fastify/cors@^8.2.0": - version "8.2.0" - resolved "https://registry.yarnpkg.com/@fastify/cors/-/cors-8.2.0.tgz#44ce6b28bc111e12679cb02f980f0ce865ff4877" - integrity sha512-qDgwpmg6C4D0D3nh8MTMuRXWyEwPnDZDBODaJv90FP2o9ukbahJByW4FtrM5Bpod5KbTf1oIExBmpItbUTQmHg== - dependencies: - fastify-plugin "^4.0.0" - mnemonist "0.39.5" - -"@fastify/deepmerge@^1.0.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@fastify/deepmerge/-/deepmerge-1.3.0.tgz#8116858108f0c7d9fd460d05a7d637a13fe3239a" - integrity sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A== - -"@fastify/error@^3.0.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@fastify/error/-/error-3.2.0.tgz#9010e0acfe07965f5fc7d2b367f58f042d0f4106" - integrity sha512-KAfcLa+CnknwVi5fWogrLXgidLic+GXnLjijXdpl8pvkvbXU5BGa37iZO9FGvsh9ZL4y+oFi5cbHBm5UOG+dmQ== - -"@fastify/fast-json-stringify-compiler@^4.1.0": - version "4.2.0" - resolved "https://registry.yarnpkg.com/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-4.2.0.tgz#52d047fac76b0d75bd660f04a5dd606659f57c5a" - integrity sha512-ypZynRvXA3dibfPykQN3RB5wBdEUgSGgny8Qc6k163wYPLD4mEGEDkACp+00YmqkGvIm8D/xYoHajwyEdWD/eg== - dependencies: - fast-json-stringify "^5.0.0" - -"@fastify/helmet@^10.1.0": - version "10.1.0" - resolved "https://registry.yarnpkg.com/@fastify/helmet/-/helmet-10.1.0.tgz#8b124c807d9528680e6dcef8ca3e2e614eb547e5" - integrity sha512-v7QJPYf1uVcCwzyHzkDGcHTqDPscXj/61JKxOdvczyDPAKTxSeb9Sc2OBActXaj7zUELiAdgnkKkzNwmCNzBLQ== - dependencies: - fastify-plugin "^4.2.1" - helmet "^6.0.0" - -"@fastify/rate-limit@^8.0.0": - version "8.0.0" - resolved "https://registry.yarnpkg.com/@fastify/rate-limit/-/rate-limit-8.0.0.tgz#81cacb05ef75a21e9c87f7b71fe967cc64e84398" - integrity sha512-73pQFgpx6RMmY5nriYQW0AIATZpa/OAvWa5PQ9JHEqgjKMLNv9zLAphWIRh5914aN6sqtv8sLICr3Tp1NbXQIQ== - dependencies: - fastify-plugin "^4.0.0" - ms "^2.1.3" - tiny-lru "^10.0.0" - -"@fastify/sensible@^5.2.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@fastify/sensible/-/sensible-5.2.0.tgz#fb346596597c2a2ebed6e747ac4beb6707843bae" - integrity sha512-fy5vqJJAMVQctUT+kYfGdaGYeW9d8JaWEqtdWN6UmzQQ3VX0qMXE7Qc/MmfE+Cj3v0g5kbeR+obd3HZr3IMf+w== - dependencies: - fast-deep-equal "^3.1.1" - fastify-plugin "^4.0.0" - forwarded "^0.2.0" - http-errors "^2.0.0" - ms "^2.1.3" - type-is "^1.6.18" - vary "^1.1.2" - -"@humanwhocodes/config-array@^0.11.14": - version "0.11.14" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" - integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== - dependencies: - "@humanwhocodes/object-schema" "^2.0.2" - debug "^4.3.1" - minimatch "^3.0.5" - -"@humanwhocodes/config-array@^0.11.8": - version "0.11.8" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9" - integrity sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g== - dependencies: - "@humanwhocodes/object-schema" "^1.2.1" - debug "^4.1.1" - minimatch "^3.0.5" - -"@humanwhocodes/module-importer@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" - integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== - -"@humanwhocodes/object-schema@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" - integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== - -"@humanwhocodes/object-schema@^2.0.2": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" - integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== - -"@isaacs/cliui@^8.0.2": - version "8.0.2" - resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" - integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== - dependencies: - string-width "^5.1.2" - string-width-cjs "npm:string-width@^4.2.0" - strip-ansi "^7.0.1" - strip-ansi-cjs "npm:strip-ansi@^6.0.1" - wrap-ansi "^8.1.0" - wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" - -"@istanbuljs/schema@^0.1.2": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" - integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== - -"@jest/expect-utils@^29.6.2": - version "29.6.2" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.6.2.tgz#1b97f290d0185d264dd9fdec7567a14a38a90534" - integrity sha512-6zIhM8go3RV2IG4aIZaZbxwpOzz3ZiM23oxAlkquOIole+G6TrbeXnykxWYlqF7kz2HlBjdKtca20x9atkEQYg== - dependencies: - jest-get-type "^29.4.3" - -"@jest/schemas@^29.6.0": - version "29.6.0" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.0.tgz#0f4cb2c8e3dca80c135507ba5635a4fd755b0040" - integrity sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ== - dependencies: - "@sinclair/typebox" "^0.27.8" - -"@jest/schemas@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" - integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== - dependencies: - "@sinclair/typebox" "^0.27.8" - -"@jest/types@^29.6.1": - version "29.6.1" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.1.tgz#ae79080278acff0a6af5eb49d063385aaa897bf2" - integrity sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw== - dependencies: - "@jest/schemas" "^29.6.0" - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^17.0.8" - chalk "^4.0.0" - -"@jridgewell/gen-mapping@^0.3.0": - version "0.3.3" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" - integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== - dependencies: - "@jridgewell/set-array" "^1.0.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" - -"@jridgewell/resolve-uri@3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" - integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== - -"@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== - -"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.14" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" - integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== - -"@jridgewell/sourcemap-codec@^1.4.15": - version "1.4.15" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" - integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== - -"@jridgewell/trace-mapping@^0.3.12": - version "0.3.17" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" - integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== - dependencies: - "@jridgewell/resolve-uri" "3.1.0" - "@jridgewell/sourcemap-codec" "1.4.14" - -"@jridgewell/trace-mapping@^0.3.9": - version "0.3.18" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6" - integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA== - dependencies: - "@jridgewell/resolve-uri" "3.1.0" - "@jridgewell/sourcemap-codec" "1.4.14" - -"@napi-rs/canvas-android-arm64@0.1.38": - version "0.1.38" - resolved "https://registry.yarnpkg.com/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.38.tgz#2612b2784831ea2d03ac269779eec72325cf5e6e" - integrity sha512-CQ87kZhrg98Ou7Zu4/AytMu96FOh9zBELmG4SoyDCZSPsMJvYO0txZ3K2ptddElFU04D4xiCsQhrcPmCUhWNeA== - -"@napi-rs/canvas-darwin-arm64@0.1.38": - version "0.1.38" - resolved "https://registry.yarnpkg.com/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.38.tgz#89a97509b3babd8e2296ccdbcbddb196789b3299" - integrity sha512-bUTqq4xtT6FCV/U1bgXI4QqqyIhS/IbJ5BzTvUBrqrdomBEh0R4aapL+nJ+fybHinyYqcKrVSiSf4LkH/QIR0A== - -"@napi-rs/canvas-darwin-x64@0.1.38": - version "0.1.38" - resolved "https://registry.yarnpkg.com/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.38.tgz#31886fe2b7f23a14af82e78bb0971c1fd1d511ef" - integrity sha512-w52CHU5uBDQCll9rKtvgSQGcds1jymZ91MR794dRnaTW52L/nV1hi6/2Depugh21IwC1KwdxsiDvHycS4rbqRQ== - -"@napi-rs/canvas-linux-arm-gnueabihf@0.1.38": - version "0.1.38" - resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.38.tgz#b1b2cf3acf684905fb076a258ca1f8a1e2724c2d" - integrity sha512-szCGMXrLR3VmhCxbfiYW0AkxjtE3yETFPsTq4/HX9zUSxNPvzJgjvmwR5Wf9eEcQQJSTDHL3ux+gLv4nR0Bzrw== - -"@napi-rs/canvas-linux-arm64-gnu@0.1.38": - version "0.1.38" - resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.38.tgz#159840545e8b83d0342a55b43a7cd309166c4301" - integrity sha512-Tx+0Vdq6KJs0h/qyk+K5puVEqYGqX4xiJ/0r+Gz9kYJjkAnjZt+omPfFah/X4Zw3IbGg+4ini4YMwiIOy5Ih0w== - -"@napi-rs/canvas-linux-arm64-musl@0.1.38": - version "0.1.38" - resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.38.tgz#51cf8e0b4cb2dab1fdac0520d9596fc8381e9153" - integrity sha512-/2R8ORGrmvYjkWUs3IsKs96NjDVPGZ06TLTMStvqyQVsAdwhd39kAuzReuR29U7apYuzlaB57yEqk3gMZnsm1A== - -"@napi-rs/canvas-linux-x64-gnu@0.1.38": - version "0.1.38" - resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.38.tgz#4c0062fb968c61e4f4b43cff4cf79eaabf47fe7b" - integrity sha512-3pV7NQisKG+4ZqNQYQoFHVcaOz295eEJp3ORZv8mS4fi3Epppq6zsPsBnJfEj4iHvyjWHajePQ/tqbAXJsTiTQ== - -"@napi-rs/canvas-linux-x64-musl@0.1.38": - version "0.1.38" - resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.38.tgz#4d848384d089db1b65e0b77a99079afebe316ed9" - integrity sha512-++6F5YRPrzXBKIxoseC/Gnt9h8+wxELKYlTrTgN02/kxLMJWjkgZkv1eeiyKuQy1El5640liYts74mQ3m3zAAw== - -"@napi-rs/canvas-win32-x64-msvc@0.1.38": - version "0.1.38" - resolved "https://registry.yarnpkg.com/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.38.tgz#723eee490ff0a705023df7efc0c699b57b388734" - integrity sha512-9H+Oh6sEjD0Xa02fJXNdrZBGWZ/AEbB9/oHLLW5O+jZiPvC64Nk2bVwZcpfeEU1xypJWVKPA4+kLmwSU8cmopA== - -"@napi-rs/canvas@0.1.38": - version "0.1.38" - resolved "https://registry.yarnpkg.com/@napi-rs/canvas/-/canvas-0.1.38.tgz#66335bc7201917dfa2679e5b4bc889e7cf844c3a" - integrity sha512-Cjq7BU9kvgFv7MMZcXy2uEEpOJFkbmCyepIXWKQA37lp5Bi1db75eulYDUGLosh5MQwM7478mg+SqLXpJ1UPwA== - optionalDependencies: - "@napi-rs/canvas-android-arm64" "0.1.38" - "@napi-rs/canvas-darwin-arm64" "0.1.38" - "@napi-rs/canvas-darwin-x64" "0.1.38" - "@napi-rs/canvas-linux-arm-gnueabihf" "0.1.38" - "@napi-rs/canvas-linux-arm64-gnu" "0.1.38" - "@napi-rs/canvas-linux-arm64-musl" "0.1.38" - "@napi-rs/canvas-linux-x64-gnu" "0.1.38" - "@napi-rs/canvas-linux-x64-musl" "0.1.38" - "@napi-rs/canvas-win32-x64-msvc" "0.1.38" - -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - -"@octokit/endpoint@^6.0.1": - version "6.0.12" - resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.12.tgz#3b4d47a4b0e79b1027fb8d75d4221928b2d05658" - integrity sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA== - dependencies: - "@octokit/types" "^6.0.3" - is-plain-object "^5.0.0" - universal-user-agent "^6.0.0" - -"@octokit/graphql@^4.8.0": - version "4.8.0" - resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.8.0.tgz#664d9b11c0e12112cbf78e10f49a05959aa22cc3" - integrity sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg== - dependencies: - "@octokit/request" "^5.6.0" - "@octokit/types" "^6.0.3" - universal-user-agent "^6.0.0" - -"@octokit/openapi-types@^8.3.0": - version "8.3.0" - resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-8.3.0.tgz#8bc912edae8c03e002882cf1e29b595b7da9b441" - integrity sha512-ZFyQ30tNpoATI7o+Z9MWFUzUgWisB8yduhcky7S4UYsRijgIGSnwUKzPBDGzf/Xkx1DuvUtqzvmuFlDSqPJqmQ== - -"@octokit/request-error@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.1.0.tgz#9e150357831bfc788d13a4fd4b1913d60c74d677" - integrity sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg== - dependencies: - "@octokit/types" "^6.0.3" - deprecation "^2.0.0" - once "^1.4.0" - -"@octokit/request@^5.6.0": - version "5.6.0" - resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.6.0.tgz#6084861b6e4fa21dc40c8e2a739ec5eff597e672" - integrity sha512-4cPp/N+NqmaGQwbh3vUsYqokQIzt7VjsgTYVXiwpUP2pxd5YiZB2XuTedbb0SPtv9XS7nzAKjAuQxmY8/aZkiA== - dependencies: - "@octokit/endpoint" "^6.0.1" - "@octokit/request-error" "^2.1.0" - "@octokit/types" "^6.16.1" - is-plain-object "^5.0.0" - node-fetch "^2.6.1" - universal-user-agent "^6.0.0" - -"@octokit/types@^6.0.3", "@octokit/types@^6.16.1": - version "6.19.0" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.19.0.tgz#e2b6fedb10c8b53cf4574aa5d1a8a5611295297a" - integrity sha512-9wdZFiJfonDyU6DjIgDHxAIn92vdSUBOwAXbO2F9rOFt6DJwuAkyGLu1CvdJPphCbPBoV9iSDMX7y4fu0v6AtA== - dependencies: - "@octokit/openapi-types" "^8.3.0" - -"@oldschoolgg/eslint-config@^2.0.13": - version "2.0.13" - resolved "https://registry.yarnpkg.com/@oldschoolgg/eslint-config/-/eslint-config-2.0.13.tgz#a203aa964dde613053b4be249c51479da86134b1" - integrity sha512-DJb9d4CAQF8rYQA1+SoJK3/sngM62Z6SO6gW4K2QCIJ8Hj6QFwVCUD7CXJkkAggxl9ua0uzBqODD7OVhJ9PgUw== - dependencies: - "@typescript-eslint/eslint-plugin" "^5.41.0" - "@typescript-eslint/parser" "^5.41.0" - eslint "^8.33.0" - eslint-config-prettier "^8.5.0" - eslint-plugin-import "^2.26.0" - eslint-plugin-prettier "^4.2.1" - eslint-plugin-simple-import-sort "^8.0.0" - eslint-plugin-unicorn "^44.0.2" - prettier "^2.7.1" - -"@oldschoolgg/toolkit@^0.0.24": - version "0.0.24" - resolved "https://registry.yarnpkg.com/@oldschoolgg/toolkit/-/toolkit-0.0.24.tgz#86867f126565d278a209d0e95b3cd9ae527b39ac" - integrity sha512-z73pPLvVohlimglhWcgGcX/bF2oadUDaZvrWBpDKKChr4kiNhVXgBlnGyOQ6xJxwZGvQ+QhepSK/ZQTHaQ4ufA== - dependencies: - deepmerge "^4.3.1" - e "^0.2.3" - emoji-regex "^10.2.1" - math-expression-evaluator "^1.3.14" - -"@oldschoolgg/ts-config@^0.0.1": - version "0.0.1" - resolved "https://registry.yarnpkg.com/@oldschoolgg/ts-config/-/ts-config-0.0.1.tgz#53c6244d393548027076537820bbce92ed33e47e" - integrity sha512-POoKxMiI10iNjWkk/0sZwxCuXM79dI94tv6Y05KOsLLHL5eVHPZQa+A2gY2pzPf5isDkHwla7i3iOsQglhcKdQ== - -"@pkgjs/parseargs@^0.11.0": - version "0.11.0" - resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" - integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== - -"@prisma/client@^5.13.0": - version "5.13.0" - resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.13.0.tgz#b9f1d0983d714e982675201d8222a9ecb4bdad4a" - integrity sha512-uYdfpPncbZ/syJyiYBwGZS8Gt1PTNoErNYMuqHDa2r30rNSFtgTA/LXsSk55R7pdRTMi5pHkeP9B14K6nHmwkg== - -"@prisma/debug@5.13.0": - version "5.13.0" - resolved "https://registry.yarnpkg.com/@prisma/debug/-/debug-5.13.0.tgz#d88b0f6fafa0c216e20e284ed9fc30f1cbe45786" - integrity sha512-699iqlEvzyCj9ETrXhs8o8wQc/eVW+FigSsHpiskSFydhjVuwTJEfj/nIYqTaWFYuxiWQRfm3r01meuW97SZaQ== - -"@prisma/engines-version@5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b": - version "5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b" - resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b.tgz#a72a4fb83ba1fd01ad45f795aa55168f60d34723" - integrity sha512-AyUuhahTINGn8auyqYdmxsN+qn0mw3eg+uhkp8zwknXYIqoT3bChG4RqNY/nfDkPvzWAPBa9mrDyBeOnWSgO6A== - -"@prisma/engines@5.13.0": - version "5.13.0" - resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.13.0.tgz#8994ebf7b4e35aee7746a8465ec22738379bcab6" - integrity sha512-hIFLm4H1boj6CBZx55P4xKby9jgDTeDG0Jj3iXtwaaHmlD5JmiDkZhh8+DYWkTGchu+rRF36AVROLnk0oaqhHw== - dependencies: - "@prisma/debug" "5.13.0" - "@prisma/engines-version" "5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b" - "@prisma/fetch-engine" "5.13.0" - "@prisma/get-platform" "5.13.0" - -"@prisma/fetch-engine@5.13.0": - version "5.13.0" - resolved "https://registry.yarnpkg.com/@prisma/fetch-engine/-/fetch-engine-5.13.0.tgz#9b6945c7b38bb59e840f8905b20ea7a3d059ca55" - integrity sha512-Yh4W+t6YKyqgcSEB3odBXt7QyVSm0OQlBSldQF2SNXtmOgMX8D7PF/fvH6E6qBCpjB/yeJLy/FfwfFijoHI6sA== - dependencies: - "@prisma/debug" "5.13.0" - "@prisma/engines-version" "5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b" - "@prisma/get-platform" "5.13.0" - -"@prisma/get-platform@5.13.0": - version "5.13.0" - resolved "https://registry.yarnpkg.com/@prisma/get-platform/-/get-platform-5.13.0.tgz#99ef909a52b9d79b64d72d2d3d8210c4892b6572" - integrity sha512-B/WrQwYTzwr7qCLifQzYOmQhZcFmIFhR81xC45gweInSUn2hTEbfKUPd2keAog+y5WI5xLAFNJ3wkXplvSVkSw== - dependencies: - "@prisma/debug" "5.13.0" - -"@rollup/rollup-android-arm-eabi@4.14.2": - version "4.14.2" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.2.tgz#9047b5b1ec19f58c0fdf3a072bd977bcec056576" - integrity sha512-ahxSgCkAEk+P/AVO0vYr7DxOD3CwAQrT0Go9BJyGQ9Ef0QxVOfjDZMiF4Y2s3mLyPrjonchIMH/tbWHucJMykQ== - -"@rollup/rollup-android-arm64@4.14.2": - version "4.14.2" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.2.tgz#08a2d2705193ebb3054941994e152808beb5254e" - integrity sha512-lAarIdxZWbFSHFSDao9+I/F5jDaKyCqAPMq5HqnfpBw8dKDiCaaqM0lq5h1pQTLeIqueeay4PieGR5jGZMWprw== - -"@rollup/rollup-darwin-arm64@4.14.2": - version "4.14.2" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.2.tgz#bf746c610f337b104408ec001549d825a91eca57" - integrity sha512-SWsr8zEUk82KSqquIMgZEg2GE5mCSfr9sE/thDROkX6pb3QQWPp8Vw8zOq2GyxZ2t0XoSIUlvHDkrf5Gmf7x3Q== - -"@rollup/rollup-darwin-x64@4.14.2": - version "4.14.2" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.2.tgz#59ebe3b858a44680d5f87546ea2df1c7e3135f6a" - integrity sha512-o/HAIrQq0jIxJAhgtIvV5FWviYK4WB0WwV91SLUnsliw1lSAoLsmgEEgRWzDguAFeUEUUoIWXiJrPqU7vGiVkA== - -"@rollup/rollup-linux-arm-gnueabihf@4.14.2": - version "4.14.2" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.2.tgz#44cffc07d04d659cb635aec11bef530d5757ee6a" - integrity sha512-nwlJ65UY9eGq91cBi6VyDfArUJSKOYt5dJQBq8xyLhvS23qO+4Nr/RreibFHjP6t+5ap2ohZrUJcHv5zk5ju/g== - -"@rollup/rollup-linux-arm64-gnu@4.14.2": - version "4.14.2" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.2.tgz#9901e2288fb192b74a2f8428c507d43cc2739ceb" - integrity sha512-Pg5TxxO2IVlMj79+c/9G0LREC9SY3HM+pfAwX7zj5/cAuwrbfj2Wv9JbMHIdPCfQpYsI4g9mE+2Bw/3aeSs2rQ== - -"@rollup/rollup-linux-arm64-musl@4.14.2": - version "4.14.2" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.2.tgz#8a2c55a72e0c716a15d830fee3bf5a1a756f13ec" - integrity sha512-cAOTjGNm84gc6tS02D1EXtG7tDRsVSDTBVXOLbj31DkwfZwgTPYZ6aafSU7rD/4R2a34JOwlF9fQayuTSkoclA== - -"@rollup/rollup-linux-powerpc64le-gnu@4.14.2": - version "4.14.2" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.2.tgz#71bf99c8017476ac85b09d21b3fa2eacbad96100" - integrity sha512-4RyT6v1kXb7C0fn6zV33rvaX05P0zHoNzaXI/5oFHklfKm602j+N4mn2YvoezQViRLPnxP8M1NaY4s/5kXO5cw== - -"@rollup/rollup-linux-riscv64-gnu@4.14.2": - version "4.14.2" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.2.tgz#48ee7fe5fee7b6d0028b6dda4fab95238208a0cd" - integrity sha512-KNUH6jC/vRGAKSorySTyc/yRYlCwN/5pnMjXylfBniwtJx5O7X17KG/0efj8XM3TZU7raYRXJFFReOzNmL1n1w== - -"@rollup/rollup-linux-s390x-gnu@4.14.2": - version "4.14.2" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.2.tgz#65ad6f82729ef9d8634847189214e3205892f42f" - integrity sha512-xPV4y73IBEXToNPa3h5lbgXOi/v0NcvKxU0xejiFw6DtIYQqOTMhZ2DN18/HrrP0PmiL3rGtRG9gz1QE8vFKXQ== - -"@rollup/rollup-linux-x64-gnu@4.14.2": - version "4.14.2" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.2.tgz#2ab802ce25c0d0d44a0ea55b0068f79e462d22cd" - integrity sha512-QBhtr07iFGmF9egrPOWyO5wciwgtzKkYPNLVCFZTmr4TWmY0oY2Dm/bmhHjKRwZoGiaKdNcKhFtUMBKvlchH+Q== - -"@rollup/rollup-linux-x64-musl@4.14.2": - version "4.14.2" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.2.tgz#85dcd3f549c2fdbcf1cb1f1b5f501933ed590880" - integrity sha512-8zfsQRQGH23O6qazZSFY5jP5gt4cFvRuKTpuBsC1ZnSWxV8ZKQpPqOZIUtdfMOugCcBvFGRa1pDC/tkf19EgBw== - -"@rollup/rollup-win32-arm64-msvc@4.14.2": - version "4.14.2" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.2.tgz#10f608dfc1e5bb96aca18c7784cc4a94d890c03c" - integrity sha512-H4s8UjgkPnlChl6JF5empNvFHp77Jx+Wfy2EtmYPe9G22XV+PMuCinZVHurNe8ggtwoaohxARJZbaH/3xjB/FA== - -"@rollup/rollup-win32-ia32-msvc@4.14.2": - version "4.14.2" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.2.tgz#f27f9fb64b7e10b04121e0054d9145ee21589267" - integrity sha512-djqpAjm/i8erWYF0K6UY4kRO3X5+T4TypIqw60Q8MTqSBaQNpNXDhxdjpZ3ikgb+wn99svA7jxcXpiyg9MUsdw== - -"@rollup/rollup-win32-x64-msvc@4.14.2": - version "4.14.2" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.2.tgz#5d2d9dc96b436469dc74ef93de069b14fb12aace" - integrity sha512-teAqzLT0yTYZa8ZP7zhFKEx4cotS8Tkk5XiqNMJhD4CpaWB1BHARE4Qy+RzwnXvSAYv+Q3jAqCVBS+PS+Yee8Q== - -"@sapphire/async-queue@^1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@sapphire/async-queue/-/async-queue-1.5.0.tgz#2f255a3f186635c4fb5a2381e375d3dfbc5312d8" - integrity sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA== - -"@sapphire/shapeshift@^3.9.3": - version "3.9.7" - resolved "https://registry.yarnpkg.com/@sapphire/shapeshift/-/shapeshift-3.9.7.tgz#43e23243cac8a0c046bf1e73baf3dbf407d33a0c" - integrity sha512-4It2mxPSr4OGn4HSQWGmhFMsNFGfFVhWeRPCRwbH972Ek2pzfGRZtb0pJ4Ze6oIzcyh2jw7nUDa6qGlWofgd9g== - dependencies: - fast-deep-equal "^3.1.3" - lodash "^4.17.21" - -"@sapphire/snowflake@3.5.1", "@sapphire/snowflake@^3.5.1": - version "3.5.1" - resolved "https://registry.yarnpkg.com/@sapphire/snowflake/-/snowflake-3.5.1.tgz#254521c188b49e8b2d4cc048b475fb2b38737fec" - integrity sha512-BxcYGzgEsdlG0dKAyOm0ehLGm2CafIrfQTZGWgkfKYbj+pNNsorZ7EotuZukc2MT70E0UbppVbtpBrqpzVzjNA== - -"@sapphire/snowflake@^3.5.3": - version "3.5.3" - resolved "https://registry.yarnpkg.com/@sapphire/snowflake/-/snowflake-3.5.3.tgz#0c102aa2ec5b34f806e9bc8625fc6a5e1d0a0c6a" - integrity sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ== - -"@sapphire/stopwatch@^1.4.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@sapphire/stopwatch/-/stopwatch-1.4.0.tgz#76bd616a5ab8618c3932a016586143b7774286c7" - integrity sha512-4gtDuls69vTLseQ3TCZnrWj2boLz7imok0W7wg0+VtF+VGTK3glRn34YEEt0Hj92Ebqmst87EPEJyZ1U5nNs9w== - dependencies: - tslib "^2.3.1" - -"@sapphire/time-utilities@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@sapphire/time-utilities/-/time-utilities-1.6.0.tgz#89624a63861814bd41fe29db80c9dbe9f3625760" - integrity sha512-GKS3LdHS7Bec+FUx9cZY88edebK9vdt5rm0VCgc8s17O/WHl5KFcuyXRlWyIF7TLlYe1vBmMIl5YBE1EjHvJBA== - dependencies: - "@sapphire/utilities" "^3.3.0" - -"@sapphire/utilities@^1.4.7": - version "1.7.0" - resolved "https://registry.yarnpkg.com/@sapphire/utilities/-/utilities-1.7.0.tgz#b40adc0f092ce988232da3879c68d0ee17ee8908" - integrity sha512-+bU6rGr529oj1RoK31aHkBoLYnEqawRwwwUMSRPeaZx21gPrvqjJgwyBBMPEWTv2kjRSyzTTVtV7U0bkZ2RMjQ== - -"@sapphire/utilities@^3.3.0": - version "3.3.0" - resolved "https://registry.yarnpkg.com/@sapphire/utilities/-/utilities-3.3.0.tgz#62ff0a52cd86bd6169a94b2f217d72da6772a3cd" - integrity sha512-wWESfB03elALhci3GjcacRh8pnK89Qe5AEKCQplKyTCKabWl64SAFw52hQBth2fMmJStgK1fr87aGhRZAB8DNA== - -"@sentry-internal/tracing@7.113.0": - version "7.113.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.113.0.tgz#936f23205ab53be62f1753b923eddc243cefde86" - integrity sha512-8MDnYENRMnEfQjvN4gkFYFaaBSiMFSU/6SQZfY9pLI3V105z6JQ4D0PGMAUVowXilwNZVpKNYohE7XByuhEC7Q== - dependencies: - "@sentry/core" "7.113.0" - "@sentry/types" "7.113.0" - "@sentry/utils" "7.113.0" - -"@sentry/core@7.113.0": - version "7.113.0" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.113.0.tgz#84307eabf03ece9304894ad24ee15581a220c5c7" - integrity sha512-pg75y3C5PG2+ur27A0Re37YTCEnX0liiEU7EOxWDGutH17x3ySwlYqLQmZsFZTSnvzv7t3MGsNZ8nT5O0746YA== - dependencies: - "@sentry/types" "7.113.0" - "@sentry/utils" "7.113.0" - -"@sentry/integrations@7.113.0": - version "7.113.0" - resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-7.113.0.tgz#cce71e07cf90c4bf9b22f85c3ce22d9ba926ae5a" - integrity sha512-w0sspGBQ+6+V/9bgCkpuM3CGwTYoQEVeTW6iNebFKbtN7MrM3XsGAM9I2cW1jVxFZROqCBPFtd2cs5n0j14aAg== - dependencies: - "@sentry/core" "7.113.0" - "@sentry/types" "7.113.0" - "@sentry/utils" "7.113.0" - localforage "^1.8.1" - -"@sentry/node@^7.113.0": - version "7.113.0" - resolved "https://registry.yarnpkg.com/@sentry/node/-/node-7.113.0.tgz#c0d15a50a167f6cfc3a736838c9a919d5d6f1b01" - integrity sha512-Vam4Ia0I9fhVw8GJOzcLP7MiiHJSKl8L9LzLMMLG3+2/dFnDQOyS7sOfk3GqgpwzqPiusP9vFu7CFSX7EMQbTg== - dependencies: - "@sentry-internal/tracing" "7.113.0" - "@sentry/core" "7.113.0" - "@sentry/integrations" "7.113.0" - "@sentry/types" "7.113.0" - "@sentry/utils" "7.113.0" - -"@sentry/types@7.113.0": - version "7.113.0" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.113.0.tgz#2193c9933838302c82814771b03a8647fa684ffb" - integrity sha512-PJbTbvkcPu/LuRwwXB1He8m+GjDDLKBtu3lWg5xOZaF5IRdXQU2xwtdXXsjge4PZR00tF7MO7X8ZynTgWbYaew== - -"@sentry/utils@7.113.0": - version "7.113.0" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.113.0.tgz#1e6e790c9d84e4809b2bb529bbd33a506b6db7bd" - integrity sha512-nzKsErwmze1mmEsbW2AwL2oB+I5v6cDEJY4sdfLekA4qZbYZ8pV5iWza6IRl4XfzGTE1qpkZmEjPU9eyo0yvYw== - dependencies: - "@sentry/types" "7.113.0" - -"@sinclair/typebox@^0.27.8": - version "0.27.8" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" - integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== - -"@types/deep-equal@^1.0.3": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@types/deep-equal/-/deep-equal-1.0.4.tgz#c0a854be62d6b9fae665137a6639aab53389a147" - integrity sha512-tqdiS4otQP4KmY0PR3u6KbZ5EWvhNdUoS/jc93UuK23C220lOZ/9TvjfxdPcKvqwwDVtmtSCrnr0p/2dirAxkA== - -"@types/estree@1.0.5", "@types/estree@^1.0.0": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" - integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== - -"@types/he@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@types/he/-/he-1.1.2.tgz#0c8b275f36d2b8b651104638e4d45693349c3953" - integrity sha512-kSJPcLO1x+oolc0R89pUl2kozldQ/fVQ1C1p5mp8fPoLdF/ZcBvckaTC2M8xXh3GYendXvCpy5m/a2eSbfgNgw== - -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" - integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== - -"@types/istanbul-lib-report@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" - integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== - dependencies: - "@types/istanbul-lib-coverage" "*" - -"@types/istanbul-reports@^3.0.0": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" - integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== - dependencies: - "@types/istanbul-lib-report" "*" - -"@types/jest-image-snapshot@^6.1.0": - version "6.1.0" - resolved "https://registry.yarnpkg.com/@types/jest-image-snapshot/-/jest-image-snapshot-6.1.0.tgz#d74176821cbdf24458f39819743e9adb3d3146e9" - integrity sha512-wYayjKQGdI0ZbmsAq7OPt+5wMMi1CDXXkF3LfoGj4eRC0dOqlYJdXqLwfC89Qf2apQdlL9StgCkw0iTDb8lpUw== - dependencies: - "@types/jest" "*" - "@types/pixelmatch" "*" - ssim.js "^3.1.1" - -"@types/jest@*": - version "29.5.3" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.3.tgz#7a35dc0044ffb8b56325c6802a4781a626b05777" - integrity sha512-1Nq7YrO/vJE/FYnqYyw0FS8LdrjExSgIiHyKg7xPpn+yi8Q4huZryKnkJatN1ZRH89Kw2v33/8ZMB7DuZeSLlA== - dependencies: - expect "^29.0.0" - pretty-format "^29.0.0" - -"@types/json-schema@^7.0.9": - version "7.0.9" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" - integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== - -"@types/json5@^0.0.29": - version "0.0.29" - resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" - integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= - -"@types/lodash@^4.14.195": - version "4.14.195" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.195.tgz#bafc975b252eb6cea78882ce8a7b6bf22a6de632" - integrity sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg== - -"@types/madge@^5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@types/madge/-/madge-5.0.0.tgz#5b77c542cb547157b73c7d3c01c82ba81fdec5ca" - integrity sha512-Son5Z121knxCXlQM3Q0ivh0OP8Fix4ztGl0VfA9JybQMPQprc2K4jtTaRc3IhGyBy6ku5cWKJxEuj8zePiZbBQ== - dependencies: - "@types/node" "*" - -"@types/node-cron@^3.0.7": - version "3.0.7" - resolved "https://registry.yarnpkg.com/@types/node-cron/-/node-cron-3.0.7.tgz#978bf75f7247385c61d23b6a060ba9eedb03e2f4" - integrity sha512-9PuLtBboc/+JJ7FshmJWv769gDonTpItN0Ol5TMwclpSQNjVyB2SRxSKBcTtbSysSL5R7Oea06kTTFNciCoYwA== - -"@types/node-fetch@^2.6.1": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.1.tgz#8f127c50481db65886800ef496f20bbf15518975" - integrity sha512-oMqjURCaxoSIsHSr1E47QHzbmzNR5rK8McHuNb11BOM9cHcIK3Avy0s/b2JlXHoQGTYS3NsvWzV1M0iK7l0wbA== - dependencies: - "@types/node" "*" - form-data "^3.0.0" - -"@types/node@*": - version "16.3.3" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.3.3.tgz#0c30adff37bbbc7a50eb9b58fae2a504d0d88038" - integrity sha512-8h7k1YgQKxKXWckzFCMfsIwn0Y61UK6tlD6y2lOb3hTOIMlK3t9/QwHOhc81TwU+RMf0As5fj7NPjroERCnejQ== - -"@types/node@^14.18.12": - version "14.18.12" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.12.tgz#0d4557fd3b94497d793efd4e7d92df2f83b4ef24" - integrity sha512-q4jlIR71hUpWTnGhXWcakgkZeHa3CCjcQcnuzU8M891BAWA2jHiziiWEPEkdS5pFsz7H9HJiy8BrK7tBRNrY7A== - -"@types/normalize-package-data@^2.4.0": - version "2.4.1" - resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" - integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== - -"@types/pixelmatch@*": - version "5.2.4" - resolved "https://registry.yarnpkg.com/@types/pixelmatch/-/pixelmatch-5.2.4.tgz#ca145cc5ede1388c71c68edf2d1f5190e5ddd0f6" - integrity sha512-HDaSHIAv9kwpMN7zlmwfTv6gax0PiporJOipcrGsVNF3Ba+kryOZc0Pio5pn6NhisgWr7TaajlPEKTbTAypIBQ== - dependencies: - "@types/node" "*" - -"@types/semver@^7.3.12": - version "7.3.13" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91" - integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw== - -"@types/stack-utils@^2.0.0": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" - integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== - -"@types/ws@8.5.9": - version "8.5.9" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.9.tgz#384c489f99c83225a53f01ebc3eddf3b8e202a8c" - integrity sha512-jbdrY0a8lxfdTp/+r7Z4CkycbOFN8WX+IOchLJr3juT/xzbJ8URyTVSJ/hvNdadTgM1mnedb47n+Y31GsFnQlg== - dependencies: - "@types/node" "*" - -"@types/ws@^8.5.9": - version "8.5.10" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787" - integrity sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A== - dependencies: - "@types/node" "*" - -"@types/yargs-parser@*": - version "21.0.0" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" - integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== - -"@types/yargs@^17.0.8": - version "17.0.24" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.24.tgz#b3ef8d50ad4aa6aecf6ddc97c580a00f5aa11902" - integrity sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw== - dependencies: - "@types/yargs-parser" "*" - -"@typescript-eslint/eslint-plugin@^5.41.0": - version "5.50.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.50.0.tgz#fb48c31cadc853ffc1dc35373f56b5e2a8908fe9" - integrity sha512-vwksQWSFZiUhgq3Kv7o1Jcj0DUNylwnIlGvKvLLYsq8pAWha6/WCnXUeaSoNNha/K7QSf2+jvmkxggC1u3pIwQ== - dependencies: - "@typescript-eslint/scope-manager" "5.50.0" - "@typescript-eslint/type-utils" "5.50.0" - "@typescript-eslint/utils" "5.50.0" - debug "^4.3.4" - grapheme-splitter "^1.0.4" - ignore "^5.2.0" - natural-compare-lite "^1.4.0" - regexpp "^3.2.0" - semver "^7.3.7" - tsutils "^3.21.0" - -"@typescript-eslint/parser@^5.41.0": - version "5.50.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.50.0.tgz#a33f44b2cc83d1b7176ec854fbecd55605b0b032" - integrity sha512-KCcSyNaogUDftK2G9RXfQyOCt51uB5yqC6pkUYqhYh8Kgt+DwR5M0EwEAxGPy/+DH6hnmKeGsNhiZRQxjH71uQ== - dependencies: - "@typescript-eslint/scope-manager" "5.50.0" - "@typescript-eslint/types" "5.50.0" - "@typescript-eslint/typescript-estree" "5.50.0" - debug "^4.3.4" - -"@typescript-eslint/scope-manager@5.50.0": - version "5.50.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.50.0.tgz#90b8a3b337ad2c52bbfe4eac38f9164614e40584" - integrity sha512-rt03kaX+iZrhssaT974BCmoUikYtZI24Vp/kwTSy841XhiYShlqoshRFDvN1FKKvU2S3gK+kcBW1EA7kNUrogg== - dependencies: - "@typescript-eslint/types" "5.50.0" - "@typescript-eslint/visitor-keys" "5.50.0" - -"@typescript-eslint/type-utils@5.50.0": - version "5.50.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.50.0.tgz#509d5cc9728d520008f7157b116a42c5460e7341" - integrity sha512-dcnXfZ6OGrNCO7E5UY/i0ktHb7Yx1fV6fnQGGrlnfDhilcs6n19eIRcvLBqx6OQkrPaFlDPk3OJ0WlzQfrV0bQ== - dependencies: - "@typescript-eslint/typescript-estree" "5.50.0" - "@typescript-eslint/utils" "5.50.0" - debug "^4.3.4" - tsutils "^3.21.0" - -"@typescript-eslint/types@5.50.0": - version "5.50.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.50.0.tgz#c461d3671a6bec6c2f41f38ed60bd87aa8a30093" - integrity sha512-atruOuJpir4OtyNdKahiHZobPKFvZnBnfDiyEaBf6d9vy9visE7gDjlmhl+y29uxZ2ZDgvXijcungGFjGGex7w== - -"@typescript-eslint/types@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" - integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== - -"@typescript-eslint/typescript-estree@5.50.0": - version "5.50.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.50.0.tgz#0b9b82975bdfa40db9a81fdabc7f93396867ea97" - integrity sha512-Gq4zapso+OtIZlv8YNAStFtT6d05zyVCK7Fx3h5inlLBx2hWuc/0465C2mg/EQDDU2LKe52+/jN4f0g9bd+kow== - dependencies: - "@typescript-eslint/types" "5.50.0" - "@typescript-eslint/visitor-keys" "5.50.0" - debug "^4.3.4" - globby "^11.1.0" - is-glob "^4.0.3" - semver "^7.3.7" - tsutils "^3.21.0" - -"@typescript-eslint/typescript-estree@^5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" - integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA== - dependencies: - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/visitor-keys" "5.62.0" - debug "^4.3.4" - globby "^11.1.0" - is-glob "^4.0.3" - semver "^7.3.7" - tsutils "^3.21.0" - -"@typescript-eslint/utils@5.50.0": - version "5.50.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.50.0.tgz#807105f5ffb860644d30d201eefad7017b020816" - integrity sha512-v/AnUFImmh8G4PH0NDkf6wA8hujNNcrwtecqW4vtQ1UOSNBaZl49zP1SHoZ/06e+UiwzHpgb5zP5+hwlYYWYAw== - dependencies: - "@types/json-schema" "^7.0.9" - "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.50.0" - "@typescript-eslint/types" "5.50.0" - "@typescript-eslint/typescript-estree" "5.50.0" - eslint-scope "^5.1.1" - eslint-utils "^3.0.0" - semver "^7.3.7" - -"@typescript-eslint/visitor-keys@5.50.0": - version "5.50.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.50.0.tgz#b752ffc143841f3d7bc57d6dd01ac5c40f8c4903" - integrity sha512-cdMeD9HGu6EXIeGOh2yVW6oGf9wq8asBgZx7nsR/D36gTfQ0odE5kcRYe5M81vjEFAcPeugXrHg78Imu55F6gg== - dependencies: - "@typescript-eslint/types" "5.50.0" - eslint-visitor-keys "^3.3.0" - -"@typescript-eslint/visitor-keys@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" - integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== - dependencies: - "@typescript-eslint/types" "5.62.0" - eslint-visitor-keys "^3.3.0" - -"@ungap/structured-clone@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" - integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== - -"@vitest/coverage-v8@^1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@vitest/coverage-v8/-/coverage-v8-1.3.1.tgz#cf5ed8eb5bff6658cea126d0f8fcd2872de0b59f" - integrity sha512-UuBnkSJUNE9rdHjDCPyJ4fYuMkoMtnghes1XohYa4At0MS3OQSAo97FrbwSLRshYsXThMZy1+ybD/byK5llyIg== - dependencies: - "@ampproject/remapping" "^2.2.1" - "@bcoe/v8-coverage" "^0.2.3" - debug "^4.3.4" - istanbul-lib-coverage "^3.2.2" - istanbul-lib-report "^3.0.1" - istanbul-lib-source-maps "^4.0.1" - istanbul-reports "^3.1.6" - magic-string "^0.30.5" - magicast "^0.3.3" - picocolors "^1.0.0" - std-env "^3.5.0" - test-exclude "^6.0.0" - v8-to-istanbul "^9.2.0" - -"@vitest/expect@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-1.6.0.tgz#0b3ba0914f738508464983f4d811bc122b51fb30" - integrity sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ== - dependencies: - "@vitest/spy" "1.6.0" - "@vitest/utils" "1.6.0" - chai "^4.3.10" - -"@vitest/runner@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-1.6.0.tgz#a6de49a96cb33b0e3ba0d9064a3e8d6ce2f08825" - integrity sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg== - dependencies: - "@vitest/utils" "1.6.0" - p-limit "^5.0.0" - pathe "^1.1.1" - -"@vitest/snapshot@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-1.6.0.tgz#deb7e4498a5299c1198136f56e6e0f692e6af470" - integrity sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ== - dependencies: - magic-string "^0.30.5" - pathe "^1.1.1" - pretty-format "^29.7.0" - -"@vitest/spy@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-1.6.0.tgz#362cbd42ccdb03f1613798fde99799649516906d" - integrity sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw== - dependencies: - tinyspy "^2.2.0" - -"@vitest/utils@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-1.6.0.tgz#5c5675ca7d6f546a7b4337de9ae882e6c57896a1" - integrity sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw== - dependencies: - diff-sequences "^29.6.3" - estree-walker "^3.0.3" - loupe "^2.3.7" - pretty-format "^29.7.0" - -"@vladfrangu/async_event_emitter@^2.2.2": - version "2.2.2" - resolved "https://registry.yarnpkg.com/@vladfrangu/async_event_emitter/-/async_event_emitter-2.2.2.tgz#84c5a3f8d648842cec5cc649b88df599af32ed88" - integrity sha512-HIzRG7sy88UZjBJamssEczH5q7t5+axva19UbZLO6u0ySbYPrwzWiXBcC0WuHyhKKoeCyneH+FvYzKQq/zTtkQ== - -abort-controller@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" - integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== - dependencies: - event-target-shim "^5.0.0" - -abstract-logging@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/abstract-logging/-/abstract-logging-2.0.1.tgz#6b0c371df212db7129b57d2e7fcf282b8bf1c839" - integrity sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA== - -acorn-jsx@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== - -acorn-walk@^8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.2.tgz#7703af9415f1b6db9315d6895503862e231d34aa" - integrity sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A== - -acorn@^8.11.3, acorn@^8.9.0: - version "8.11.3" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" - integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== - -acorn@^8.8.0, acorn@^8.8.2: - version "8.8.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" - integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== - -ajv-formats@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" - integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== - dependencies: - ajv "^8.0.0" - -ajv@^6.10.0, ajv@^6.12.4: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@^8.0.0, ajv@^8.10.0, ajv@^8.11.0: - version "8.12.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" - integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== - dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-regex@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" - integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -ansi-styles@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" - integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== - -ansi-styles@^6.1.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" - integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== - -any-promise@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" - integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== - -app-module-path@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/app-module-path/-/app-module-path-2.2.0.tgz#641aa55dfb7d6a6f0a8141c4b9c0aa50b6c24dd5" - integrity sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ== - -archy@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" - integrity sha1-+cjBN1fMHde8N5rHeyxipcKGjEA= - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -array-buffer-byte-length@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead" - integrity sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A== - dependencies: - call-bind "^1.0.2" - is-array-buffer "^3.0.1" - -array-includes@^3.1.6: - version "3.1.6" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.6.tgz#9e9e720e194f198266ba9e18c29e6a9b0e4b225f" - integrity sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - get-intrinsic "^1.1.3" - is-string "^1.0.7" - -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - -array.prototype.flat@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz#ffc6576a7ca3efc2f46a143b9d1dda9b4b3cf5e2" - integrity sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - es-shim-unscopables "^1.0.0" - -array.prototype.flatmap@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz#1aae7903c2100433cb8261cd4ed310aab5c4a183" - integrity sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - es-shim-unscopables "^1.0.0" - -ascii-table3@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/ascii-table3/-/ascii-table3-0.9.0.tgz#fed1f8c735bd056f1d67b4dff482a72b0858ebad" - integrity sha512-/JcvVCQRTVQHwLI8TCxSeOS9AcCV01MbCJC4plSP5ulygJH+D30lz85nvMcER5k+qoX2fJ1C/i13Zo1/eoMGTw== - dependencies: - printable-characters "^1.0.42" - -assertion-error@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" - integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== - -ast-module-types@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ast-module-types/-/ast-module-types-5.0.0.tgz#32b2b05c56067ff38e95df66f11d6afd6c9ba16b" - integrity sha512-JvqziE0Wc0rXQfma0HZC/aY7URXHFuZV84fJRtP8u+lhp0JYCNd5wJzVXP45t0PH0Mej3ynlzvdyITYIu0G4LQ== - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= - -atomic-sleep@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" - integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== - -available-typed-arrays@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" - integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== - -available-typed-arrays@^1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" - integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== - dependencies: - possible-typed-array-names "^1.0.0" - -avvio@^8.2.0: - version "8.2.1" - resolved "https://registry.yarnpkg.com/avvio/-/avvio-8.2.1.tgz#b5a482729847abb84d5aadce06511c04a0a62f82" - integrity sha512-TAlMYvOuwGyLK3PfBb5WKBXZmXz2fVCgv23d6zZFdle/q3gPjmxBaeuC0pY0Dzs5PWMSgfqqEZkrye19GlDTgw== - dependencies: - archy "^1.0.0" - debug "^4.0.0" - fastq "^1.6.1" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -base64-js@^1.3.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - -bl@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" - integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== - dependencies: - buffer "^5.5.0" - inherits "^2.0.4" - readable-stream "^3.4.0" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== - dependencies: - balanced-match "^1.0.0" - -braces@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -buffer@^5.5.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" - integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.1.13" - -buffer@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" - integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.2.1" - -bufferutil@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.8.tgz#1de6a71092d65d7766c4d8a522b261a6e787e8ea" - integrity sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw== - dependencies: - node-gyp-build "^4.3.0" - -builtin-modules@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" - integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== - -bytes@3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - -cac@^6.7.14: - version "6.7.14" - resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" - integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== - -call-bind@^1.0.0, call-bind@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== - dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" - -call-bind@^1.0.5, call-bind@^1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" - integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - set-function-length "^1.2.1" - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -chai@^4.3.10: - version "4.4.1" - resolved "https://registry.yarnpkg.com/chai/-/chai-4.4.1.tgz#3603fa6eba35425b0f2ac91a009fe924106e50d1" - integrity sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g== - dependencies: - assertion-error "^1.1.0" - check-error "^1.0.3" - deep-eql "^4.1.3" - get-func-name "^2.0.2" - loupe "^2.3.6" - pathval "^1.1.1" - type-detect "^4.0.8" - -chalk@^2.0.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chart.js@^3.7.0: - version "3.9.1" - resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.9.1.tgz#3abf2c775169c4c71217a107163ac708515924b8" - integrity sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w== - -"chartjs-node-canvas@github:gc/ChartjsNodeCanvas#a598b6dd27c44351f235bca07ca4ee660121f289": - version "4.1.6" - resolved "https://codeload.github.com/gc/ChartjsNodeCanvas/tar.gz/a598b6dd27c44351f235bca07ca4ee660121f289" - dependencies: - tslib "^2.3.1" - -chartjs-plugin-datalabels@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-2.2.0.tgz#369578e131d743c2e34b5fbe2d3f9335f6639b8f" - integrity sha512-14ZU30lH7n89oq+A4bWaJPnAG8a7ZTk7dKf48YAzMvJjQtjrgg5Dpk9f+LbjCF6bpx3RAGTeL13IXpKQYyRvlw== - -check-error@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.3.tgz#a6502e4312a7ee969f646e83bb3ddd56281bd694" - integrity sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg== - dependencies: - get-func-name "^2.0.2" - -ci-info@^3.2.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91" - integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw== - -ci-info@^3.4.0: - version "3.7.1" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.7.1.tgz#708a6cdae38915d597afdf3b145f2f8e1ff55f3f" - integrity sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w== - -clean-regexp@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/clean-regexp/-/clean-regexp-1.0.0.tgz#8df7c7aae51fd36874e8f8d05b9180bc11a3fed7" - integrity sha1-jffHquUf02h06PjQW5GAvBGj/tc= - dependencies: - escape-string-regexp "^1.0.5" - -cli-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" - integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== - dependencies: - restore-cursor "^3.1.0" - -cli-spinners@^2.5.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.7.0.tgz#f815fd30b5f9eaac02db604c7a231ed7cb2f797a" - integrity sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw== - -cliui@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" - integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.1" - wrap-ansi "^7.0.0" - -clone@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" - integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= - -color-name@^1.1.4, color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -commander@^10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" - integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== - -commander@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" - integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== - -commondir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" - integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== - -complex.js@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.1.1.tgz#0675dac8e464ec431fb2ab7d30f41d889fb25c31" - integrity sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -concurrently@^7.6.0: - version "7.6.0" - resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-7.6.0.tgz#531a6f5f30cf616f355a4afb8f8fcb2bba65a49a" - integrity sha512-BKtRgvcJGeZ4XttiDiNcFiRlxoAeZOseqUvyYRUp/Vtd+9p1ULmeoSqGsDA+2ivdeDFpqrJvGvmI+StKfKl5hw== - dependencies: - chalk "^4.1.0" - date-fns "^2.29.1" - lodash "^4.17.21" - rxjs "^7.0.0" - shell-quote "^1.7.3" - spawn-command "^0.0.2-1" - supports-color "^8.1.0" - tree-kill "^1.2.2" - yargs "^17.3.1" - -convert-source-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" - integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== - -cookie@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" - integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== - -cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -date-fns@^2.29.1: - version "2.30.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0" - integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw== - dependencies: - "@babel/runtime" "^7.21.0" - -debug@^3.2.7: - version "3.2.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" - integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== - dependencies: - ms "^2.1.1" - -debug@^4.0.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -decimal.js@^10.4.3: - version "10.4.3" - resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" - integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== - -deep-eql@^4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.3.tgz#7c7775513092f7df98d8df9996dd085eb668cc6d" - integrity sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw== - dependencies: - type-detect "^4.0.0" - -deep-equal@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.2.3.tgz#af89dafb23a396c7da3e862abc0be27cf51d56e1" - integrity sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA== - dependencies: - array-buffer-byte-length "^1.0.0" - call-bind "^1.0.5" - es-get-iterator "^1.1.3" - get-intrinsic "^1.2.2" - is-arguments "^1.1.1" - is-array-buffer "^3.0.2" - is-date-object "^1.0.5" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.2" - isarray "^2.0.5" - object-is "^1.1.5" - object-keys "^1.1.1" - object.assign "^4.1.4" - regexp.prototype.flags "^1.5.1" - side-channel "^1.0.4" - which-boxed-primitive "^1.0.2" - which-collection "^1.0.1" - which-typed-array "^1.1.13" - -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - -deep-is@^0.1.3, deep-is@~0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" - integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= - -deepmerge@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" - integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== - -defaults@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a" - integrity sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A== - dependencies: - clone "^1.0.2" - -define-data-property@^1.0.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.1.tgz#c35f7cd0ab09883480d12ac5cb213715587800b3" - integrity sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ== - dependencies: - get-intrinsic "^1.2.1" - gopd "^1.0.1" - has-property-descriptors "^1.0.0" - -define-data-property@^1.1.2, define-data-property@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" - integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - gopd "^1.0.1" - -define-properties@^1.1.3, define-properties@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" - integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== - dependencies: - has-property-descriptors "^1.0.0" - object-keys "^1.1.1" - -define-properties@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" - integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== - dependencies: - define-data-property "^1.0.1" - has-property-descriptors "^1.0.0" - object-keys "^1.1.1" - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= - -depd@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - -dependency-tree@^10.0.9: - version "10.0.9" - resolved "https://registry.yarnpkg.com/dependency-tree/-/dependency-tree-10.0.9.tgz#0c6c0dbeb0c5ec2cf83bf755f30e9cb12e7b4ac7" - integrity sha512-dwc59FRIsht+HfnTVM0BCjJaEWxdq2YAvEDy4/Hn6CwS3CBWMtFnL3aZGAkQn3XCYxk/YcTDE4jX2Q7bFTwCjA== - dependencies: - commander "^10.0.1" - filing-cabinet "^4.1.6" - precinct "^11.0.5" - typescript "^5.0.4" - -deprecation@^2.0.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" - integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== - -detective-amd@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/detective-amd/-/detective-amd-5.0.2.tgz#579900f301c160efe037a6377ec7e937434b2793" - integrity sha512-XFd/VEQ76HSpym80zxM68ieB77unNuoMwopU2TFT/ErUk5n4KvUTwW4beafAVUugrjV48l4BmmR0rh2MglBaiA== - dependencies: - ast-module-types "^5.0.0" - escodegen "^2.0.0" - get-amd-module-type "^5.0.1" - node-source-walk "^6.0.1" - -detective-cjs@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/detective-cjs/-/detective-cjs-5.0.1.tgz#836ad51c6de4863efc7c419ec243694f760ff8b2" - integrity sha512-6nTvAZtpomyz/2pmEmGX1sXNjaqgMplhQkskq2MLrar0ZAIkHMrDhLXkRiK2mvbu9wSWr0V5/IfiTrZqAQMrmQ== - dependencies: - ast-module-types "^5.0.0" - node-source-walk "^6.0.0" - -detective-es6@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/detective-es6/-/detective-es6-4.0.1.tgz#38d5d49a6d966e992ef8f2d9bffcfe861a58a88a" - integrity sha512-k3Z5tB4LQ8UVHkuMrFOlvb3GgFWdJ9NqAa2YLUU/jTaWJIm+JJnEh4PsMc+6dfT223Y8ACKOaC0qcj7diIhBKw== - dependencies: - node-source-walk "^6.0.1" - -detective-postcss@^6.1.3: - version "6.1.3" - resolved "https://registry.yarnpkg.com/detective-postcss/-/detective-postcss-6.1.3.tgz#51a2d4419327ad85d0af071c7054c79fafca7e73" - integrity sha512-7BRVvE5pPEvk2ukUWNQ+H2XOq43xENWbH0LcdCE14mwgTBEAMoAx+Fc1rdp76SmyZ4Sp48HlV7VedUnP6GA1Tw== - dependencies: - is-url "^1.2.4" - postcss "^8.4.23" - postcss-values-parser "^6.0.2" - -detective-sass@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/detective-sass/-/detective-sass-5.0.3.tgz#63e54bc9b32f4bdbd9d5002308f9592a3d3a508f" - integrity sha512-YsYT2WuA8YIafp2RVF5CEfGhhyIVdPzlwQgxSjK+TUm3JoHP+Tcorbk3SfG0cNZ7D7+cYWa0ZBcvOaR0O8+LlA== - dependencies: - gonzales-pe "^4.3.0" - node-source-walk "^6.0.1" - -detective-scss@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/detective-scss/-/detective-scss-4.0.3.tgz#79758baa0158f72bfc4481eb7e21cc3b5f1ea6eb" - integrity sha512-VYI6cHcD0fLokwqqPFFtDQhhSnlFWvU614J42eY6G0s8c+MBhi9QAWycLwIOGxlmD8I/XvGSOUV1kIDhJ70ZPg== - dependencies: - gonzales-pe "^4.3.0" - node-source-walk "^6.0.1" - -detective-stylus@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/detective-stylus/-/detective-stylus-4.0.0.tgz#ce97b6499becdc291de7b3c11df8c352c1eee46e" - integrity sha512-TfPotjhszKLgFBzBhTOxNHDsutIxx9GTWjrL5Wh7Qx/ydxKhwUrlSFeLIn+ZaHPF+h0siVBkAQSuy6CADyTxgQ== - -detective-typescript@^11.1.0: - version "11.2.0" - resolved "https://registry.yarnpkg.com/detective-typescript/-/detective-typescript-11.2.0.tgz#5b1450b518cb84b6cfb98ea72d5edd9660668e1b" - integrity sha512-ARFxjzizOhPqs1fYC/2NMC3N4jrQ6HvVflnXBTRqNEqJuXwyKLRr9CrJwkRcV/SnZt1sNXgsF6FPm0x57Tq0rw== - dependencies: - "@typescript-eslint/typescript-estree" "^5.62.0" - ast-module-types "^5.0.0" - node-source-walk "^6.0.2" - typescript "^5.4.4" - -diff-sequences@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2" - integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA== - -diff-sequences@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" - integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== - -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - -discord-api-types@0.37.61: - version "0.37.61" - resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.37.61.tgz#9dd8e58c624237e6f1b23be2d29579af268b8c5b" - integrity sha512-o/dXNFfhBpYHpQFdT6FWzeO7pKc838QeeZ9d91CfVAtpr5XLK4B/zYxQbYgPdoMiTDvJfzcsLW5naXgmHGDNXw== - -discord.js@^14.14.1: - version "14.14.1" - resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-14.14.1.tgz#9a2bea23bba13819705ab87612837610abce9ee3" - integrity sha512-/hUVzkIerxKHyRKopJy5xejp4MYKDPTszAnpYxzVVv4qJYf+Tkt+jnT2N29PIPschicaEEpXwF2ARrTYHYwQ5w== - dependencies: - "@discordjs/builders" "^1.7.0" - "@discordjs/collection" "1.5.3" - "@discordjs/formatters" "^0.3.3" - "@discordjs/rest" "^2.1.0" - "@discordjs/util" "^1.0.2" - "@discordjs/ws" "^1.0.2" - "@sapphire/snowflake" "3.5.1" - "@types/ws" "8.5.9" - discord-api-types "0.37.61" - fast-deep-equal "3.1.3" - lodash.snakecase "4.1.1" - tslib "2.6.2" - undici "5.27.2" - ws "8.14.2" - -doctrine@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" - integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== - dependencies: - esutils "^2.0.2" - -doctrine@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== - dependencies: - esutils "^2.0.2" - -dotenv-cli@^7.3.0: - version "7.3.0" - resolved "https://registry.yarnpkg.com/dotenv-cli/-/dotenv-cli-7.3.0.tgz#21e33e7944713001677658d68856063968edfbd2" - integrity sha512-314CA4TyK34YEJ6ntBf80eUY+t1XaFLyem1k9P0sX1gn30qThZ5qZr/ZwE318gEnzyYP9yj9HJk6SqwE0upkfw== - dependencies: - cross-spawn "^7.0.3" - dotenv "^16.3.0" - dotenv-expand "^10.0.0" - minimist "^1.2.6" - -dotenv-expand@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-10.0.0.tgz#12605d00fb0af6d0a592e6558585784032e4ef37" - integrity sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A== - -dotenv@^16.0.3: - version "16.0.3" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07" - integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ== - -dotenv@^16.3.0: - version "16.4.5" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" - integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== - -e@^0.2.3, e@^0.2.32: - version "0.2.32" - resolved "https://registry.yarnpkg.com/e/-/e-0.2.32.tgz#b15d9060c5b499b18b9631bd04fe6f34045fd64d" - integrity sha512-nvPrZCz0XWDAnZeNRca5bO+XJHsE0MfBPB4Aa3LNXVsPpZkWCdiItM4/LgnlN4bU0y8f3n+HKs193fNEy5FBrQ== - -e@^0.2.33: - version "0.2.33" - resolved "https://registry.yarnpkg.com/e/-/e-0.2.33.tgz#6158e62f3b54f786ad3cbbfe8a62dfe9dd370e17" - integrity sha512-anpW+L+S+xxSOg0/IdVDwcjxS6b63Cbg0EPhiUbvfr2xSQ9IB0BK+QOjj1ndn5RfnhOyM20AwTOt26xyw/BCLA== - -eastasianwidth@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" - integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== - -emoji-regex@^10.2.1: - version "10.2.1" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.2.1.tgz#a41c330d957191efd3d9dfe6e1e8e1e9ab048b3f" - integrity sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -emoji-regex@^9.2.2: - version "9.2.2" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" - integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== - -enhanced-resolve@^5.14.1: - version "5.16.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz#65ec88778083056cb32487faa9aef82ed0864787" - integrity sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA== - dependencies: - graceful-fs "^4.2.4" - tapable "^2.2.0" - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -es-abstract@^1.19.0, es-abstract@^1.20.4: - version "1.21.1" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.21.1.tgz#e6105a099967c08377830a0c9cb589d570dd86c6" - integrity sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg== - dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - es-set-tostringtag "^2.0.1" - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - function.prototype.name "^1.1.5" - get-intrinsic "^1.1.3" - get-symbol-description "^1.0.0" - globalthis "^1.0.3" - gopd "^1.0.1" - has "^1.0.3" - has-property-descriptors "^1.0.0" - has-proto "^1.0.1" - has-symbols "^1.0.3" - internal-slot "^1.0.4" - is-array-buffer "^3.0.1" - is-callable "^1.2.7" - is-negative-zero "^2.0.2" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.2" - is-string "^1.0.7" - is-typed-array "^1.1.10" - is-weakref "^1.0.2" - object-inspect "^1.12.2" - object-keys "^1.1.1" - object.assign "^4.1.4" - regexp.prototype.flags "^1.4.3" - safe-regex-test "^1.0.0" - string.prototype.trimend "^1.0.6" - string.prototype.trimstart "^1.0.6" - typed-array-length "^1.0.4" - unbox-primitive "^1.0.2" - which-typed-array "^1.1.9" - -es-define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" - integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== - dependencies: - get-intrinsic "^1.2.4" - -es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - -es-get-iterator@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.3.tgz#3ef87523c5d464d41084b2c3c9c214f1199763d6" - integrity sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.3" - has-symbols "^1.0.3" - is-arguments "^1.1.1" - is-map "^2.0.2" - is-set "^2.0.2" - is-string "^1.0.7" - isarray "^2.0.5" - stop-iteration-iterator "^1.0.0" - -es-set-tostringtag@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz#338d502f6f674301d710b80c8592de8a15f09cd8" - integrity sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg== - dependencies: - get-intrinsic "^1.1.3" - has "^1.0.3" - has-tostringtag "^1.0.0" - -es-shim-unscopables@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz#702e632193201e3edf8713635d083d378e510241" - integrity sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w== - dependencies: - has "^1.0.3" - -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - -esbuild@^0.20.1, esbuild@^0.20.2, esbuild@~0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.20.2.tgz#9d6b2386561766ee6b5a55196c6d766d28c87ea1" - integrity sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g== - optionalDependencies: - "@esbuild/aix-ppc64" "0.20.2" - "@esbuild/android-arm" "0.20.2" - "@esbuild/android-arm64" "0.20.2" - "@esbuild/android-x64" "0.20.2" - "@esbuild/darwin-arm64" "0.20.2" - "@esbuild/darwin-x64" "0.20.2" - "@esbuild/freebsd-arm64" "0.20.2" - "@esbuild/freebsd-x64" "0.20.2" - "@esbuild/linux-arm" "0.20.2" - "@esbuild/linux-arm64" "0.20.2" - "@esbuild/linux-ia32" "0.20.2" - "@esbuild/linux-loong64" "0.20.2" - "@esbuild/linux-mips64el" "0.20.2" - "@esbuild/linux-ppc64" "0.20.2" - "@esbuild/linux-riscv64" "0.20.2" - "@esbuild/linux-s390x" "0.20.2" - "@esbuild/linux-x64" "0.20.2" - "@esbuild/netbsd-x64" "0.20.2" - "@esbuild/openbsd-x64" "0.20.2" - "@esbuild/sunos-x64" "0.20.2" - "@esbuild/win32-arm64" "0.20.2" - "@esbuild/win32-ia32" "0.20.2" - "@esbuild/win32-x64" "0.20.2" - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== - -escape-latex@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/escape-latex/-/escape-latex-1.2.0.tgz#07c03818cf7dac250cce517f4fda1b001ef2bca1" - integrity sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw== - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= - -escape-string-regexp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" - integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== - -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -escodegen@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" - integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== - dependencies: - esprima "^4.0.1" - estraverse "^5.2.0" - esutils "^2.0.2" - optionator "^0.8.1" - optionalDependencies: - source-map "~0.6.1" - -eslint-config-prettier@^8.5.0: - version "8.6.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.6.0.tgz#dec1d29ab728f4fa63061774e1672ac4e363d207" - integrity sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA== - -eslint-import-resolver-node@^0.3.7: - version "0.3.7" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz#83b375187d412324a1963d84fa664377a23eb4d7" - integrity sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA== - dependencies: - debug "^3.2.7" - is-core-module "^2.11.0" - resolve "^1.22.1" - -eslint-module-utils@^2.7.4: - version "2.7.4" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz#4f3e41116aaf13a20792261e61d3a2e7e0583974" - integrity sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA== - dependencies: - debug "^3.2.7" - -eslint-plugin-import@^2.26.0: - version "2.27.5" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz#876a6d03f52608a3e5bb439c2550588e51dd6c65" - integrity sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow== - dependencies: - array-includes "^3.1.6" - array.prototype.flat "^1.3.1" - array.prototype.flatmap "^1.3.1" - debug "^3.2.7" - doctrine "^2.1.0" - eslint-import-resolver-node "^0.3.7" - eslint-module-utils "^2.7.4" - has "^1.0.3" - is-core-module "^2.11.0" - is-glob "^4.0.3" - minimatch "^3.1.2" - object.values "^1.1.6" - resolve "^1.22.1" - semver "^6.3.0" - tsconfig-paths "^3.14.1" - -eslint-plugin-prettier@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b" - integrity sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ== - dependencies: - prettier-linter-helpers "^1.0.0" - -eslint-plugin-simple-import-sort@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-8.0.0.tgz#9d9a2372b0606e999ea841b10458a370a6ccc160" - integrity sha512-bXgJQ+lqhtQBCuWY/FUWdB27j4+lqcvXv5rUARkzbeWLwea+S5eBZEQrhnO+WgX3ZoJHVj0cn943iyXwByHHQw== - -eslint-plugin-unicorn@^44.0.2: - version "44.0.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-44.0.2.tgz#6324a001c0a5e2ac00fb51b30db27d14c6c36ab3" - integrity sha512-GLIDX1wmeEqpGaKcnMcqRvMVsoabeF0Ton0EX4Th5u6Kmf7RM9WBl705AXFEsns56ESkEs0uyelLuUTvz9Tr0w== - dependencies: - "@babel/helper-validator-identifier" "^7.19.1" - ci-info "^3.4.0" - clean-regexp "^1.0.0" - eslint-utils "^3.0.0" - esquery "^1.4.0" - indent-string "^4.0.0" - is-builtin-module "^3.2.0" - lodash "^4.17.21" - pluralize "^8.0.0" - read-pkg-up "^7.0.1" - regexp-tree "^0.1.24" - safe-regex "^2.1.1" - semver "^7.3.7" - strip-indent "^3.0.0" - -eslint-scope@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - -eslint-scope@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" - integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== - dependencies: - esrecurse "^4.3.0" - estraverse "^5.2.0" - -eslint-scope@^7.2.2: - version "7.2.2" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" - integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== - dependencies: - esrecurse "^4.3.0" - estraverse "^5.2.0" - -eslint-utils@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" - integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== - dependencies: - eslint-visitor-keys "^2.0.0" - -eslint-visitor-keys@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" - integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== - -eslint-visitor-keys@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" - integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== - -eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: - version "3.4.3" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" - integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== - -eslint@^8.33.0: - version "8.33.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.33.0.tgz#02f110f32998cb598c6461f24f4d306e41ca33d7" - integrity sha512-WjOpFQgKK8VrCnAtl8We0SUOy/oVZ5NHykyMiagV1M9r8IFpIJX7DduK6n1mpfhlG7T1NLWm2SuD8QB7KFySaA== - dependencies: - "@eslint/eslintrc" "^1.4.1" - "@humanwhocodes/config-array" "^0.11.8" - "@humanwhocodes/module-importer" "^1.0.1" - "@nodelib/fs.walk" "^1.2.8" - ajv "^6.10.0" - chalk "^4.0.0" - cross-spawn "^7.0.2" - debug "^4.3.2" - doctrine "^3.0.0" - escape-string-regexp "^4.0.0" - eslint-scope "^7.1.1" - eslint-utils "^3.0.0" - eslint-visitor-keys "^3.3.0" - espree "^9.4.0" - esquery "^1.4.0" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" - find-up "^5.0.0" - glob-parent "^6.0.2" - globals "^13.19.0" - grapheme-splitter "^1.0.4" - ignore "^5.2.0" - import-fresh "^3.0.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - is-path-inside "^3.0.3" - js-sdsl "^4.1.4" - js-yaml "^4.1.0" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" - lodash.merge "^4.6.2" - minimatch "^3.1.2" - natural-compare "^1.4.0" - optionator "^0.9.1" - regexpp "^3.2.0" - strip-ansi "^6.0.1" - strip-json-comments "^3.1.0" - text-table "^0.2.0" - -eslint@^8.36.0: - version "8.57.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" - integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== - dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@eslint-community/regexpp" "^4.6.1" - "@eslint/eslintrc" "^2.1.4" - "@eslint/js" "8.57.0" - "@humanwhocodes/config-array" "^0.11.14" - "@humanwhocodes/module-importer" "^1.0.1" - "@nodelib/fs.walk" "^1.2.8" - "@ungap/structured-clone" "^1.2.0" - ajv "^6.12.4" - chalk "^4.0.0" - cross-spawn "^7.0.2" - debug "^4.3.2" - doctrine "^3.0.0" - escape-string-regexp "^4.0.0" - eslint-scope "^7.2.2" - eslint-visitor-keys "^3.4.3" - espree "^9.6.1" - esquery "^1.4.2" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" - find-up "^5.0.0" - glob-parent "^6.0.2" - globals "^13.19.0" - graphemer "^1.4.0" - ignore "^5.2.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - is-path-inside "^3.0.3" - js-yaml "^4.1.0" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" - lodash.merge "^4.6.2" - minimatch "^3.1.2" - natural-compare "^1.4.0" - optionator "^0.9.3" - strip-ansi "^6.0.1" - text-table "^0.2.0" - -espree@^9.4.0: - version "9.4.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.4.1.tgz#51d6092615567a2c2cff7833445e37c28c0065bd" - integrity sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg== - dependencies: - acorn "^8.8.0" - acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.3.0" - -espree@^9.6.0, espree@^9.6.1: - version "9.6.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" - integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== - dependencies: - acorn "^8.9.0" - acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.4.1" - -esprima@^4.0.0, esprima@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esquery@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" - integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== - dependencies: - estraverse "^5.1.0" - -esquery@^1.4.2: - version "1.5.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" - integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== - dependencies: - estraverse "^5.1.0" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.1.0, estraverse@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" - integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== - -estree-walker@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d" - integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== - dependencies: - "@types/estree" "^1.0.0" - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -event-target-shim@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" - integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== - -eventemitter3@^4.0.4: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - -events@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" - integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== - -execa@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c" - integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^8.0.1" - human-signals "^5.0.0" - is-stream "^3.0.0" - merge-stream "^2.0.0" - npm-run-path "^5.1.0" - onetime "^6.0.0" - signal-exit "^4.1.0" - strip-final-newline "^3.0.0" - -expect@^29.0.0: - version "29.6.2" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.6.2.tgz#7b08e83eba18ddc4a2cf62b5f2d1918f5cd84521" - integrity sha512-iAErsLxJ8C+S02QbLAwgSGSezLQK+XXRDt8IuFXFpwCNw2ECmzZSmjKcCaFVp5VRMk+WAvz6h6jokzEzBFZEuA== - dependencies: - "@jest/expect-utils" "^29.6.2" - "@types/node" "*" - jest-get-type "^29.4.3" - jest-matcher-utils "^29.6.2" - jest-message-util "^29.6.2" - jest-util "^29.6.2" - -fast-check@^3.18.0: - version "3.18.0" - resolved "https://registry.yarnpkg.com/fast-check/-/fast-check-3.18.0.tgz#025c137cb1ad3f229af7d1f913f4aa2e075fa0c5" - integrity sha512-/951xaT0kA40w0GXRsZXEwSTE7LugjZtSA/8vPgFkiPQ8wNp8tRvqWuNDHBgLxJYXtsK11e/7Q4ObkKW5BdTFQ== - dependencies: - pure-rand "^6.1.0" - -fast-content-type-parse@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fast-content-type-parse/-/fast-content-type-parse-1.0.0.tgz#cddce00df7d7efb3727d375a598e4904bfcb751c" - integrity sha512-Xbc4XcysUXcsP5aHUU7Nq3OwvHq97C+WnbkeIefpeYLX+ryzFJlU6OStFJhs6Ol0LkUGpcK+wL0JwfM+FCU5IA== - -fast-decode-uri-component@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz#46f8b6c22b30ff7a81357d4f59abfae938202543" - integrity sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg== - -fast-deep-equal@3.1.3, fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-diff@^1.1.2: - version "1.2.0" - resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" - integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== - -fast-glob@^3.2.9: - version "3.2.12" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" - integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-json-stringify@^5.0.0: - version "5.6.2" - resolved "https://registry.yarnpkg.com/fast-json-stringify/-/fast-json-stringify-5.6.2.tgz#1ea6c2b8d0f27f297f682d1af039398a603af507" - integrity sha512-F6xkRrXvtGbAiDSEI5Rk7qk2P63Y9kc8bO6Dnsd3Rt6sBNr2QxNFWs0JbKftgiyOfGxnJaRoHe4SizCTqeAyrA== - dependencies: - "@fastify/deepmerge" "^1.0.0" - ajv "^8.10.0" - ajv-formats "^2.1.1" - fast-deep-equal "^3.1.3" - fast-uri "^2.1.0" - rfdc "^1.2.0" - -fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= - -fast-querystring@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/fast-querystring/-/fast-querystring-1.1.1.tgz#f4c56ef56b1a954880cfd8c01b83f9e1a3d3fda2" - integrity sha512-qR2r+e3HvhEFmpdHMv//U8FnFlnYjaC6QKDuaXALDkw2kvHO8WDjxH+f/rHGR4Me4pnk8p9JAkRNTjYHAKRn2Q== - dependencies: - fast-decode-uri-component "^1.0.1" - -fast-redact@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.1.2.tgz#d58e69e9084ce9fa4c1a6fa98a3e1ecf5d7839aa" - integrity sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw== - -fast-uri@^2.0.0, fast-uri@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-2.2.0.tgz#519a0f849bef714aad10e9753d69d8f758f7445a" - integrity sha512-cIusKBIt/R/oI6z/1nyfe2FvGKVTohVRfvkOhvx0nCEW+xf5NoCXjAHcWp93uOUBchzYcsvPlrapAdX1uW+YGg== - -fastify-plugin@^4.0.0, fastify-plugin@^4.2.1: - version "4.5.0" - resolved "https://registry.yarnpkg.com/fastify-plugin/-/fastify-plugin-4.5.0.tgz#8b853923a0bba6ab6921bb8f35b81224e6988d91" - integrity sha512-79ak0JxddO0utAXAQ5ccKhvs6vX2MGyHHMMsmZkBANrq3hXc1CHzvNPHOcvTsVMEPl5I+NT+RO4YKMGehOfSIg== - -fastify-raw-body@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/fastify-raw-body/-/fastify-raw-body-4.2.0.tgz#62a52fbdfd17fe5276d3a907baccab992c80f051" - integrity sha512-3CUeuNYq1g8ur5Po/Wa8kSmbTSpDhvk5X8scoXVwc9zvhVZxNL1chGp21ATThMo6vmrZmNPxOO+/LhkRU7nMSQ== - dependencies: - fastify-plugin "^4.0.0" - raw-body "^2.5.1" - secure-json-parse "^2.4.0" - -fastify@^4.14.1: - version "4.14.1" - resolved "https://registry.yarnpkg.com/fastify/-/fastify-4.14.1.tgz#be1b27a13910c74ecb8625de4fa42feab9703259" - integrity sha512-yjrDeXe77j9gRlSV2UJry8mcFWbD0NQ5JYjnPi4tkFjHZVaG3/BD5wxOmRzGnHPC0YvaBJ0XWrIfFPl2IHRa1w== - dependencies: - "@fastify/ajv-compiler" "^3.5.0" - "@fastify/error" "^3.0.0" - "@fastify/fast-json-stringify-compiler" "^4.1.0" - abstract-logging "^2.0.1" - avvio "^8.2.0" - fast-content-type-parse "^1.0.0" - find-my-way "^7.3.0" - light-my-request "^5.6.1" - pino "^8.5.0" - process-warning "^2.0.0" - proxy-addr "^2.0.7" - rfdc "^1.3.0" - secure-json-parse "^2.5.0" - semver "^7.3.7" - tiny-lru "^10.0.0" - -fastq@^1.6.0, fastq@^1.6.1: - version "1.11.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.1.tgz#5d8175aae17db61947f8b162cfc7f63264d22807" - integrity sha512-HOnr8Mc60eNYl1gzwp6r5RoUyAn5/glBolUzP/Ez6IFVPMPirxn/9phgL6zhOtaTy7ISwPvQ+wT+hfcRZh/bzw== - dependencies: - reusify "^1.0.4" - -file-entry-cache@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" - integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== - dependencies: - flat-cache "^3.0.4" - -filing-cabinet@^4.1.6: - version "4.2.0" - resolved "https://registry.yarnpkg.com/filing-cabinet/-/filing-cabinet-4.2.0.tgz#bd81241edce6e0c051882bef7b69ffa4c017baf9" - integrity sha512-YZ21ryzRcyqxpyKggdYSoXx//d3sCJzM3lsYoaeg/FyXdADGJrUl+BW1KIglaVLJN5BBcMtWylkygY8zBp2MrQ== - dependencies: - app-module-path "^2.2.0" - commander "^10.0.1" - enhanced-resolve "^5.14.1" - is-relative-path "^1.0.2" - module-definition "^5.0.1" - module-lookup-amd "^8.0.5" - resolve "^1.22.3" - resolve-dependency-path "^3.0.2" - sass-lookup "^5.0.1" - stylus-lookup "^5.0.1" - tsconfig-paths "^4.2.0" - typescript "^5.0.4" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -find-my-way@^7.3.0: - version "7.6.0" - resolved "https://registry.yarnpkg.com/find-my-way/-/find-my-way-7.6.0.tgz#f1e271fd1aafe87e87860662f9940124274f73c7" - integrity sha512-H7berWdHJ+5CNVr4ilLWPai4ml7Y2qAsxjw3pfeBxPigZmaDTzF0wjJLj90xRCmGcWYcyt050yN+34OZDJm1eQ== - dependencies: - fast-deep-equal "^3.1.3" - fast-querystring "^1.0.0" - safe-regex2 "^2.0.0" - -find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -flat-cache@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" - integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== - dependencies: - flatted "^3.1.0" - rimraf "^3.0.2" - -flatted@^3.1.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.1.tgz#bbef080d95fca6709362c73044a1634f7c6e7d05" - integrity sha512-OMQjaErSFHmHqZe+PSidH5n8j3O0F2DdnVh8JB4j4eUQ2k6KvB0qGfrKIhapvez5JerBbmWkaLYUYWISaESoXg== - -for-each@^0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" - integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== - dependencies: - is-callable "^1.1.3" - -foreground-child@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d" - integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg== - dependencies: - cross-spawn "^7.0.0" - signal-exit "^4.0.1" - -form-data@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" - integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - -forwarded@0.2.0, forwarded@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - -fraction.js@4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.4.tgz#b2bac8249a610c3396106da97c5a71da75b94b1c" - integrity sha512-pwiTgt0Q7t+GHZA4yaLjObx4vXmmdcS0iSJ19o8d/goUGgItX9UZWKWNnLHehxviD8wU2IWRsnR8cD5+yOJP2Q== - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -fsevents@~2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== - -fsevents@~2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - -function.prototype.name@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621" - integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.0" - functions-have-names "^1.2.2" - -functions-have-names@^1.2.2, functions-have-names@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" - integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== - -get-amd-module-type@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/get-amd-module-type/-/get-amd-module-type-5.0.1.tgz#bef38ea3674e1aa1bda9c59c8b0da598582f73f2" - integrity sha512-jb65zDeHyDjFR1loOVk0HQGM5WNwoGB8aLWy3LKCieMKol0/ProHkhO2X1JxojuN10vbz1qNn09MJ7tNp7qMzw== - dependencies: - ast-module-types "^5.0.0" - node-source-walk "^6.0.1" - -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-func-name@^2.0.0, get-func-name@^2.0.1, get-func-name@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" - integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== - -get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f" - integrity sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.3" - -get-intrinsic@^1.2.0, get-intrinsic@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" - integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-proto "^1.0.1" - has-symbols "^1.0.3" - -get-intrinsic@^1.2.2, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" - integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - has-proto "^1.0.1" - has-symbols "^1.0.3" - hasown "^2.0.0" - -get-own-enumerable-property-symbols@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" - integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== - -get-stdin@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398" - integrity sha512-jZV7n6jGE3Gt7fgSTJoz91Ak5MuTLwMwkoYdjxuJ/AmjIsE1UC03y/IWkZCQGEvVNS9qoRNwy5BCqxImv0FVeA== - -get-stream@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2" - integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA== - -get-symbol-description@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" - integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.1" - -get-tsconfig@^4.7.3: - version "4.7.3" - resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.7.3.tgz#0498163d98f7b58484dd4906999c0c9d5f103f83" - integrity sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg== - dependencies: - resolve-pkg-maps "^1.0.0" - -glob-parent@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob-parent@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" - integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== - dependencies: - is-glob "^4.0.3" - -glob@^10.3.7: - version "10.3.12" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.12.tgz#3a65c363c2e9998d220338e88a5f6ac97302960b" - integrity sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg== - dependencies: - foreground-child "^3.1.0" - jackspeak "^2.3.6" - minimatch "^9.0.1" - minipass "^7.0.4" - path-scurry "^1.10.2" - -glob@^7.1.3, glob@^7.1.4, glob@^7.2.3: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - -globals@^13.19.0: - version "13.20.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82" - integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ== - dependencies: - type-fest "^0.20.2" - -globalthis@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" - integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== - dependencies: - define-properties "^1.1.3" - -globby@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" - integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.2.9" - ignore "^5.2.0" - merge2 "^1.4.1" - slash "^3.0.0" - -glur@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/glur/-/glur-1.1.2.tgz#f20ea36db103bfc292343921f1f91e83c3467689" - integrity sha512-l+8esYHTKOx2G/Aao4lEQ0bnHWg4fWtJbVoZZT9Knxi01pB8C80BR85nONLFwkkQoFRCmXY+BUcGZN3yZ2QsRA== - -gonzales-pe@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.3.0.tgz#fe9dec5f3c557eead09ff868c65826be54d067b3" - integrity sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ== - dependencies: - minimist "^1.2.5" - -gopd@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" - integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== - dependencies: - get-intrinsic "^1.1.3" - -graceful-fs@^4.2.4: - version "4.2.10" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" - integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== - -graceful-fs@^4.2.9: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -grapheme-splitter@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" - integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== - -graphemer@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" - integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== - -has-bigints@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" - integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-property-descriptors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" - integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== - dependencies: - get-intrinsic "^1.1.1" - -has-property-descriptors@^1.0.1, has-property-descriptors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" - integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== - dependencies: - es-define-property "^1.0.0" - -has-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" - integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== - -has-symbols@^1.0.2, has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== - -has-tostringtag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" - integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== - dependencies: - has-symbols "^1.0.2" - -has-tostringtag@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" - integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== - dependencies: - has-symbols "^1.0.3" - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -hasown@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.1.tgz#26f48f039de2c0f8d3356c223fb8d50253519faa" - integrity sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA== - dependencies: - function-bind "^1.1.2" - -he@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - -helmet@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/helmet/-/helmet-6.0.1.tgz#52ec353638b2e87f14fe079d142b368ac11e79a4" - integrity sha512-8wo+VdQhTMVBMCITYZaGTbE4lvlthelPYSvoyNvk4RECTmrVjMerp9RfUOQXZWLvCcAn1pKj7ZRxK4lI9Alrcw== - -hosted-git-info@^2.1.4: - version "2.8.9" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" - integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== - -html-escaper@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" - integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== - -http-errors@2.0.0, http-errors@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== - dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" - -human-signals@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" - integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== - -iconv-lite@0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -ieee754@^1.1.13, ieee754@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - -ignore@^5.2.0: - version "5.2.4" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" - integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== - -immediate@~3.0.5: - version "3.0.6" - resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" - integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== - -import-fresh@^3.0.0, import-fresh@^3.2.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= - -indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" - integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -ini@~1.3.0: - version "1.3.8" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" - integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== - -internal-slot@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.4.tgz#8551e7baf74a7a6ba5f749cfb16aa60722f0d6f3" - integrity sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ== - dependencies: - get-intrinsic "^1.1.3" - has "^1.0.3" - side-channel "^1.0.4" - -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -is-arguments@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" - integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-array-buffer@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.1.tgz#deb1db4fcae48308d54ef2442706c0393997052a" - integrity sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.3" - is-typed-array "^1.1.10" - -is-array-buffer@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe" - integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.2.0" - is-typed-array "^1.1.10" - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= - -is-bigint@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.2.tgz#ffb381442503235ad245ea89e45b3dbff040ee5a" - integrity sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA== - -is-boolean-object@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.1.tgz#3c0878f035cb821228d350d2e1e36719716a3de8" - integrity sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng== - dependencies: - call-bind "^1.0.2" - -is-builtin-module@^3.2.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169" - integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A== - dependencies: - builtin-modules "^3.3.0" - -is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" - integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== - -is-core-module@^2.11.0, is-core-module@^2.9.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" - integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== - dependencies: - has "^1.0.3" - -is-core-module@^2.13.0: - version "2.13.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" - integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== - dependencies: - hasown "^2.0.0" - -is-date-object@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.4.tgz#550cfcc03afada05eea3dd30981c7b09551f73e5" - integrity sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A== - -is-date-object@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" - integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== - dependencies: - has-tostringtag "^1.0.0" - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-interactive@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" - integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== - -is-map@^2.0.1, is-map@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" - integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== - -is-negative-zero@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" - integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== - -is-number-object@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.5.tgz#6edfaeed7950cff19afedce9fbfca9ee6dd289eb" - integrity sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw== - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-obj@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" - integrity sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg== - -is-path-inside@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" - integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== - -is-plain-object@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" - integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== - -is-regex@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" - integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-regexp@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" - integrity sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA== - -is-relative-path@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-relative-path/-/is-relative-path-1.0.2.tgz#091b46a0d67c1ed0fe85f1f8cfdde006bb251d46" - integrity sha512-i1h+y50g+0hRbBD+dbnInl3JlJ702aar58snAeX+MxBAPvzXGej7sYoPMhlnykabt0ZzCJNBEyzMlekuQZN7fA== - -is-set@^2.0.1, is-set@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" - integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== - -is-shared-array-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" - integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== - dependencies: - call-bind "^1.0.2" - -is-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" - integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== - -is-string@^1.0.5, is-string@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" - integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== - dependencies: - has-tostringtag "^1.0.0" - -is-symbol@^1.0.2, is-symbol@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" - integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== - dependencies: - has-symbols "^1.0.2" - -is-typed-array@^1.1.10, is-typed-array@^1.1.9: - version "1.1.10" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.10.tgz#36a5b5cb4189b575d1a3e4b08536bfb485801e3f" - integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A== - dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - for-each "^0.3.3" - gopd "^1.0.1" - has-tostringtag "^1.0.0" - -is-unicode-supported@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" - integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== - -is-url-superb@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-url-superb/-/is-url-superb-4.0.0.tgz#b54d1d2499bb16792748ac967aa3ecb41a33a8c2" - integrity sha512-GI+WjezhPPcbM+tqE9LnmsY5qqjwHzTvjJ36wxYX5ujNXefSUJ/T17r5bqDV8yLhcgB59KTPNOc9O9cmHTPWsA== - -is-url@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52" - integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww== - -is-weakmap@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" - integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA== - -is-weakref@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" - integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== - dependencies: - call-bind "^1.0.2" - -is-weakset@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.2.tgz#4569d67a747a1ce5a994dfd4ef6dcea76e7c0a1d" - integrity sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.1" - -isarray@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -istanbul-lib-coverage@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" - integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== - -istanbul-lib-coverage@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" - integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== - -istanbul-lib-report@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" - integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== - dependencies: - istanbul-lib-coverage "^3.0.0" - make-dir "^3.0.0" - supports-color "^7.1.0" - -istanbul-lib-report@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" - integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== - dependencies: - istanbul-lib-coverage "^3.0.0" - make-dir "^4.0.0" - supports-color "^7.1.0" - -istanbul-lib-source-maps@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" - integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== - dependencies: - debug "^4.1.1" - istanbul-lib-coverage "^3.0.0" - source-map "^0.6.1" - -istanbul-reports@^3.1.6: - version "3.1.7" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" - integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== - dependencies: - html-escaper "^2.0.0" - istanbul-lib-report "^3.0.0" - -jackspeak@^2.3.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8" - integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ== - dependencies: - "@isaacs/cliui" "^8.0.2" - optionalDependencies: - "@pkgjs/parseargs" "^0.11.0" - -javascript-natural-sort@^0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59" - integrity sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw== - -jest-diff@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.6.2.tgz#c36001e5543e82a0805051d3ceac32e6825c1c46" - integrity sha512-t+ST7CB9GX5F2xKwhwCf0TAR17uNDiaPTZnVymP9lw0lssa9vG+AFyDZoeIHStU3WowFFwT+ky+er0WVl2yGhA== - dependencies: - chalk "^4.0.0" - diff-sequences "^29.4.3" - jest-get-type "^29.4.3" - pretty-format "^29.6.2" - -jest-get-type@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.3.tgz#1ab7a5207c995161100b5187159ca82dd48b3dd5" - integrity sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg== - -jest-image-snapshot@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/jest-image-snapshot/-/jest-image-snapshot-6.2.0.tgz#b6bfc3e1585ec14b2b0595daf537ae1b4d7f8d13" - integrity sha512-9mTHBKiiSIZ26csbLmjKyN+SrVypM93S5y+jULCvn6YItgepvcrJIKGNeSyt9d2EZiutOroLs/UjtrWiBzpHbA== - dependencies: - chalk "^4.0.0" - get-stdin "^5.0.1" - glur "^1.1.2" - lodash "^4.17.4" - pixelmatch "^5.1.0" - pngjs "^3.4.0" - rimraf "^2.6.2" - ssim.js "^3.1.1" - -jest-matcher-utils@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.6.2.tgz#39de0be2baca7a64eacb27291f0bd834fea3a535" - integrity sha512-4LiAk3hSSobtomeIAzFTe+N8kL6z0JtF3n6I4fg29iIW7tt99R7ZcIFW34QkX+DuVrf+CUe6wuVOpm7ZKFJzZQ== - dependencies: - chalk "^4.0.0" - jest-diff "^29.6.2" - jest-get-type "^29.4.3" - pretty-format "^29.6.2" - -jest-message-util@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.6.2.tgz#af7adc2209c552f3f5ae31e77cf0a261f23dc2bb" - integrity sha512-vnIGYEjoPSuRqV8W9t+Wow95SDp6KPX2Uf7EoeG9G99J2OVh7OSwpS4B6J0NfpEIpfkBNHlBZpA2rblEuEFhZQ== - dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.6.1" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - micromatch "^4.0.4" - pretty-format "^29.6.2" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-util@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.6.2.tgz#8a052df8fff2eebe446769fd88814521a517664d" - integrity sha512-3eX1qb6L88lJNCFlEADKOkjpXJQyZRiavX1INZ4tRnrBVr2COd3RgcTLyUiEXMNBlDU/cgYq6taUS0fExrWW4w== - dependencies: - "@jest/types" "^29.6.1" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - -js-sdsl@^4.1.4: - version "4.3.0" - resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.3.0.tgz#aeefe32a451f7af88425b11fdb5f58c90ae1d711" - integrity sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ== - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-tokens@^8.0.2: - version "8.0.3" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-8.0.3.tgz#1c407ec905643603b38b6be6977300406ec48775" - integrity sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw== - -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - -json-parse-even-better-errors@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema-traverse@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" - integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== - -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= - -json5@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" - integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== - dependencies: - minimist "^1.2.0" - -json5@^2.2.2: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - -jsonc-parser@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" - integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== - -levn@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" - integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== - dependencies: - prelude-ls "^1.2.1" - type-check "~0.4.0" - -levn@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= - dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" - -lie@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e" - integrity sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw== - dependencies: - immediate "~3.0.5" - -light-my-request@^5.6.1: - version "5.9.1" - resolved "https://registry.yarnpkg.com/light-my-request/-/light-my-request-5.9.1.tgz#076f8d4cc4639408cc48381d4f2860212d469d4b" - integrity sha512-UT7pUk8jNCR1wR7w3iWfIjx32DiB2f3hFdQSOwy3/EPQ3n3VocyipUxcyRZR0ahoev+fky69uA+GejPa9KuHKg== - dependencies: - cookie "^0.5.0" - process-warning "^2.0.0" - set-cookie-parser "^2.4.1" - -lines-and-columns@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" - integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= - -local-pkg@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-0.5.0.tgz#093d25a346bae59a99f80e75f6e9d36d7e8c925c" - integrity sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg== - dependencies: - mlly "^1.4.2" - pkg-types "^1.0.3" - -localforage@^1.8.1: - version "1.10.0" - resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.10.0.tgz#5c465dc5f62b2807c3a84c0c6a1b1b3212781dd4" - integrity sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg== - dependencies: - lie "3.1.1" - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - -lodash.merge@^4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== - -lodash.snakecase@4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d" - integrity sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw== - -lodash@^4.17.21, lodash@^4.17.4: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -log-symbols@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" - integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== - dependencies: - chalk "^4.1.0" - is-unicode-supported "^0.1.0" - -loupe@^2.3.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.6.tgz#76e4af498103c532d1ecc9be102036a21f787b53" - integrity sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA== - dependencies: - get-func-name "^2.0.0" - -loupe@^2.3.7: - version "2.3.7" - resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.7.tgz#6e69b7d4db7d3ab436328013d37d1c8c3540c697" - integrity sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA== - dependencies: - get-func-name "^2.0.1" - -lru-cache@^10.2.0: - version "10.2.2" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.2.tgz#48206bc114c1252940c41b25b41af5b545aca878" - integrity sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ== - -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - -lru-cache@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-8.0.0.tgz#152bf98daafa6a46a5905694fccd6f5f7f6095fe" - integrity sha512-pMu1vSJIwJPS/YuMJAJFjvKA2OC7rvgKqJHr90JmZ1kv/hO+MuzqHRSWqyn730vlOwc1Bx/c8+3izTGzmKyXNQ== - -madge@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/madge/-/madge-7.0.0.tgz#64b1762033b0f969caa7e5853004b6850e8430bb" - integrity sha512-x9eHkBWoCJ2B8yGesWf8LRucarkbH5P3lazqgvmxe4xn5U2Meyfu906iG9mBB1RnY/f4D+gtELWdiz1k6+jAZA== - dependencies: - chalk "^4.1.2" - commander "^7.2.0" - commondir "^1.0.1" - debug "^4.3.4" - dependency-tree "^10.0.9" - ora "^5.4.1" - pluralize "^8.0.0" - precinct "^11.0.5" - pretty-ms "^7.0.1" - rc "^1.2.8" - stream-to-array "^2.3.0" - ts-graphviz "^1.8.1" - walkdir "^0.4.1" - -magic-bytes.js@^1.5.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/magic-bytes.js/-/magic-bytes.js-1.10.0.tgz#c41cf4bc2f802992b05e64962411c9dd44fdef92" - integrity sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ== - -magic-string@^0.30.5: - version "0.30.7" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.7.tgz#0cecd0527d473298679da95a2d7aeb8c64048505" - integrity sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA== - dependencies: - "@jridgewell/sourcemap-codec" "^1.4.15" - -magicast@^0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/magicast/-/magicast-0.3.3.tgz#a15760f982deec9dabc5f314e318d7c6bddcb27b" - integrity sha512-ZbrP1Qxnpoes8sz47AM0z08U+jW6TyRgZzcWy3Ma3vDhJttwMwAFDMMQFobwdBxByBD46JYmxRzeF7w2+wJEuw== - dependencies: - "@babel/parser" "^7.23.6" - "@babel/types" "^7.23.6" - source-map-js "^1.0.2" - -mahoji@^0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/mahoji/-/mahoji-0.0.7.tgz#bb99bc2e32bc934534328d054698625c57af61ee" - integrity sha512-N5R29v9ltVwYgMbD8xa1YQf60B0Jhgzqrj2Tz5m/b7XbbCy10pQYbRuF9dwcemhNJqJWmFhEGj/sFtZXTgq+gQ== - -make-dir@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" - integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== - dependencies: - semver "^6.0.0" - -make-dir@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" - integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== - dependencies: - semver "^7.5.3" - -math-expression-evaluator@^1.3.14: - version "1.3.14" - resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.3.14.tgz#0ebeaccf65fea0f6f5a626f88df41814e5fcd9bf" - integrity sha512-M6AMrvq9bO8uL42KvQHPA2/SbAobA0R7gviUmPrcTcGfdwpaLitz4q2Euzx2lP9Oy88vxK3HOrsISgSwKsYS4A== - -mathjs@^12.4.2: - version "12.4.2" - resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-12.4.2.tgz#27fe9ac2a534b40c25c272585dfc358a90597bb1" - integrity sha512-lW14EzwAFgbNN7AZikxplmhs7wiXDhMphBOGCA3KS6T29ECEkHJsBtbEW5cnCz7sXtl4nDyvTdR+DqVsZyiiEw== - dependencies: - "@babel/runtime" "^7.24.4" - complex.js "^2.1.1" - decimal.js "^10.4.3" - escape-latex "^1.2.0" - fraction.js "4.3.4" - javascript-natural-sort "^0.7.1" - seedrandom "^3.0.5" - tiny-emitter "^2.1.0" - typed-function "^4.1.1" - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -merge2@^1.3.0, merge2@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -micromatch@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" - integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== - dependencies: - braces "^3.0.1" - picomatch "^2.2.3" - -mime-db@1.48.0: - version "1.48.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.48.0.tgz#e35b31045dd7eada3aaad537ed88a33afbef2d1d" - integrity sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ== - -mime-types@^2.1.12, mime-types@~2.1.24: - version "2.1.31" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.31.tgz#a00d76b74317c61f9c2db2218b8e9f8e9c5c9e6b" - integrity sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg== - dependencies: - mime-db "1.48.0" - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -mimic-fn@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" - integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== - -min-indent@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" - integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== - -minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^9.0.1: - version "9.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51" - integrity sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw== - dependencies: - brace-expansion "^2.0.1" - -minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: - version "1.2.7" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" - integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== - -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0": - version "7.0.4" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c" - integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ== - -minipass@^7.0.4: - version "7.1.0" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.0.tgz#b545f84af94e567386770159302ca113469c80b8" - integrity sha512-oGZRv2OT1lO2UF1zUcwdTb3wqUwI0kBGTgt/T7OdSj6M6N5m3o5uPf0AIW6lVxGGoiWUR7e2AwTE+xiwK8WQig== - -mlly@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.2.1.tgz#cd50151f5712b651c5c379085157bcdff661133b" - integrity sha512-1aMEByaWgBPEbWV2BOPEMySRrzl7rIHXmQxam4DM8jVjalTQDjpN2ZKOLUrwyhfZQO7IXHml2StcHMhooDeEEQ== - dependencies: - acorn "^8.8.2" - pathe "^1.1.0" - pkg-types "^1.0.3" - ufo "^1.1.2" - -mlly@^1.4.2: - version "1.6.1" - resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.6.1.tgz#0983067dc3366d6314fc5e12712884e6978d028f" - integrity sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA== - dependencies: - acorn "^8.11.3" - pathe "^1.1.2" - pkg-types "^1.0.3" - ufo "^1.3.2" - -mnemonist@0.39.5: - version "0.39.5" - resolved "https://registry.yarnpkg.com/mnemonist/-/mnemonist-0.39.5.tgz#5850d9b30d1b2bc57cc8787e5caa40f6c3420477" - integrity sha512-FPUtkhtJ0efmEFGpU14x7jGbTB+s18LrzRL2KgoWz9YvcY3cPomz8tih01GbHwnGk/OmkOKfqd/RAQoc8Lm7DQ== - dependencies: - obliterator "^2.0.1" - -module-definition@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/module-definition/-/module-definition-5.0.1.tgz#62d1194e5d5ea6176b7dc7730f818f466aefa32f" - integrity sha512-kvw3B4G19IXk+BOXnYq/D/VeO9qfHaapMeuS7w7sNUqmGaA6hywdFHMi+VWeR9wUScXM7XjoryTffCZ5B0/8IA== - dependencies: - ast-module-types "^5.0.0" - node-source-walk "^6.0.1" - -module-lookup-amd@^8.0.5: - version "8.0.5" - resolved "https://registry.yarnpkg.com/module-lookup-amd/-/module-lookup-amd-8.0.5.tgz#aaeea41979105b49339380ca3f7d573db78c32a5" - integrity sha512-vc3rYLjDo5Frjox8NZpiyLXsNWJ5BWshztc/5KSOMzpg9k5cHH652YsJ7VKKmtM4SvaxuE9RkrYGhiSjH3Ehow== - dependencies: - commander "^10.0.1" - glob "^7.2.3" - requirejs "^2.3.6" - requirejs-config-file "^4.0.0" - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@^2.1.1, ms@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -murmurhash@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/murmurhash/-/murmurhash-2.0.1.tgz#4097720e08cf978872194ad84ea5be2dec9b610f" - integrity sha512-5vQEh3y+DG/lMPM0mCGPDnyV8chYg/g7rl6v3Gd8WMF9S429ox3Xk8qrk174kWhG767KQMqqxLD1WnGd77hiew== - -nan@^2.18.0: - version "2.18.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.18.0.tgz#26a6faae7ffbeb293a39660e88a76b82e30b7554" - integrity sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w== - -nanoid@^3.3.7: - version "3.3.7" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" - integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== - -natural-compare-lite@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" - integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= - -nice-napi@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/nice-napi/-/nice-napi-1.0.2.tgz#dc0ab5a1eac20ce548802fc5686eaa6bc654927b" - integrity sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA== - dependencies: - node-addon-api "^3.0.0" - node-gyp-build "^4.2.2" - -node-addon-api@^3.0.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" - integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== - -node-cron@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/node-cron/-/node-cron-3.0.3.tgz#c4bc7173dd96d96c50bdb51122c64415458caff2" - integrity sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A== - dependencies: - uuid "8.3.2" - -node-fetch@^2.6.1, node-fetch@^2.6.7: - version "2.6.7" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" - integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== - dependencies: - whatwg-url "^5.0.0" - -node-gyp-build@^4.2.2, node-gyp-build@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3" - integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q== - -node-source-walk@^6.0.0, node-source-walk@^6.0.1, node-source-walk@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/node-source-walk/-/node-source-walk-6.0.2.tgz#ba81bc4bc0f6f05559b084bea10be84c3f87f211" - integrity sha512-jn9vOIK/nfqoFCcpK89/VCVaLg1IHE6UVfDOzvqmANaJ/rWCTEdH8RZ1V278nv2jr36BJdyQXIAavBLXpzdlag== - dependencies: - "@babel/parser" "^7.21.8" - -normalize-package-data@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" - integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== - dependencies: - hosted-git-info "^2.1.4" - resolve "^1.10.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - -npm-run-path@^5.1.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.3.0.tgz#e23353d0ebb9317f174e93417e4a4d82d0249e9f" - integrity sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ== - dependencies: - path-key "^4.0.0" - -object-inspect@^1.12.2, object-inspect@^1.9.0: - version "1.12.3" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" - integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== - -object-is@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" - integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - -object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object.assign@^4.1.4: - version "4.1.4" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" - integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - has-symbols "^1.0.3" - object-keys "^1.1.1" - -object.values@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.6.tgz#4abbaa71eba47d63589d402856f908243eea9b1d" - integrity sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - -obliterator@^2.0.1: - version "2.0.4" - resolved "https://registry.yarnpkg.com/obliterator/-/obliterator-2.0.4.tgz#fa650e019b2d075d745e44f1effeb13a2adbe816" - integrity sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ== - -oldschooljs@^2.5.4: - version "2.5.4" - resolved "https://registry.yarnpkg.com/oldschooljs/-/oldschooljs-2.5.4.tgz#59aa85be412ac14ca7b2cda885911d16ceaac702" - integrity sha512-m3Jqmbx2pQIBAqCQn7nQlRO/VkWqHUWUTzdTiXmOHrK+NEB1fapnC1QuwQaf0sCv/fyy+FN2CV2nnDelm/315w== - dependencies: - deepmerge "^4.3.1" - e "^0.2.32" - node-fetch "^2.6.7" - -on-exit-leak-free@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz#5c703c968f7e7f851885f6459bf8a8a57edc9cc4" - integrity sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w== - -once@^1.3.0, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -onetime@^5.1.0: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -onetime@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" - integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== - dependencies: - mimic-fn "^4.0.0" - -optionator@^0.8.1: - version "0.8.3" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" - integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== - dependencies: - deep-is "~0.1.3" - fast-levenshtein "~2.0.6" - levn "~0.3.0" - prelude-ls "~1.1.2" - type-check "~0.3.2" - word-wrap "~1.2.3" - -optionator@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" - integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== - dependencies: - deep-is "^0.1.3" - fast-levenshtein "^2.0.6" - levn "^0.4.1" - prelude-ls "^1.2.1" - type-check "^0.4.0" - word-wrap "^1.2.3" - -optionator@^0.9.3: - version "0.9.4" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" - integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== - dependencies: - deep-is "^0.1.3" - fast-levenshtein "^2.0.6" - levn "^0.4.1" - prelude-ls "^1.2.1" - type-check "^0.4.0" - word-wrap "^1.2.5" - -ora@^5.4.1: - version "5.4.1" - resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" - integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== - dependencies: - bl "^4.1.0" - chalk "^4.1.0" - cli-cursor "^3.1.0" - cli-spinners "^2.5.0" - is-interactive "^1.0.0" - is-unicode-supported "^0.1.0" - log-symbols "^4.1.0" - strip-ansi "^6.0.0" - wcwidth "^1.0.1" - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-limit@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-limit@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-5.0.0.tgz#6946d5b7140b649b7a33a027d89b4c625b3a5985" - integrity sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ== - dependencies: - yocto-queue "^1.0.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -p-queue@^6.6.2: - version "6.6.2" - resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" - integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== - dependencies: - eventemitter3 "^4.0.4" - p-timeout "^3.2.0" - -p-timeout@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" - integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== - dependencies: - p-finally "^1.0.0" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -parse-json@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-even-better-errors "^2.3.0" - lines-and-columns "^1.1.6" - -parse-ms@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-2.1.0.tgz#348565a753d4391fa524029956b172cb7753097d" - integrity sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-key@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" - integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-scurry@^1.10.2: - version "1.10.2" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.2.tgz#8f6357eb1239d5fa1da8b9f70e9c080675458ba7" - integrity sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA== - dependencies: - lru-cache "^10.2.0" - minipass "^5.0.0 || ^6.0.2 || ^7.0.0" - -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - -pathe@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.0.tgz#e2e13f6c62b31a3289af4ba19886c230f295ec03" - integrity sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w== - -pathe@^1.1.1, pathe@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec" - integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ== - -pathval@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" - integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== - -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== - -picomatch@^2.2.3: - version "2.3.0" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" - integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== - -pino-abstract-transport@v1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.0.0.tgz#cc0d6955fffcadb91b7b49ef220a6cc111d48bb3" - integrity sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA== - dependencies: - readable-stream "^4.0.0" - split2 "^4.0.0" - -pino-std-serializers@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-6.0.0.tgz#4c20928a1bafca122fdc2a7a4a171ca1c5f9c526" - integrity sha512-mMMOwSKrmyl+Y12Ri2xhH1lbzQxwwpuru9VjyJpgFIH4asSj88F2csdMwN6+M5g1Ll4rmsYghHLQJw81tgZ7LQ== - -pino@^8.5.0: - version "8.11.0" - resolved "https://registry.yarnpkg.com/pino/-/pino-8.11.0.tgz#2a91f454106b13e708a66c74ebc1c2ab7ab38498" - integrity sha512-Z2eKSvlrl2rH8p5eveNUnTdd4AjJk8tAsLkHYZQKGHP4WTh2Gi1cOSOs3eWPqaj+niS3gj4UkoreoaWgF3ZWYg== - dependencies: - atomic-sleep "^1.0.0" - fast-redact "^3.1.1" - on-exit-leak-free "^2.1.0" - pino-abstract-transport v1.0.0 - pino-std-serializers "^6.0.0" - process-warning "^2.0.0" - quick-format-unescaped "^4.0.3" - real-require "^0.2.0" - safe-stable-stringify "^2.3.1" - sonic-boom "^3.1.0" - thread-stream "^2.0.0" - -piscina@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/piscina/-/piscina-4.4.0.tgz#e3af8e5721d8fad08c6ccaf8a64f9f42279efbb5" - integrity sha512-+AQduEJefrOApE4bV7KRmp3N2JnnyErlVqq4P/jmko4FPz9Z877BCccl/iB3FdrWSUkvbGV9Kan/KllJgat3Vg== - optionalDependencies: - nice-napi "^1.0.2" - -pixelmatch@^5.1.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/pixelmatch/-/pixelmatch-5.3.0.tgz#5e5321a7abedfb7962d60dbf345deda87cb9560a" - integrity sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q== - dependencies: - pngjs "^6.0.0" - -pkg-types@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.0.3.tgz#988b42ab19254c01614d13f4f65a2cfc7880f868" - integrity sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A== - dependencies: - jsonc-parser "^3.2.0" - mlly "^1.2.0" - pathe "^1.1.0" - -pluralize@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" - integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== - -pngjs@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f" - integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w== - -pngjs@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-6.0.0.tgz#ca9e5d2aa48db0228a52c419c3308e87720da821" - integrity sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg== - -possible-typed-array-names@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" - integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== - -postcss-values-parser@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/postcss-values-parser/-/postcss-values-parser-6.0.2.tgz#636edc5b86c953896f1bb0d7a7a6615df00fb76f" - integrity sha512-YLJpK0N1brcNJrs9WatuJFtHaV9q5aAOj+S4DI5S7jgHlRfm0PIbDCAFRYMQD5SHq7Fy6xsDhyutgS0QOAs0qw== - dependencies: - color-name "^1.1.4" - is-url-superb "^4.0.0" - quote-unquote "^1.0.0" - -postcss@^8.4.23, postcss@^8.4.38: - version "8.4.38" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e" - integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A== - dependencies: - nanoid "^3.3.7" - picocolors "^1.0.0" - source-map-js "^1.2.0" - -precinct@^11.0.5: - version "11.0.5" - resolved "https://registry.yarnpkg.com/precinct/-/precinct-11.0.5.tgz#3e15b3486670806f18addb54b8533e23596399ff" - integrity sha512-oHSWLC8cL/0znFhvln26D14KfCQFFn4KOLSw6hmLhd+LQ2SKt9Ljm89but76Pc7flM9Ty1TnXyrA2u16MfRV3w== - dependencies: - "@dependents/detective-less" "^4.1.0" - commander "^10.0.1" - detective-amd "^5.0.2" - detective-cjs "^5.0.1" - detective-es6 "^4.0.1" - detective-postcss "^6.1.3" - detective-sass "^5.0.3" - detective-scss "^4.0.3" - detective-stylus "^4.0.0" - detective-typescript "^11.1.0" - module-definition "^5.0.1" - node-source-walk "^6.0.2" - -prelude-ls@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" - integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== - -prelude-ls@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" - integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= - -prettier-linter-helpers@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" - integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== - dependencies: - fast-diff "^1.1.2" - -prettier@^2.7.1: - version "2.8.3" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.3.tgz#ab697b1d3dd46fb4626fbe2f543afe0cc98d8632" - integrity sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw== - -pretty-format@^29.0.0, pretty-format@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.6.2.tgz#3d5829261a8a4d89d8b9769064b29c50ed486a47" - integrity sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg== - dependencies: - "@jest/schemas" "^29.6.0" - ansi-styles "^5.0.0" - react-is "^18.0.0" - -pretty-format@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" - integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== - dependencies: - "@jest/schemas" "^29.6.3" - ansi-styles "^5.0.0" - react-is "^18.0.0" - -pretty-ms@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-7.0.1.tgz#7d903eaab281f7d8e03c66f867e239dc32fb73e8" - integrity sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q== - dependencies: - parse-ms "^2.1.0" - -printable-characters@^1.0.42: - version "1.0.42" - resolved "https://registry.yarnpkg.com/printable-characters/-/printable-characters-1.0.42.tgz#3f18e977a9bd8eb37fcc4ff5659d7be90868b3d8" - integrity sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ== - -prisma@^5.13.0: - version "5.13.0" - resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.13.0.tgz#1f06e20ccfb6038ad68869e6eacd3b346f9d0851" - integrity sha512-kGtcJaElNRAdAGsCNykFSZ7dBKpL14Cbs+VaQ8cECxQlRPDjBlMHNFYeYt0SKovAVy2Y65JXQwB3A5+zIQwnTg== - dependencies: - "@prisma/engines" "5.13.0" - -process-warning@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-2.1.0.tgz#1e60e3bfe8183033bbc1e702c2da74f099422d1a" - integrity sha512-9C20RLxrZU/rFnxWncDkuF6O999NdIf3E1ws4B0ZeY3sRVPzWBMsYDE2lxjxhiXxg464cQTgKUGm8/i6y2YGXg== - -process@^0.11.10: - version "0.11.10" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== - -proxy-addr@^2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - -punycode@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" - integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== - -pure-rand@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" - integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -quick-format-unescaped@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.3.tgz#6d6b66b8207aa2b35eef12be1421bb24c428f652" - integrity sha512-MaL/oqh02mhEo5m5J2rwsVL23Iw2PEaGVHgT2vFt8AAsr0lfvQA5dpXo9TPu0rz7tSBdUPgkbam0j/fj5ZM8yg== - -quote-unquote@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/quote-unquote/-/quote-unquote-1.0.0.tgz#67a9a77148effeaf81a4d428404a710baaac8a0b" - integrity sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg== - -random-js@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/random-js/-/random-js-2.1.0.tgz#0c03238b0a4f701f7a4c7303036ade3083d8ee14" - integrity sha512-CRUyWmnzmZBA7RZSVGq0xMqmgCyPPxbiKNLFA5ud7KenojVX2s7Gv+V7eB52beKTPGxWRnVZ7D/tCIgYJJ8vNQ== - -raw-body@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" - integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - -rc@^1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - -react-is@^18.0.0: - version "18.2.0" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" - integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== - -read-pkg-up@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" - integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== - dependencies: - find-up "^4.1.0" - read-pkg "^5.2.0" - type-fest "^0.8.1" - -read-pkg@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" - integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== - dependencies: - "@types/normalize-package-data" "^2.4.0" - normalize-package-data "^2.5.0" - parse-json "^5.0.0" - type-fest "^0.6.0" - -readable-stream@^3.4.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readable-stream@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.2.0.tgz#a7ef523d3b39e4962b0db1a1af22777b10eeca46" - integrity sha512-gJrBHsaI3lgBoGMW/jHZsQ/o/TIWiu5ENCJG1BB7fuCKzpFM8GaS2UoBVt9NO+oI+3FcrBNbUkl3ilDe09aY4A== - dependencies: - abort-controller "^3.0.0" - buffer "^6.0.3" - events "^3.3.0" - process "^0.11.10" - -real-require@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.2.0.tgz#209632dea1810be2ae063a6ac084fee7e33fba78" - integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg== - -regenerator-runtime@^0.14.0: - version "0.14.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" - integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== - -regexp-tree@^0.1.24, regexp-tree@~0.1.1: - version "0.1.24" - resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.24.tgz#3d6fa238450a4d66e5bc9c4c14bb720e2196829d" - integrity sha512-s2aEVuLhvnVJW6s/iPgEGK6R+/xngd2jNQ+xy4bXNDKxZKJH6jpPHY6kVeVv1IeLCHgswRj+Kl3ELaDjG6V1iw== - -regexp.prototype.flags@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" - integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - functions-have-names "^1.2.2" - -regexp.prototype.flags@^1.5.1: - version "1.5.2" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" - integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== - dependencies: - call-bind "^1.0.6" - define-properties "^1.2.1" - es-errors "^1.3.0" - set-function-name "^2.0.1" - -regexpp@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" - integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= - -require-from-string@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== - -requirejs-config-file@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/requirejs-config-file/-/requirejs-config-file-4.0.0.tgz#4244da5dd1f59874038cc1091d078d620abb6ebc" - integrity sha512-jnIre8cbWOyvr8a5F2KuqBnY+SDA4NXr/hzEZJG79Mxm2WiFQz2dzhC8ibtPJS7zkmBEl1mxSwp5HhC1W4qpxw== - dependencies: - esprima "^4.0.0" - stringify-object "^3.2.1" - -requirejs@^2.3.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/requirejs/-/requirejs-2.3.6.tgz#e5093d9601c2829251258c0b9445d4d19fa9e7c9" - integrity sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg== - -resolve-dependency-path@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/resolve-dependency-path/-/resolve-dependency-path-3.0.2.tgz#012816717bcbe8b846835da11af9d2beb5acef50" - integrity sha512-Tz7zfjhLfsvR39ADOSk9us4421J/1ztVBo4rWUkF38hgHK5m0OCZ3NxFVpqHRkjctnwVa15igEUHFJp8MCS7vA== - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -resolve-pkg-maps@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" - integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== - -resolve@^1.10.0, resolve@^1.22.1: - version "1.22.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" - integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== - dependencies: - is-core-module "^2.9.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -resolve@^1.22.3: - version "1.22.8" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" - integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== - dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -restore-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" - integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== - dependencies: - onetime "^5.1.0" - signal-exit "^3.0.2" - -ret@~0.2.0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.2.2.tgz#b6861782a1f4762dce43402a71eb7a283f44573c" - integrity sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ== - -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -rfdc@^1.2.0, rfdc@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" - integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== - -rimraf@^2.6.2: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -rimraf@^5.0.5: - version "5.0.5" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.5.tgz#9be65d2d6e683447d2e9013da2bf451139a61ccf" - integrity sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A== - dependencies: - glob "^10.3.7" - -rollup@^4.13.0: - version "4.14.2" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.14.2.tgz#992df3c3bb4ca84ce6b00d51aacb1e5a62d0a14c" - integrity sha512-WkeoTWvuBoFjFAhsEOHKRoZ3r9GfTyhh7Vff1zwebEFLEFjT1lG3784xEgKiTa7E+e70vsC81roVL2MP4tgEEQ== - dependencies: - "@types/estree" "1.0.5" - optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.14.2" - "@rollup/rollup-android-arm64" "4.14.2" - "@rollup/rollup-darwin-arm64" "4.14.2" - "@rollup/rollup-darwin-x64" "4.14.2" - "@rollup/rollup-linux-arm-gnueabihf" "4.14.2" - "@rollup/rollup-linux-arm64-gnu" "4.14.2" - "@rollup/rollup-linux-arm64-musl" "4.14.2" - "@rollup/rollup-linux-powerpc64le-gnu" "4.14.2" - "@rollup/rollup-linux-riscv64-gnu" "4.14.2" - "@rollup/rollup-linux-s390x-gnu" "4.14.2" - "@rollup/rollup-linux-x64-gnu" "4.14.2" - "@rollup/rollup-linux-x64-musl" "4.14.2" - "@rollup/rollup-win32-arm64-msvc" "4.14.2" - "@rollup/rollup-win32-ia32-msvc" "4.14.2" - "@rollup/rollup-win32-x64-msvc" "4.14.2" - fsevents "~2.3.2" - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -rxjs@^7.0.0: - version "7.8.1" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" - integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== - dependencies: - tslib "^2.1.0" - -safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-regex-test@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" - integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.3" - is-regex "^1.1.4" - -safe-regex2@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/safe-regex2/-/safe-regex2-2.0.0.tgz#b287524c397c7a2994470367e0185e1916b1f5b9" - integrity sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ== - dependencies: - ret "~0.2.0" - -safe-regex@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-2.1.1.tgz#f7128f00d056e2fe5c11e81a1324dd974aadced2" - integrity sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A== - dependencies: - regexp-tree "~0.1.1" - -safe-stable-stringify@^2.3.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.1.tgz#34694bd8a30575b7f94792aa51527551bd733d61" - integrity sha512-dVHE6bMtS/bnL2mwualjc6IxEv1F+OCUpA46pKUj6F8uDbUM0jCCulPqRNPSnWwGNKx5etqMjZYdXtrm5KJZGA== - -"safer-buffer@>= 2.1.2 < 3": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -sass-lookup@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/sass-lookup/-/sass-lookup-5.0.1.tgz#1f01d7ff21e09d8c9dcf8d05b3fca28f2f96e6ed" - integrity sha512-t0X5PaizPc2H4+rCwszAqHZRtr4bugo4pgiCvrBFvIX0XFxnr29g77LJcpyj9A0DcKf7gXMLcgvRjsonYI6x4g== - dependencies: - commander "^10.0.1" - -secure-json-parse@^2.4.0, secure-json-parse@^2.5.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/secure-json-parse/-/secure-json-parse-2.7.0.tgz#5a5f9cd6ae47df23dba3151edd06855d47e09862" - integrity sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw== - -seedrandom@^3.0.5: - version "3.0.5" - resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7" - integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg== - -"semver@2 || 3 || 4 || 5": - version "5.7.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" - integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== - -semver@^6.0.0, semver@^6.3.0: - version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^7.3.7: - version "7.5.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" - integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== - dependencies: - lru-cache "^6.0.0" - -semver@^7.5.3: - version "7.6.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" - integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== - dependencies: - lru-cache "^6.0.0" - -set-cookie-parser@^2.4.1: - version "2.4.8" - resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.4.8.tgz#d0da0ed388bc8f24e706a391f9c9e252a13c58b2" - integrity sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg== - -set-function-length@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.1.tgz#47cc5945f2c771e2cf261c6737cf9684a2a5e425" - integrity sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g== - dependencies: - define-data-property "^1.1.2" - es-errors "^1.3.0" - function-bind "^1.1.2" - get-intrinsic "^1.2.3" - gopd "^1.0.1" - has-property-descriptors "^1.0.1" - -set-function-name@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" - integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== - dependencies: - define-data-property "^1.1.4" - es-errors "^1.3.0" - functions-have-names "^1.2.3" - has-property-descriptors "^1.0.2" - -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -shell-quote@^1.7.3: - version "1.8.1" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680" - integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== - -side-channel@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" - integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== - dependencies: - call-bind "^1.0.0" - get-intrinsic "^1.0.2" - object-inspect "^1.9.0" - -siginfo@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" - integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== - -signal-exit@^3.0.2: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -signal-exit@^4.0.1, signal-exit@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" - integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== - -simple-statistics@^7.8.3: - version "7.8.3" - resolved "https://registry.yarnpkg.com/simple-statistics/-/simple-statistics-7.8.3.tgz#62998dd7786ba14fa27b07f4f3cd498466f7961a" - integrity sha512-JFvMY00t6SBGtwMuJ+nqgsx9ylkMiJ5JlK9bkj8AdvniIe5615wWQYkKHXe84XtSuc40G/tlrPu0A5/NlJvv8A== - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -sonic-boom@^3.1.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-3.2.1.tgz#972ceab831b5840a08a002fa95a672008bda1c38" - integrity sha512-iITeTHxy3B9FGu8aVdiDXUVAcHMF9Ss0cCsAOo2HfCrmVGT3/DT5oYaeu0M/YKZDlKTvChEyPq0zI9Hf33EX6A== - dependencies: - atomic-sleep "^1.0.0" - -sonic-boom@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-4.0.1.tgz#515b7cef2c9290cb362c4536388ddeece07aed30" - integrity sha512-hTSD/6JMLyT4r9zeof6UtuBDpjJ9sO08/nmS5djaA9eozT9oOlNdpXSnzcgj4FTqpk3nkLrs61l4gip9r1HCrQ== - dependencies: - atomic-sleep "^1.0.0" - -source-map-js@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" - integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== - -source-map-js@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" - integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== - -source-map@^0.6.1, source-map@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -spawn-command@^0.0.2-1: - version "0.0.2-1" - resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0" - integrity sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg== - -spdx-correct@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" - integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== - dependencies: - spdx-expression-parse "^3.0.0" - spdx-license-ids "^3.0.0" - -spdx-exceptions@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" - integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== - -spdx-expression-parse@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" - integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== - dependencies: - spdx-exceptions "^2.1.0" - spdx-license-ids "^3.0.0" - -spdx-license-ids@^3.0.0: - version "3.0.9" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.9.tgz#8a595135def9592bda69709474f1cbeea7c2467f" - integrity sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ== - -split2@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/split2/-/split2-4.1.0.tgz#101907a24370f85bb782f08adaabe4e281ecf809" - integrity sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ== - -ssim.js@^3.1.1: - version "3.5.0" - resolved "https://registry.yarnpkg.com/ssim.js/-/ssim.js-3.5.0.tgz#d7276b9ee99b57a5ff0db34035f02f35197e62df" - integrity sha512-Aj6Jl2z6oDmgYFFbQqK7fght19bXdOxY7Tj03nF+03M9gCBAjeIiO8/PlEGMfKDwYpw4q6iBqVq2YuREorGg/g== - -stack-utils@^2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" - integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== - dependencies: - escape-string-regexp "^2.0.0" - -stackback@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" - integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== - -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - -std-env@^3.5.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.7.0.tgz#c9f7386ced6ecf13360b6c6c55b8aaa4ef7481d2" - integrity sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg== - -stop-iteration-iterator@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz#6a60be0b4ee757d1ed5254858ec66b10c49285e4" - integrity sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ== - dependencies: - internal-slot "^1.0.4" - -stream-to-array@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/stream-to-array/-/stream-to-array-2.3.0.tgz#bbf6b39f5f43ec30bc71babcb37557acecf34353" - integrity sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA== - dependencies: - any-promise "^1.1.0" - -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^5.0.1, string-width@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" - integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== - dependencies: - eastasianwidth "^0.2.0" - emoji-regex "^9.2.2" - strip-ansi "^7.0.1" - -string.prototype.trimend@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz#c4a27fa026d979d79c04f17397f250a462944533" - integrity sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - -string.prototype.trimstart@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz#e90ab66aa8e4007d92ef591bbf3cd422c56bdcf4" - integrity sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -stringify-object@^3.2.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" - integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw== - dependencies: - get-own-enumerable-property-symbols "^3.0.0" - is-obj "^1.0.1" - is-regexp "^1.0.0" - -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^7.0.1: - version "7.1.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" - integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== - dependencies: - ansi-regex "^6.0.1" - -strip-bom@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= - -strip-final-newline@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" - integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== - -strip-indent@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" - integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== - dependencies: - min-indent "^1.0.0" - -strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== - -strip-literal@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-literal/-/strip-literal-2.0.0.tgz#5d063580933e4e03ebb669b12db64d2200687527" - integrity sha512-f9vHgsCWBq2ugHAkGMiiYY+AYG0D/cbloKKg0nhaaaSNsujdGIpVXCNsrJpCKr5M0f4aI31mr13UjY6GAuXCKA== - dependencies: - js-tokens "^8.0.2" - -stylus-lookup@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/stylus-lookup/-/stylus-lookup-5.0.1.tgz#3c4d116c3b1e8e1a8169c0d9cd20e608595560f4" - integrity sha512-tLtJEd5AGvnVy4f9UHQMw4bkJJtaAcmo54N+ovQBjDY3DuWyK9Eltxzr5+KG0q4ew6v2EHyuWWNnHeiw/Eo7rQ== - dependencies: - commander "^10.0.1" - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-color@^8.1.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -tapable@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" - integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== - -test-exclude@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" - integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== - dependencies: - "@istanbuljs/schema" "^0.1.2" - glob "^7.1.4" - minimatch "^3.0.4" - -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= - -thread-stream@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.2.0.tgz#310c03a253f729094ce5d4638ef5186dfa80a9e8" - integrity sha512-rUkv4/fnb4rqy/gGy7VuqK6wE1+1DOCOWy4RMeaV69ZHMP11tQKZvZSip1yTgrKCMZzEMcCL/bKfHvSfDHx+iQ== - dependencies: - real-require "^0.2.0" - -tiny-emitter@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" - integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== - -tiny-lru@^10.0.0: - version "10.0.1" - resolved "https://registry.yarnpkg.com/tiny-lru/-/tiny-lru-10.0.1.tgz#aaf5d22207e641ed1b176ac2e616d6cc2fc9ef66" - integrity sha512-Vst+6kEsWvb17Zpz14sRJV/f8bUWKhqm6Dc+v08iShmIJ/WxqWytHzCTd6m88pS33rE2zpX34TRmOpAJPloNCA== - -tinybench@^2.5.1: - version "2.6.0" - resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.6.0.tgz#1423284ee22de07c91b3752c048d2764714b341b" - integrity sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA== - -tinypool@^0.8.3: - version "0.8.4" - resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.8.4.tgz#e217fe1270d941b39e98c625dcecebb1408c9aa8" - integrity sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ== - -tinyspy@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-2.2.1.tgz#117b2342f1f38a0dbdcc73a50a454883adf861d1" - integrity sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A== - -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -toidentifier@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= - -tree-kill@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" - integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== - -ts-graphviz@^1.8.1: - version "1.8.2" - resolved "https://registry.yarnpkg.com/ts-graphviz/-/ts-graphviz-1.8.2.tgz#6c4768d05f8a36e37abe34855ffe89a4c4bd96cc" - integrity sha512-5YhbFoHmjxa7pgQLkB07MtGnGJ/yhvjmc9uhsnDBEICME6gkPf83SBwLDQqGDoCa3XzUMWLk1AU2Wn1u1naDtA== - -ts-mixer@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/ts-mixer/-/ts-mixer-6.0.3.tgz#69bd50f406ff39daa369885b16c77a6194c7cae6" - integrity sha512-k43M7uCG1AkTyxgnmI5MPwKoUvS/bRvLvUb7+Pgpdlmok8AoqmUaZxUUw8zKM5B1lqZrt41GjYgnvAi0fppqgQ== - -tsconfig-paths@^3.14.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a" - integrity sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ== - dependencies: - "@types/json5" "^0.0.29" - json5 "^1.0.1" - minimist "^1.2.6" - strip-bom "^3.0.0" - -tsconfig-paths@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz#ef78e19039133446d244beac0fd6a1632e2d107c" - integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== - dependencies: - json5 "^2.2.2" - minimist "^1.2.6" - strip-bom "^3.0.0" - -tslib@2.6.2, tslib@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" - integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== - -tslib@^1.8.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - -tslib@^2.1.0, tslib@^2.3.1: - version "2.5.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" - integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== - -tsutils@^3.21.0: - version "3.21.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" - integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== - dependencies: - tslib "^1.8.1" - -tsx@^4.9.0: - version "4.9.0" - resolved "https://registry.yarnpkg.com/tsx/-/tsx-4.9.0.tgz#08d7ea58df19d87416d26cce8caf93d0c3619852" - integrity sha512-UY0UUhDPL6MkqkZU4xTEjEBOLfV+RIt4xeeJ1qwK73xai4/zveG+X6+tieILa7rjtegUW2LE4p7fw7gAoLuytA== - dependencies: - esbuild "~0.20.2" - get-tsconfig "^4.7.3" - optionalDependencies: - fsevents "~2.3.3" - -type-check@^0.4.0, type-check@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" - integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== - dependencies: - prelude-ls "^1.2.1" - -type-check@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" - integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= - dependencies: - prelude-ls "~1.1.2" - -type-detect@^4.0.0, type-detect@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" - integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== - -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== - -type-fest@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" - integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== - -type-fest@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== - -type-is@^1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -typed-array-length@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" - integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng== - dependencies: - call-bind "^1.0.2" - for-each "^0.3.3" - is-typed-array "^1.1.9" - -typed-function@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-4.1.1.tgz#38ce3cae31f4f513bcb263563fdad27b2afa73e8" - integrity sha512-Pq1DVubcvibmm8bYcMowjVnnMwPVMeh0DIdA8ad8NZY2sJgapANJmiigSUwlt+EgXxpfIv8MWrQXTIzkfYZLYQ== - -typescript@5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.2.tgz#891e1a90c5189d8506af64b9ef929fca99ba1ee5" - integrity sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw== - -typescript@^5.0.4, typescript@^5.4.4: - version "5.4.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611" - integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== - -ufo@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.1.2.tgz#d0d9e0fa09dece0c31ffd57bd363f030a35cfe76" - integrity sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ== - -ufo@^1.3.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.4.0.tgz#39845b31be81b4f319ab1d99fd20c56cac528d32" - integrity sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ== - -unbox-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" - integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== - dependencies: - call-bind "^1.0.2" - has-bigints "^1.0.2" - has-symbols "^1.0.3" - which-boxed-primitive "^1.0.2" - -undici@5.27.2: - version "5.27.2" - resolved "https://registry.yarnpkg.com/undici/-/undici-5.27.2.tgz#a270c563aea5b46cc0df2550523638c95c5d4411" - integrity sha512-iS857PdOEy/y3wlM3yRp+6SNQQ6xU0mmZcwRSriqk+et/cwWAtwmIGf6WkoDN2EK/AMdCO/dfXzIwi+rFMrjjQ== - dependencies: - "@fastify/busboy" "^2.0.0" - -universal-user-agent@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" - integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== - -unpipe@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -util-deprecate@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - -uuid@8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - -v8-to-istanbul@^9.2.0: - version "9.2.0" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz#2ed7644a245cddd83d4e087b9b33b3e62dfd10ad" - integrity sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA== - dependencies: - "@jridgewell/trace-mapping" "^0.3.12" - "@types/istanbul-lib-coverage" "^2.0.1" - convert-source-map "^2.0.0" - -validate-npm-package-license@^3.0.1: - version "3.0.4" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" - integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== - dependencies: - spdx-correct "^3.0.0" - spdx-expression-parse "^3.0.0" - -vary@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= - -vite-node@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-1.6.0.tgz#2c7e61129bfecc759478fa592754fd9704aaba7f" - integrity sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw== - dependencies: - cac "^6.7.14" - debug "^4.3.4" - pathe "^1.1.1" - picocolors "^1.0.0" - vite "^5.0.0" - -vite@^5.0.0: - version "5.2.8" - resolved "https://registry.yarnpkg.com/vite/-/vite-5.2.8.tgz#a99e09939f1a502992381395ce93efa40a2844aa" - integrity sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA== - dependencies: - esbuild "^0.20.1" - postcss "^8.4.38" - rollup "^4.13.0" - optionalDependencies: - fsevents "~2.3.3" - -vitest@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/vitest/-/vitest-1.6.0.tgz#9d5ad4752a3c451be919e412c597126cffb9892f" - integrity sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA== - dependencies: - "@vitest/expect" "1.6.0" - "@vitest/runner" "1.6.0" - "@vitest/snapshot" "1.6.0" - "@vitest/spy" "1.6.0" - "@vitest/utils" "1.6.0" - acorn-walk "^8.3.2" - chai "^4.3.10" - debug "^4.3.4" - execa "^8.0.1" - local-pkg "^0.5.0" - magic-string "^0.30.5" - pathe "^1.1.1" - picocolors "^1.0.0" - std-env "^3.5.0" - strip-literal "^2.0.0" - tinybench "^2.5.1" - tinypool "^0.8.3" - vite "^5.0.0" - vite-node "1.6.0" - why-is-node-running "^2.2.2" - -walkdir@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/walkdir/-/walkdir-0.4.1.tgz#dc119f83f4421df52e3061e514228a2db20afa39" - integrity sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ== - -wcwidth@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" - integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== - dependencies: - defaults "^1.0.3" - -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - -which-boxed-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" - integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== - dependencies: - is-bigint "^1.0.1" - is-boolean-object "^1.1.0" - is-number-object "^1.0.4" - is-string "^1.0.5" - is-symbol "^1.0.3" - -which-collection@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906" - integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A== - dependencies: - is-map "^2.0.1" - is-set "^2.0.1" - is-weakmap "^2.0.1" - is-weakset "^2.0.1" - -which-typed-array@^1.1.13: - version "1.1.14" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.14.tgz#1f78a111aee1e131ca66164d8bdc3ab062c95a06" - integrity sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg== - dependencies: - available-typed-arrays "^1.0.6" - call-bind "^1.0.5" - for-each "^0.3.3" - gopd "^1.0.1" - has-tostringtag "^1.0.1" - -which-typed-array@^1.1.9: - version "1.1.9" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6" - integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA== - dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - for-each "^0.3.3" - gopd "^1.0.1" - has-tostringtag "^1.0.0" - is-typed-array "^1.1.10" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -why-is-node-running@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-2.2.2.tgz#4185b2b4699117819e7154594271e7e344c9973e" - integrity sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA== - dependencies: - siginfo "^2.0.0" - stackback "0.0.2" - -word-wrap@^1.2.3, word-wrap@^1.2.5, word-wrap@~1.2.3: - version "1.2.5" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" - integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== - -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" - integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== - dependencies: - ansi-styles "^6.1.0" - string-width "^5.0.1" - strip-ansi "^7.0.1" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -ws@8.14.2: - version "8.14.2" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f" - integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g== - -ws@^8.14.2: - version "8.17.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.0.tgz#d145d18eca2ed25aaf791a183903f7be5e295fea" - integrity sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow== - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - -yargs-parser@^21.1.1: - version "21.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - -yargs@^17.3.1: - version "17.7.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" - integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== - dependencies: - cliui "^8.0.1" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.1.1" - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - -yocto-queue@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251" - integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g== - -zlib-sync@^0.1.9: - version "0.1.9" - resolved "https://registry.yarnpkg.com/zlib-sync/-/zlib-sync-0.1.9.tgz#7075cc257e4551f5d9fc74e24cf2c11e39c7a0d1" - integrity sha512-DinB43xCjVwIBDpaIvQqHbmDsnYnSt6HJ/yiB2MZQGTqgPcwBSZqLkimXwK8BvdjQ/MaZysb5uEenImncqvCqQ== - dependencies: - nan "^2.18.0" - -zod@^3.23.6: - version "3.23.6" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.6.tgz#c08a977e2255dab1fdba933651584a05fcbf19e1" - integrity sha512-RTHJlZhsRbuA8Hmp/iNL7jnfc4nZishjsanDAfEY1QpDQZCahUp3xDzl+zfweE9BklxMUcgBgS1b7Lvie/ZVwA== +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"@ampproject/remapping@npm:^2.3.0": + version: 2.3.0 + resolution: "@ampproject/remapping@npm:2.3.0" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/81d63cca5443e0f0c72ae18b544cc28c7c0ec2cea46e7cb888bb0e0f411a1191d0d6b7af798d54e30777d8d1488b2ec0732aac2be342d3d7d3ffd271c6f489ed + languageName: node + linkType: hard + +"@babel/helper-string-parser@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-string-parser@npm:7.24.7" + checksum: 10c0/47840c7004e735f3dc93939c77b099bb41a64bf3dda0cae62f60e6f74a5ff80b63e9b7cf77b5ec25a324516381fc994e1f62f922533236a8e3a6af57decb5e1e + languageName: node + linkType: hard + +"@babel/helper-string-parser@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/helper-string-parser@npm:7.24.8" + checksum: 10c0/6361f72076c17fabf305e252bf6d580106429014b3ab3c1f5c4eb3e6d465536ea6b670cc0e9a637a77a9ad40454d3e41361a2909e70e305116a23d68ce094c08 + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-validator-identifier@npm:7.24.7" + checksum: 10c0/87ad608694c9477814093ed5b5c080c2e06d44cb1924ae8320474a74415241223cc2a725eea2640dd783ff1e3390e5f95eede978bc540e870053152e58f1d651 + languageName: node + linkType: hard + +"@babel/parser@npm:^7.24.4": + version: 7.24.8 + resolution: "@babel/parser@npm:7.24.8" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/ce69671de8fa6f649abf849be262707ac700b573b8b1ce1893c66cc6cd76aeb1294a19e8c290b0eadeb2f47d3f413a2e57a281804ffbe76bfb9fa50194cf3c52 + languageName: node + linkType: hard + +"@babel/runtime@npm:^7.21.0": + version: 7.23.9 + resolution: "@babel/runtime@npm:7.23.9" + dependencies: + regenerator-runtime: "npm:^0.14.0" + checksum: 10c0/e71205fdd7082b2656512cc98e647d9ea7e222e4fe5c36e9e5adc026446fcc3ba7b3cdff8b0b694a0b78bb85db83e7b1e3d4c56ef90726682b74f13249cf952d + languageName: node + linkType: hard + +"@babel/types@npm:^7.24.0": + version: 7.24.9 + resolution: "@babel/types@npm:7.24.9" + dependencies: + "@babel/helper-string-parser": "npm:^7.24.8" + "@babel/helper-validator-identifier": "npm:^7.24.7" + to-fast-properties: "npm:^2.0.0" + checksum: 10c0/4970b3481cab39c5c3fdb7c28c834df5c7049f3c7f43baeafe121bb05270ebf0da7c65b097abf314877f213baa591109c82204f30d66cdd46c22ece4a2f32415 + languageName: node + linkType: hard + +"@babel/types@npm:^7.8.3": + version: 7.24.7 + resolution: "@babel/types@npm:7.24.7" + dependencies: + "@babel/helper-string-parser": "npm:^7.24.7" + "@babel/helper-validator-identifier": "npm:^7.24.7" + to-fast-properties: "npm:^2.0.0" + checksum: 10c0/d9ecbfc3eb2b05fb1e6eeea546836ac30d990f395ef3fe3f75ced777a222c3cfc4489492f72e0ce3d9a5a28860a1ce5f81e66b88cf5088909068b3ff4fab72c1 + languageName: node + linkType: hard + +"@bcoe/v8-coverage@npm:^0.2.3": + version: 0.2.3 + resolution: "@bcoe/v8-coverage@npm:0.2.3" + checksum: 10c0/6b80ae4cb3db53f486da2dc63b6e190a74c8c3cca16bb2733f234a0b6a9382b09b146488ae08e2b22cf00f6c83e20f3e040a2f7894f05c045c946d6a090b1d52 + languageName: node + linkType: hard + +"@biomejs/biome@npm:^1.8.3": + version: 1.8.3 + resolution: "@biomejs/biome@npm:1.8.3" + dependencies: + "@biomejs/cli-darwin-arm64": "npm:1.8.3" + "@biomejs/cli-darwin-x64": "npm:1.8.3" + "@biomejs/cli-linux-arm64": "npm:1.8.3" + "@biomejs/cli-linux-arm64-musl": "npm:1.8.3" + "@biomejs/cli-linux-x64": "npm:1.8.3" + "@biomejs/cli-linux-x64-musl": "npm:1.8.3" + "@biomejs/cli-win32-arm64": "npm:1.8.3" + "@biomejs/cli-win32-x64": "npm:1.8.3" + dependenciesMeta: + "@biomejs/cli-darwin-arm64": + optional: true + "@biomejs/cli-darwin-x64": + optional: true + "@biomejs/cli-linux-arm64": + optional: true + "@biomejs/cli-linux-arm64-musl": + optional: true + "@biomejs/cli-linux-x64": + optional: true + "@biomejs/cli-linux-x64-musl": + optional: true + "@biomejs/cli-win32-arm64": + optional: true + "@biomejs/cli-win32-x64": + optional: true + bin: + biome: bin/biome + checksum: 10c0/95fe99ce82cd8242f1be51cbf3ac26043b253f5a369d3dc24df09bdb32ec04dba679b1d4fa8b9d602b1bf2c30ecd80af14aa8f5c92d6e0cd6214a99a1099a65b + languageName: node + linkType: hard + +"@biomejs/cli-darwin-arm64@npm:1.8.3": + version: 1.8.3 + resolution: "@biomejs/cli-darwin-arm64@npm:1.8.3" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@biomejs/cli-darwin-x64@npm:1.8.3": + version: 1.8.3 + resolution: "@biomejs/cli-darwin-x64@npm:1.8.3" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@biomejs/cli-linux-arm64-musl@npm:1.8.3": + version: 1.8.3 + resolution: "@biomejs/cli-linux-arm64-musl@npm:1.8.3" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@biomejs/cli-linux-arm64@npm:1.8.3": + version: 1.8.3 + resolution: "@biomejs/cli-linux-arm64@npm:1.8.3" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@biomejs/cli-linux-x64-musl@npm:1.8.3": + version: 1.8.3 + resolution: "@biomejs/cli-linux-x64-musl@npm:1.8.3" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@biomejs/cli-linux-x64@npm:1.8.3": + version: 1.8.3 + resolution: "@biomejs/cli-linux-x64@npm:1.8.3" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@biomejs/cli-win32-arm64@npm:1.8.3": + version: 1.8.3 + resolution: "@biomejs/cli-win32-arm64@npm:1.8.3" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@biomejs/cli-win32-x64@npm:1.8.3": + version: 1.8.3 + resolution: "@biomejs/cli-win32-x64@npm:1.8.3" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@discordjs/builders@npm:^1.8.2": + version: 1.8.2 + resolution: "@discordjs/builders@npm:1.8.2" + dependencies: + "@discordjs/formatters": "npm:^0.4.0" + "@discordjs/util": "npm:^1.1.0" + "@sapphire/shapeshift": "npm:^3.9.7" + discord-api-types: "npm:0.37.83" + fast-deep-equal: "npm:^3.1.3" + ts-mixer: "npm:^6.0.4" + tslib: "npm:^2.6.2" + checksum: 10c0/108fe8903e9bc875a28b39629589391e44f41835169114e93c15548b5635632862168d4bec3392eac12e981d06b830d90f11b7af38fd664d7ea9cfa46b0726f3 + languageName: node + linkType: hard + +"@discordjs/collection@npm:1.5.3": + version: 1.5.3 + resolution: "@discordjs/collection@npm:1.5.3" + checksum: 10c0/54338e005e9a1c6a5c91464cbcc378a5bf74410ad5b8bba1961b80d0ec8f1eb1b768ea737ad03bcb62430ea84aa5561523318b739538b334115ed775630ca42c + languageName: node + linkType: hard + +"@discordjs/collection@npm:^2.1.0": + version: 2.1.0 + resolution: "@discordjs/collection@npm:2.1.0" + checksum: 10c0/537df017962d4b98a35768db0d47e457a3f64daf550921368f98d3fbd66358d76f00ce01eac61163841b353747e702d1e1ea8badfdc2fb5a412eae7e84b1c607 + languageName: node + linkType: hard + +"@discordjs/formatters@npm:^0.4.0": + version: 0.4.0 + resolution: "@discordjs/formatters@npm:0.4.0" + dependencies: + discord-api-types: "npm:0.37.83" + checksum: 10c0/9788e00559155f1767f85d02f14b0368affd4b7b86c1ff2207779906093ad9caf86cd74a60e681da5e762234b45fc9368c044247a484b4691cd652d5ff0d4f70 + languageName: node + linkType: hard + +"@discordjs/rest@npm:^2.3.0": + version: 2.3.0 + resolution: "@discordjs/rest@npm:2.3.0" + dependencies: + "@discordjs/collection": "npm:^2.1.0" + "@discordjs/util": "npm:^1.1.0" + "@sapphire/async-queue": "npm:^1.5.2" + "@sapphire/snowflake": "npm:^3.5.3" + "@vladfrangu/async_event_emitter": "npm:^2.2.4" + discord-api-types: "npm:0.37.83" + magic-bytes.js: "npm:^1.10.0" + tslib: "npm:^2.6.2" + undici: "npm:6.13.0" + checksum: 10c0/f971c16f4095fd9f092d8d73dfed788341823bc06901b5b6b3b15a65697dc6bff4cc6ade93bedc2c2a6e01d39dae8bac9ebbed59a3a336fdbf2d81a4e63d2581 + languageName: node + linkType: hard + +"@discordjs/util@npm:^1.1.0": + version: 1.1.0 + resolution: "@discordjs/util@npm:1.1.0" + checksum: 10c0/e4a7945b17eb86b3fa5bf49e49e0d0786c8f39e66bbeee19f01db4e817cf5a4761e684ec63a7d832722648d0e23236eeb21c450c34a11683102813a23701bda4 + languageName: node + linkType: hard + +"@discordjs/ws@npm:^1.1.1": + version: 1.1.1 + resolution: "@discordjs/ws@npm:1.1.1" + dependencies: + "@discordjs/collection": "npm:^2.1.0" + "@discordjs/rest": "npm:^2.3.0" + "@discordjs/util": "npm:^1.1.0" + "@sapphire/async-queue": "npm:^1.5.2" + "@types/ws": "npm:^8.5.10" + "@vladfrangu/async_event_emitter": "npm:^2.2.4" + discord-api-types: "npm:0.37.83" + tslib: "npm:^2.6.2" + ws: "npm:^8.16.0" + checksum: 10c0/57e46534e3ea8ba53c1fbd282d4f0aa03563e10244847f6e6393957a56d05ae0af52c43114d6a6e1d7a3f3127498b09efd69dd3da11abe5e4a5170ac2e269178 + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/aix-ppc64@npm:0.21.5" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-arm64@npm:0.21.5" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-arm@npm:0.21.5" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-x64@npm:0.21.5" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/darwin-arm64@npm:0.21.5" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/darwin-x64@npm:0.21.5" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/freebsd-arm64@npm:0.21.5" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/freebsd-x64@npm:0.21.5" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-arm64@npm:0.21.5" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-arm@npm:0.21.5" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-ia32@npm:0.21.5" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-loong64@npm:0.21.5" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-mips64el@npm:0.21.5" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-ppc64@npm:0.21.5" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-riscv64@npm:0.21.5" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-s390x@npm:0.21.5" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-x64@npm:0.21.5" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/netbsd-x64@npm:0.21.5" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/openbsd-x64@npm:0.21.5" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/sunos-x64@npm:0.21.5" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-arm64@npm:0.21.5" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-ia32@npm:0.21.5" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-x64@npm:0.21.5" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@ioredis/as-callback@npm:^3.0.0": + version: 3.0.0 + resolution: "@ioredis/as-callback@npm:3.0.0" + checksum: 10c0/cbeae0c4a8f8f8ea1987d105056fd595a5a1a781a73dfa3a13ac13027fd1de099af6f3590bd315fd20284efd8597430a7ba3b912570804d158dabbdb2071523b + languageName: node + linkType: hard + +"@ioredis/commands@npm:^1.1.1, @ioredis/commands@npm:^1.2.0": + version: 1.2.0 + resolution: "@ioredis/commands@npm:1.2.0" + checksum: 10c0/a5d3c29dd84d8a28b7c67a441ac1715cbd7337a7b88649c0f17c345d89aa218578d2b360760017c48149ef8a70f44b051af9ac0921a0622c2b479614c4f65b36 + languageName: node + linkType: hard + +"@isaacs/cliui@npm:^8.0.2": + version: 8.0.2 + resolution: "@isaacs/cliui@npm:8.0.2" + dependencies: + string-width: "npm:^5.1.2" + string-width-cjs: "npm:string-width@^4.2.0" + strip-ansi: "npm:^7.0.1" + strip-ansi-cjs: "npm:strip-ansi@^6.0.1" + wrap-ansi: "npm:^8.1.0" + wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" + checksum: 10c0/b1bf42535d49f11dc137f18d5e4e63a28c5569de438a221c369483731e9dac9fb797af554e8bf02b6192d1e5eba6e6402cf93900c3d0ac86391d00d04876789e + languageName: node + linkType: hard + +"@istanbuljs/schema@npm:^0.1.2": + version: 0.1.3 + resolution: "@istanbuljs/schema@npm:0.1.3" + checksum: 10c0/61c5286771676c9ca3eb2bd8a7310a9c063fb6e0e9712225c8471c582d157392c88f5353581c8c9adbe0dff98892317d2fdfc56c3499aa42e0194405206a963a + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.5 + resolution: "@jridgewell/gen-mapping@npm:0.3.5" + dependencies: + "@jridgewell/set-array": "npm:^1.2.1" + "@jridgewell/sourcemap-codec": "npm:^1.4.10" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/1be4fd4a6b0f41337c4f5fdf4afc3bd19e39c3691924817108b82ffcb9c9e609c273f936932b9fba4b3a298ce2eb06d9bff4eb1cc3bd81c4f4ee1b4917e25feb + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e + languageName: node + linkType: hard + +"@jridgewell/set-array@npm:^1.2.1": + version: 1.2.1 + resolution: "@jridgewell/set-array@npm:1.2.1" + checksum: 10c0/2a5aa7b4b5c3464c895c802d8ae3f3d2b92fcbe84ad12f8d0bfbb1f5ad006717e7577ee1fd2eac00c088abe486c7adb27976f45d2941ff6b0b92b2c3302c60f4 + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.10": + version: 1.4.14 + resolution: "@jridgewell/sourcemap-codec@npm:1.4.14" + checksum: 10c0/3fbaff1387c1338b097eeb6ff92890d7838f7de0dde259e4983763b44540bfd5ca6a1f7644dc8ad003a57f7e80670d5b96a8402f1386ba9aee074743ae9bad51 + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.15": + version: 1.4.15 + resolution: "@jridgewell/sourcemap-codec@npm:1.4.15" + checksum: 10c0/0c6b5ae663087558039052a626d2d7ed5208da36cfd707dcc5cea4a07cfc918248403dcb5989a8f7afaf245ce0573b7cc6fd94c4a30453bd10e44d9363940ba5 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.23, @jridgewell/trace-mapping@npm:^0.3.24": + version: 0.3.25 + resolution: "@jridgewell/trace-mapping@npm:0.3.25" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 10c0/3d1ce6ebc69df9682a5a8896b414c6537e428a1d68b02fcc8363b04284a8ca0df04d0ee3013132252ab14f2527bc13bea6526a912ecb5658f0e39fd2860b4df4 + languageName: node + linkType: hard + +"@napi-rs/canvas-android-arm64@npm:0.1.53": + version: 0.1.53 + resolution: "@napi-rs/canvas-android-arm64@npm:0.1.53" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@napi-rs/canvas-darwin-arm64@npm:0.1.53": + version: 0.1.53 + resolution: "@napi-rs/canvas-darwin-arm64@npm:0.1.53" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@napi-rs/canvas-darwin-x64@npm:0.1.53": + version: 0.1.53 + resolution: "@napi-rs/canvas-darwin-x64@npm:0.1.53" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@napi-rs/canvas-linux-arm-gnueabihf@npm:0.1.53": + version: 0.1.53 + resolution: "@napi-rs/canvas-linux-arm-gnueabihf@npm:0.1.53" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@napi-rs/canvas-linux-arm64-gnu@npm:0.1.53": + version: 0.1.53 + resolution: "@napi-rs/canvas-linux-arm64-gnu@npm:0.1.53" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@napi-rs/canvas-linux-arm64-musl@npm:0.1.53": + version: 0.1.53 + resolution: "@napi-rs/canvas-linux-arm64-musl@npm:0.1.53" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@napi-rs/canvas-linux-x64-gnu@npm:0.1.53": + version: 0.1.53 + resolution: "@napi-rs/canvas-linux-x64-gnu@npm:0.1.53" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@napi-rs/canvas-linux-x64-musl@npm:0.1.53": + version: 0.1.53 + resolution: "@napi-rs/canvas-linux-x64-musl@npm:0.1.53" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@napi-rs/canvas-win32-x64-msvc@npm:0.1.53": + version: 0.1.53 + resolution: "@napi-rs/canvas-win32-x64-msvc@npm:0.1.53" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@napi-rs/canvas@npm:^0.1.53": + version: 0.1.53 + resolution: "@napi-rs/canvas@npm:0.1.53" + dependencies: + "@napi-rs/canvas-android-arm64": "npm:0.1.53" + "@napi-rs/canvas-darwin-arm64": "npm:0.1.53" + "@napi-rs/canvas-darwin-x64": "npm:0.1.53" + "@napi-rs/canvas-linux-arm-gnueabihf": "npm:0.1.53" + "@napi-rs/canvas-linux-arm64-gnu": "npm:0.1.53" + "@napi-rs/canvas-linux-arm64-musl": "npm:0.1.53" + "@napi-rs/canvas-linux-x64-gnu": "npm:0.1.53" + "@napi-rs/canvas-linux-x64-musl": "npm:0.1.53" + "@napi-rs/canvas-win32-x64-msvc": "npm:0.1.53" + dependenciesMeta: + "@napi-rs/canvas-android-arm64": + optional: true + "@napi-rs/canvas-darwin-arm64": + optional: true + "@napi-rs/canvas-darwin-x64": + optional: true + "@napi-rs/canvas-linux-arm-gnueabihf": + optional: true + "@napi-rs/canvas-linux-arm64-gnu": + optional: true + "@napi-rs/canvas-linux-arm64-musl": + optional: true + "@napi-rs/canvas-linux-x64-gnu": + optional: true + "@napi-rs/canvas-linux-x64-musl": + optional: true + "@napi-rs/canvas-win32-x64-msvc": + optional: true + checksum: 10c0/7722db881ebf7571df9ec0ba00172b09ab48e54352ce55f7e15fe8671c18aea216744ca060088c19fa1327e66d95c00b5c662d82cf6536e7b4e2cdf3d76b2261 + languageName: node + linkType: hard + +"@nodelib/fs.scandir@npm:2.1.5": + version: 2.1.5 + resolution: "@nodelib/fs.scandir@npm:2.1.5" + dependencies: + "@nodelib/fs.stat": "npm:2.0.5" + run-parallel: "npm:^1.1.9" + checksum: 10c0/732c3b6d1b1e967440e65f284bd06e5821fedf10a1bea9ed2bb75956ea1f30e08c44d3def9d6a230666574edbaf136f8cfd319c14fd1f87c66e6a44449afb2eb + languageName: node + linkType: hard + +"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": + version: 2.0.5 + resolution: "@nodelib/fs.stat@npm:2.0.5" + checksum: 10c0/88dafe5e3e29a388b07264680dc996c17f4bda48d163a9d4f5c1112979f0ce8ec72aa7116122c350b4e7976bc5566dc3ddb579be1ceaacc727872eb4ed93926d + languageName: node + linkType: hard + +"@nodelib/fs.walk@npm:^1.2.3": + version: 1.2.8 + resolution: "@nodelib/fs.walk@npm:1.2.8" + dependencies: + "@nodelib/fs.scandir": "npm:2.1.5" + fastq: "npm:^1.6.0" + checksum: 10c0/db9de047c3bb9b51f9335a7bb46f4fcfb6829fb628318c12115fbaf7d369bfce71c15b103d1fc3b464812d936220ee9bc1c8f762d032c9f6be9acc99249095b1 + languageName: node + linkType: hard + +"@npmcli/agent@npm:^2.0.0": + version: 2.2.2 + resolution: "@npmcli/agent@npm:2.2.2" + dependencies: + agent-base: "npm:^7.1.0" + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.1" + lru-cache: "npm:^10.0.1" + socks-proxy-agent: "npm:^8.0.3" + checksum: 10c0/325e0db7b287d4154ecd164c0815c08007abfb07653cc57bceded17bb7fd240998a3cbdbe87d700e30bef494885eccc725ab73b668020811d56623d145b524ae + languageName: node + linkType: hard + +"@npmcli/fs@npm:^3.1.0": + version: 3.1.1 + resolution: "@npmcli/fs@npm:3.1.1" + dependencies: + semver: "npm:^7.3.5" + checksum: 10c0/c37a5b4842bfdece3d14dfdb054f73fe15ed2d3da61b34ff76629fb5b1731647c49166fd2a8bf8b56fcfa51200382385ea8909a3cbecdad612310c114d3f6c99 + languageName: node + linkType: hard + +"@oldschoolgg/toolkit@git+https://github.com/oldschoolgg/toolkit.git#cd7c6865229ca7dc4a66b3816586f2d3f4a4fbed": + version: 0.0.24 + resolution: "@oldschoolgg/toolkit@https://github.com/oldschoolgg/toolkit.git#commit=cd7c6865229ca7dc4a66b3816586f2d3f4a4fbed" + dependencies: + decimal.js: "npm:^10.4.3" + deep-object-diff: "npm:^1.1.9" + deepmerge: "npm:4.3.1" + e: "npm:0.2.33" + emoji-regex: "npm:^10.2.1" + fast-deep-equal: "npm:^3.1.3" + ioredis: "npm:^5.4.1" + ioredis-mock: "npm:^8.9.0" + math-expression-evaluator: "npm:^1.3.14" + pure-rand: "npm:^6.1.0" + zod: "npm:3.23.8" + peerDependencies: + discord.js: ^14.15.3 + oldschooljs: ^2.5.9 + checksum: 10c0/42eaec1c99c671adab7b56ca7e11d37bf5a0e07d0f0da0a892cdf477a78c061ea131a43b1c578d09f1c6b02e05d1ce47db9586ad9a8de62679cc492c847c3fca + languageName: node + linkType: hard + +"@opentelemetry/api-logs@npm:0.52.1": + version: 0.52.1 + resolution: "@opentelemetry/api-logs@npm:0.52.1" + dependencies: + "@opentelemetry/api": "npm:^1.0.0" + checksum: 10c0/fddecb2211f987bf1a7f104594e58227655c887a6a22b41e9ead5ed925a4594b56186b38fca8e24db33058a924d8b54ddd6b315eca915c469f9653ce7813c31a + languageName: node + linkType: hard + +"@opentelemetry/api@npm:^1.0.0, @opentelemetry/api@npm:^1.6.0, @opentelemetry/api@npm:^1.8, @opentelemetry/api@npm:^1.9.0": + version: 1.9.0 + resolution: "@opentelemetry/api@npm:1.9.0" + checksum: 10c0/9aae2fe6e8a3a3eeb6c1fdef78e1939cf05a0f37f8a4fae4d6bf2e09eb1e06f966ece85805626e01ba5fab48072b94f19b835449e58b6d26720ee19a58298add + languageName: node + linkType: hard + +"@opentelemetry/context-async-hooks@npm:^1.25.1": + version: 1.25.1 + resolution: "@opentelemetry/context-async-hooks@npm:1.25.1" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10c0/bdea47675fe7ca7363b548ca86e724baa102bbb68d92702a20c281615dbae040aad907ff08f553f0e4985868f99a762aadac04f07ad51915ef512c5c817d7976 + languageName: node + linkType: hard + +"@opentelemetry/core@npm:1.25.1, @opentelemetry/core@npm:^1.1.0, @opentelemetry/core@npm:^1.25.1, @opentelemetry/core@npm:^1.8.0": + version: 1.25.1 + resolution: "@opentelemetry/core@npm:1.25.1" + dependencies: + "@opentelemetry/semantic-conventions": "npm:1.25.1" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10c0/37270396fe3546e454f5a6e8cab0e5777e49a8e4e56ef05644c4e458b3ba7c662f57ad1ba2dd936ddaef54cbe985abd7cee0d3e9188dfdc0e3b3d446c3484337 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-connect@npm:0.37.0": + version: 0.37.0 + resolution: "@opentelemetry/instrumentation-connect@npm:0.37.0" + dependencies: + "@opentelemetry/core": "npm:^1.8.0" + "@opentelemetry/instrumentation": "npm:^0.52.0" + "@opentelemetry/semantic-conventions": "npm:^1.22.0" + "@types/connect": "npm:3.4.36" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10c0/aeacb5b2816fb912dfaa434738b004056b32b93b1ab933cf21944c4cd92efd72bb6094b585428e171140a4c52bf6bd692ac166c669ccce9a4bd2fe82589898af + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-express@npm:0.40.1": + version: 0.40.1 + resolution: "@opentelemetry/instrumentation-express@npm:0.40.1" + dependencies: + "@opentelemetry/core": "npm:^1.8.0" + "@opentelemetry/instrumentation": "npm:^0.52.0" + "@opentelemetry/semantic-conventions": "npm:^1.22.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10c0/fd150c52ed84f648fb3caaa7c8e971212b4d76abc61e64d1d9acd9d0cbe371709f7f1d0724d891809b8f737cfaffc6d4d08ec2c7c3ee1fa814b46057dbeef2eb + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-fastify@npm:0.37.0": + version: 0.37.0 + resolution: "@opentelemetry/instrumentation-fastify@npm:0.37.0" + dependencies: + "@opentelemetry/core": "npm:^1.8.0" + "@opentelemetry/instrumentation": "npm:^0.52.0" + "@opentelemetry/semantic-conventions": "npm:^1.22.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10c0/57dec98c9685117ab407b526eb6aba110938523e9b7d0c957f661dc7923515ae952737c652fc45a62bc421ef50beaa6454d7bffa82bfdb23f170a70f8d9cb58a + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-graphql@npm:0.41.0": + version: 0.41.0 + resolution: "@opentelemetry/instrumentation-graphql@npm:0.41.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.52.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10c0/3c5f8c22c5d54cdd06142c52c595fe573bb9c625baa561a891f63cf2fe85f6a5bcd7107d7539551b17f13aa098628fdb06184ffc86860f63c8cda0c69b9722d9 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-hapi@npm:0.39.0": + version: 0.39.0 + resolution: "@opentelemetry/instrumentation-hapi@npm:0.39.0" + dependencies: + "@opentelemetry/core": "npm:^1.8.0" + "@opentelemetry/instrumentation": "npm:^0.52.0" + "@opentelemetry/semantic-conventions": "npm:^1.22.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10c0/78f56c1a140e990fac6688117a42274303ba4f51050378a67a2e0e715f84b0c545fa84de5d64ecab93e40c01e60cc454d0407afc3b23b8d8a3493aea6a60cc01 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-http@npm:0.52.1": + version: 0.52.1 + resolution: "@opentelemetry/instrumentation-http@npm:0.52.1" + dependencies: + "@opentelemetry/core": "npm:1.25.1" + "@opentelemetry/instrumentation": "npm:0.52.1" + "@opentelemetry/semantic-conventions": "npm:1.25.1" + semver: "npm:^7.5.2" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10c0/4309a99b0410e7ab1351efc26f93965e6df32a18fa529841442de016e32ba35b97f2621331b171e37e75cd4d386372edc7164ec2323fac9fd57fc0082aff55a7 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-ioredis@npm:0.41.0": + version: 0.41.0 + resolution: "@opentelemetry/instrumentation-ioredis@npm:0.41.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.52.0" + "@opentelemetry/redis-common": "npm:^0.36.2" + "@opentelemetry/semantic-conventions": "npm:^1.23.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10c0/96a231efe066c529a9ff2a5adc1211df7d1ab42119eeebf512917a16e9eeec404bcb74e85fdc10e98c3f6fd951613d9ba8ee6c0e68d325b6684fc952cc6015a6 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-koa@npm:0.41.0": + version: 0.41.0 + resolution: "@opentelemetry/instrumentation-koa@npm:0.41.0" + dependencies: + "@opentelemetry/core": "npm:^1.8.0" + "@opentelemetry/instrumentation": "npm:^0.52.0" + "@opentelemetry/semantic-conventions": "npm:^1.22.0" + "@types/koa": "npm:2.14.0" + "@types/koa__router": "npm:12.0.3" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10c0/990c6323bf216bb063e5ba9d8792ae6e3272a7e192ba0114aa22081c5df5ae7131f36991bf1aad12ee2db887b55dc47980f947f9c2302e0c66b0bb113cc48554 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-mongodb@npm:0.45.0": + version: 0.45.0 + resolution: "@opentelemetry/instrumentation-mongodb@npm:0.45.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.52.0" + "@opentelemetry/sdk-metrics": "npm:^1.9.1" + "@opentelemetry/semantic-conventions": "npm:^1.22.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10c0/a1ebb5f52d96b8ac424e20cfcbf8249cb979a521ad64ebab1a24432196fd5f5274c52b0ec044b03ec6d80f7d23810dea16c7b72d0a13b8e2baaeb27a1cdfe7ae + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-mongoose@npm:0.39.0": + version: 0.39.0 + resolution: "@opentelemetry/instrumentation-mongoose@npm:0.39.0" + dependencies: + "@opentelemetry/core": "npm:^1.8.0" + "@opentelemetry/instrumentation": "npm:^0.52.0" + "@opentelemetry/semantic-conventions": "npm:^1.22.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10c0/4d7979d59c0d5b6077dcb1c27ee4f70dbf13b50219ed85f0c27cee2a9990d43c58735e260d76206ffafb4a2b454f56738148a17e7afcfb7f4c72c90bd57335e7 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-mysql2@npm:0.39.0": + version: 0.39.0 + resolution: "@opentelemetry/instrumentation-mysql2@npm:0.39.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.52.0" + "@opentelemetry/semantic-conventions": "npm:^1.22.0" + "@opentelemetry/sql-common": "npm:^0.40.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10c0/beb8795a2ad25cbc65d7e51be3691edd91cab8555172b7e09a30341546523adfa371c295b1d9945a3546ccd956fe4a70026939e0dee0081c2b886436cde4e948 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-mysql@npm:0.39.0": + version: 0.39.0 + resolution: "@opentelemetry/instrumentation-mysql@npm:0.39.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.52.0" + "@opentelemetry/semantic-conventions": "npm:^1.22.0" + "@types/mysql": "npm:2.15.22" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10c0/29e447a0287f0c09230b2fe472183866dc3c61872ad898a8586c30341448999fbe6f330b74bd16120b4e258599782d471eb346ced2cdc84bd7a77c52bff30fcd + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-nestjs-core@npm:0.38.0": + version: 0.38.0 + resolution: "@opentelemetry/instrumentation-nestjs-core@npm:0.38.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.52.0" + "@opentelemetry/semantic-conventions": "npm:^1.23.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10c0/1ca49884aa11f97c391dccb9cc24424899cbc7d7ae16ac1d151e882f2df914a05fbb14ecf8a3ca5b998353bce82cf6e36bc653696243a01f55dbc838f73863bf + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-pg@npm:0.42.0": + version: 0.42.0 + resolution: "@opentelemetry/instrumentation-pg@npm:0.42.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.52.0" + "@opentelemetry/semantic-conventions": "npm:^1.22.0" + "@opentelemetry/sql-common": "npm:^0.40.1" + "@types/pg": "npm:8.6.1" + "@types/pg-pool": "npm:2.0.4" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10c0/985579e2f9852dc2684d869c451acce64075980d11a145c7c85c7d8d9efec213b54d1a01ec7335ce9edeac124d888998085a22da3f9738eed4f9a26bbbac6f82 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-redis-4@npm:0.40.0": + version: 0.40.0 + resolution: "@opentelemetry/instrumentation-redis-4@npm:0.40.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.52.0" + "@opentelemetry/redis-common": "npm:^0.36.2" + "@opentelemetry/semantic-conventions": "npm:^1.22.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10c0/d77fc095e8827cd743072e3fdcf0b2cb91ed09e21edb1a437ce9fc948ef670e0f44df740dee0cfe798c537d09370f7099cb5ea9ea0dde7e67927116dc856032a + languageName: node + linkType: hard + +"@opentelemetry/instrumentation@npm:0.52.1, @opentelemetry/instrumentation@npm:^0.49 || ^0.50 || ^0.51 || ^0.52.0, @opentelemetry/instrumentation@npm:^0.52.0, @opentelemetry/instrumentation@npm:^0.52.1": + version: 0.52.1 + resolution: "@opentelemetry/instrumentation@npm:0.52.1" + dependencies: + "@opentelemetry/api-logs": "npm:0.52.1" + "@types/shimmer": "npm:^1.0.2" + import-in-the-middle: "npm:^1.8.1" + require-in-the-middle: "npm:^7.1.1" + semver: "npm:^7.5.2" + shimmer: "npm:^1.2.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10c0/1d4946b470ac31358ba8d81a9f9653a1d705db96ffb8958fef873340c3d3c9699cfd8ff617c313ea8c6a8ece51aa7cf8af37d87a60813c31ed2207e5c14a33ba + languageName: node + linkType: hard + +"@opentelemetry/instrumentation@npm:^0.43.0": + version: 0.43.0 + resolution: "@opentelemetry/instrumentation@npm:0.43.0" + dependencies: + "@types/shimmer": "npm:^1.0.2" + import-in-the-middle: "npm:1.4.2" + require-in-the-middle: "npm:^7.1.1" + semver: "npm:^7.5.2" + shimmer: "npm:^1.2.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10c0/78682576ec5094eaa20e5e6766f93329d495cb4a58cbc56a95752ff15bfc58a511ffe0981b596eb7b893014441f4c748028448b35ff5e37f82b1e1917f9610c7 + languageName: node + linkType: hard + +"@opentelemetry/redis-common@npm:^0.36.2": + version: 0.36.2 + resolution: "@opentelemetry/redis-common@npm:0.36.2" + checksum: 10c0/4cb831628551b9f13dca8d65897e300ff7be0e256b77f455a26fb053bbdfc7997b27d066ab1402ca929e7ac77598e0d593f91762d8af9f798c19ba1524e9d078 + languageName: node + linkType: hard + +"@opentelemetry/resources@npm:1.25.1, @opentelemetry/resources@npm:^1.25.1": + version: 1.25.1 + resolution: "@opentelemetry/resources@npm:1.25.1" + dependencies: + "@opentelemetry/core": "npm:1.25.1" + "@opentelemetry/semantic-conventions": "npm:1.25.1" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10c0/4edbf04945c7647b9af847f2f8abccabb54f4f8935fd75c199dc22879f8b7927ac50fac8e877ef48e81c586a08d63bbfe41c345caf94a8ce2c623fa99bb8e999 + languageName: node + linkType: hard + +"@opentelemetry/sdk-metrics@npm:^1.9.1": + version: 1.25.1 + resolution: "@opentelemetry/sdk-metrics@npm:1.25.1" + dependencies: + "@opentelemetry/core": "npm:1.25.1" + "@opentelemetry/resources": "npm:1.25.1" + lodash.merge: "npm:^4.6.2" + peerDependencies: + "@opentelemetry/api": ">=1.3.0 <1.10.0" + checksum: 10c0/e27d693e2e34dfeadc4632f771a2f7aca7266f7be6d159bb488bb9cdd68edd5a3fca1ecb0cc3a703a61f0f95fbf806d48e5711052519d50d7d235eedb9ce22ae + languageName: node + linkType: hard + +"@opentelemetry/sdk-trace-base@npm:^1.22, @opentelemetry/sdk-trace-base@npm:^1.25.1": + version: 1.25.1 + resolution: "@opentelemetry/sdk-trace-base@npm:1.25.1" + dependencies: + "@opentelemetry/core": "npm:1.25.1" + "@opentelemetry/resources": "npm:1.25.1" + "@opentelemetry/semantic-conventions": "npm:1.25.1" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10c0/bcbc5de75edb8f36a05c7d21699782b4aa100482588d89e318d3f35944d45e776f50f7b353273a0925bc0b3b6e82cbf294cba4cb0792d951148b4ee105280aa2 + languageName: node + linkType: hard + +"@opentelemetry/semantic-conventions@npm:1.25.1, @opentelemetry/semantic-conventions@npm:^1.17.0, @opentelemetry/semantic-conventions@npm:^1.22.0, @opentelemetry/semantic-conventions@npm:^1.23.0, @opentelemetry/semantic-conventions@npm:^1.25.1": + version: 1.25.1 + resolution: "@opentelemetry/semantic-conventions@npm:1.25.1" + checksum: 10c0/fb1d6349e91f142c82931e89e0242215be8248e77919b6faa7e259757e499183546c9b4046de72b053b5222453bc74fff70280d2b4d1229484ba7b2c07f16a3a + languageName: node + linkType: hard + +"@opentelemetry/sql-common@npm:^0.40.1": + version: 0.40.1 + resolution: "@opentelemetry/sql-common@npm:0.40.1" + dependencies: + "@opentelemetry/core": "npm:^1.1.0" + peerDependencies: + "@opentelemetry/api": ^1.1.0 + checksum: 10c0/60a70358f0c94f610e2995333e96b406626d67d03d38ed03b15a3461ad0f8d64afbf6275cca7cb58fe955ecdce832f3ffc9b73f9d88503bba5d2a620bbd6d351 + languageName: node + linkType: hard + +"@pkgjs/parseargs@npm:^0.11.0": + version: 0.11.0 + resolution: "@pkgjs/parseargs@npm:0.11.0" + checksum: 10c0/5bd7576bb1b38a47a7fc7b51ac9f38748e772beebc56200450c4a817d712232b8f1d3ef70532c80840243c657d491cf6a6be1e3a214cff907645819fdc34aadd + languageName: node + linkType: hard + +"@prisma/client@npm:^5.17.0": + version: 5.17.0 + resolution: "@prisma/client@npm:5.17.0" + peerDependencies: + prisma: "*" + peerDependenciesMeta: + prisma: + optional: true + checksum: 10c0/cc6c5e9bfbc2f9a01fdf73e009c42298b8a9fea8c9b19db0089cad84a9ee94c3bb6f66f53f1e2f4b32b3506706bf16d23a8e3bcb4619a8bc76d0812a8382ae63 + languageName: node + linkType: hard + +"@prisma/debug@npm:5.17.0": + version: 5.17.0 + resolution: "@prisma/debug@npm:5.17.0" + checksum: 10c0/10aca89c8cd3a96c7f1153792110f33d96d1875e4af807002b9ca061eda255b1aa21e757b9e7a1690ac0676fb2312c441191cdb357acf45617dd658678984053 + languageName: node + linkType: hard + +"@prisma/engines-version@npm:5.17.0-31.393aa359c9ad4a4bb28630fb5613f9c281cde053": + version: 5.17.0-31.393aa359c9ad4a4bb28630fb5613f9c281cde053 + resolution: "@prisma/engines-version@npm:5.17.0-31.393aa359c9ad4a4bb28630fb5613f9c281cde053" + checksum: 10c0/164b4cd6965da770bcd085fa0596466b092060d19eb8a4ba3402e66bd9b2e813cae417eeca99422b66a3a05a65cfe6d0e0339083b53644acf553ac138693232d + languageName: node + linkType: hard + +"@prisma/engines@npm:5.17.0": + version: 5.17.0 + resolution: "@prisma/engines@npm:5.17.0" + dependencies: + "@prisma/debug": "npm:5.17.0" + "@prisma/engines-version": "npm:5.17.0-31.393aa359c9ad4a4bb28630fb5613f9c281cde053" + "@prisma/fetch-engine": "npm:5.17.0" + "@prisma/get-platform": "npm:5.17.0" + checksum: 10c0/b1d48c39fbe16680947685960be615894ccc1a2ca40263fc6d1ac4599e3100f2f31e71b02bd000c0f3269cd045f38817dfbddd37fefcb8a4dec6155a6df48e2f + languageName: node + linkType: hard + +"@prisma/fetch-engine@npm:5.17.0": + version: 5.17.0 + resolution: "@prisma/fetch-engine@npm:5.17.0" + dependencies: + "@prisma/debug": "npm:5.17.0" + "@prisma/engines-version": "npm:5.17.0-31.393aa359c9ad4a4bb28630fb5613f9c281cde053" + "@prisma/get-platform": "npm:5.17.0" + checksum: 10c0/b5c554e8a637871fd6497e656d67e649d9eea3a06be325b68a686b707c78d200ba9ba20bd76b0a3408e5cb78f6e34bab535ce161174273db377353a01368806e + languageName: node + linkType: hard + +"@prisma/get-platform@npm:5.17.0": + version: 5.17.0 + resolution: "@prisma/get-platform@npm:5.17.0" + dependencies: + "@prisma/debug": "npm:5.17.0" + checksum: 10c0/8687736c6e18737e29544bc1f98653b75b4dcb85c1ffe02686da100e843bb30041dd9d00146a2178517d34b783a650c8b76bdde5029d1675bd28c2be6ee6565a + languageName: node + linkType: hard + +"@prisma/instrumentation@npm:5.16.1": + version: 5.16.1 + resolution: "@prisma/instrumentation@npm:5.16.1" + dependencies: + "@opentelemetry/api": "npm:^1.8" + "@opentelemetry/instrumentation": "npm:^0.49 || ^0.50 || ^0.51 || ^0.52.0" + "@opentelemetry/sdk-trace-base": "npm:^1.22" + checksum: 10c0/6ade66f5786d0a577bb971704e3302b5f9b8b33b0957331c7360693fc198718ef04e070f8eeb737c5fe53beff44b41a1a2644e379a58c11f7ea9fcb155a879fc + languageName: node + linkType: hard + +"@rollup/rollup-android-arm-eabi@npm:4.14.2": + version: 4.14.2 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.14.2" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@rollup/rollup-android-arm64@npm:4.14.2": + version: 4.14.2 + resolution: "@rollup/rollup-android-arm64@npm:4.14.2" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-arm64@npm:4.14.2": + version: 4.14.2 + resolution: "@rollup/rollup-darwin-arm64@npm:4.14.2" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-x64@npm:4.14.2": + version: 4.14.2 + resolution: "@rollup/rollup-darwin-x64@npm:4.14.2" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-gnueabihf@npm:4.14.2": + version: 4.14.2 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.14.2" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-gnu@npm:4.14.2": + version: 4.14.2 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.14.2" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-musl@npm:4.14.2": + version: 4.14.2 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.14.2" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-powerpc64le-gnu@npm:4.14.2": + version: 4.14.2 + resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.14.2" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-gnu@npm:4.14.2": + version: 4.14.2 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.14.2" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-s390x-gnu@npm:4.14.2": + version: 4.14.2 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.14.2" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-gnu@npm:4.14.2": + version: 4.14.2 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.14.2" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-musl@npm:4.14.2": + version: 4.14.2 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.14.2" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-win32-arm64-msvc@npm:4.14.2": + version: 4.14.2 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.14.2" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-ia32-msvc@npm:4.14.2": + version: 4.14.2 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.14.2" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-msvc@npm:4.14.2": + version: 4.14.2 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.14.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@sapphire/async-queue@npm:^1.5.2": + version: 1.5.2 + resolution: "@sapphire/async-queue@npm:1.5.2" + checksum: 10c0/e588a70efe7993a85e3e81103f3f9c3c2c12dd6f3ab2a0f0b763fa412a99780db7a9e1560f188c77945e5e561a40be53120f49b5d83dfa88dd2cf02955e46647 + languageName: node + linkType: hard + +"@sapphire/ratelimits@npm:^2.4.9": + version: 2.4.9 + resolution: "@sapphire/ratelimits@npm:2.4.9" + checksum: 10c0/e2e7da0ab8180914b42807044a14a786474f179e5e5e19b8b920a5ddc255b81263cbadf3d1c7ec60e2fbffc581d4de2e99b294bfec27d35874759d1d1b537cc7 + languageName: node + linkType: hard + +"@sapphire/shapeshift@npm:^3.9.7": + version: 3.9.7 + resolution: "@sapphire/shapeshift@npm:3.9.7" + dependencies: + fast-deep-equal: "npm:^3.1.3" + lodash: "npm:^4.17.21" + checksum: 10c0/29883d4c1986714fd4b2a7e4a58bf30a4598a02bbe031605dd99b766310a98889e2ae6f16bf37f43d91cf5960733dc1a619bfdaa85a96bc0ff9cc5ce57b8e689 + languageName: node + linkType: hard + +"@sapphire/snowflake@npm:3.5.3, @sapphire/snowflake@npm:^3.5.3": + version: 3.5.3 + resolution: "@sapphire/snowflake@npm:3.5.3" + checksum: 10c0/361b6a3e16eea4eb48b2608005ce397d4163065ccd84077059a0f609b97fb9cc52a173d7a64bbfce6f5d1e77be0f9704fd72ef64683a3dc7e455e9b4a1c90db1 + languageName: node + linkType: hard + +"@sapphire/time-utilities@npm:^1.6.0": + version: 1.6.0 + resolution: "@sapphire/time-utilities@npm:1.6.0" + dependencies: + "@sapphire/utilities": "npm:^3.3.0" + checksum: 10c0/d325a2c57da389908493b997b689f5e707f89c420890cfd2293182b6d5b59722df955464cffafedc7909e58585bfff98a41961b1327feb7525cec2cdde3e1dab + languageName: node + linkType: hard + +"@sapphire/timer-manager@npm:^1.0.2": + version: 1.0.2 + resolution: "@sapphire/timer-manager@npm:1.0.2" + checksum: 10c0/0c9fee9a94b5927020aa98dac0fbe7ab903fc8b028383ae28c625f262eec4b65973b5ba5a17d329f72e1abf2092e7655647517d8818378781d5b790cb8b2e62a + languageName: node + linkType: hard + +"@sapphire/utilities@npm:^3.3.0": + version: 3.3.0 + resolution: "@sapphire/utilities@npm:3.3.0" + checksum: 10c0/bec0edc52154801852f7ae063bda6100ea0d1fd9707eebf4dd274aac85c35a3c2930baf7524ba61f2890352d54e5500f337dc4af791d34910a9b455b41924af4 + languageName: node + linkType: hard + +"@sentry/core@npm:8.15.0": + version: 8.15.0 + resolution: "@sentry/core@npm:8.15.0" + dependencies: + "@sentry/types": "npm:8.15.0" + "@sentry/utils": "npm:8.15.0" + checksum: 10c0/2be955c7ad8d3f36e770feb720e2d1d39427ae3e1dad2352ddd31a01025b20c0cc27df45be5d78193ab9aaf9c40231d980717d4dfca3288403a286a23e3fdb4b + languageName: node + linkType: hard + +"@sentry/node@npm:^8.15.0": + version: 8.15.0 + resolution: "@sentry/node@npm:8.15.0" + dependencies: + "@opentelemetry/api": "npm:^1.9.0" + "@opentelemetry/context-async-hooks": "npm:^1.25.1" + "@opentelemetry/core": "npm:^1.25.1" + "@opentelemetry/instrumentation": "npm:^0.52.1" + "@opentelemetry/instrumentation-connect": "npm:0.37.0" + "@opentelemetry/instrumentation-express": "npm:0.40.1" + "@opentelemetry/instrumentation-fastify": "npm:0.37.0" + "@opentelemetry/instrumentation-graphql": "npm:0.41.0" + "@opentelemetry/instrumentation-hapi": "npm:0.39.0" + "@opentelemetry/instrumentation-http": "npm:0.52.1" + "@opentelemetry/instrumentation-ioredis": "npm:0.41.0" + "@opentelemetry/instrumentation-koa": "npm:0.41.0" + "@opentelemetry/instrumentation-mongodb": "npm:0.45.0" + "@opentelemetry/instrumentation-mongoose": "npm:0.39.0" + "@opentelemetry/instrumentation-mysql": "npm:0.39.0" + "@opentelemetry/instrumentation-mysql2": "npm:0.39.0" + "@opentelemetry/instrumentation-nestjs-core": "npm:0.38.0" + "@opentelemetry/instrumentation-pg": "npm:0.42.0" + "@opentelemetry/instrumentation-redis-4": "npm:0.40.0" + "@opentelemetry/resources": "npm:^1.25.1" + "@opentelemetry/sdk-trace-base": "npm:^1.25.1" + "@opentelemetry/semantic-conventions": "npm:^1.25.1" + "@prisma/instrumentation": "npm:5.16.1" + "@sentry/core": "npm:8.15.0" + "@sentry/opentelemetry": "npm:8.15.0" + "@sentry/types": "npm:8.15.0" + "@sentry/utils": "npm:8.15.0" + opentelemetry-instrumentation-fetch-node: "npm:1.2.0" + dependenciesMeta: + opentelemetry-instrumentation-fetch-node: + optional: true + checksum: 10c0/ace05427c15cfd755680d3f6722927f5c608b3f9f98b9649999c9e485c2141bfe2aaeb8c0eb71b332ed0a36c23ecdee4dd1dcdaaea37d7583809a2df221a468d + languageName: node + linkType: hard + +"@sentry/opentelemetry@npm:8.15.0": + version: 8.15.0 + resolution: "@sentry/opentelemetry@npm:8.15.0" + dependencies: + "@sentry/core": "npm:8.15.0" + "@sentry/types": "npm:8.15.0" + "@sentry/utils": "npm:8.15.0" + peerDependencies: + "@opentelemetry/api": ^1.9.0 + "@opentelemetry/core": ^1.25.1 + "@opentelemetry/instrumentation": ^0.52.1 + "@opentelemetry/sdk-trace-base": ^1.25.1 + "@opentelemetry/semantic-conventions": ^1.25.1 + checksum: 10c0/3f9b268223a6fc468096d93107d5eff3626b88b5506b73a026287f7fb93b903eac0de822d106a216dfd6cf4b230788a68ec09261a7a0964d361c787af4d3aff5 + languageName: node + linkType: hard + +"@sentry/types@npm:8.15.0": + version: 8.15.0 + resolution: "@sentry/types@npm:8.15.0" + checksum: 10c0/b51a043e8a259f42cf4615852347b33eb2fafdaffede48246d266b8fa92418b83a2408818e2ac89155445b42119d373d557345fbf470b4500eeaeae01e64dca7 + languageName: node + linkType: hard + +"@sentry/utils@npm:8.15.0": + version: 8.15.0 + resolution: "@sentry/utils@npm:8.15.0" + dependencies: + "@sentry/types": "npm:8.15.0" + checksum: 10c0/a7de61301d3358afde40cd7684bcb4c15248e8cab0af77b541cb25a759ad32b7f83b681106ae7895eb1669117a70056867c4bcca644097344f0d919a1f2ab3ea + languageName: node + linkType: hard + +"@types/accepts@npm:*": + version: 1.3.7 + resolution: "@types/accepts@npm:1.3.7" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/7b21efc78b98ed57063ac31588f871f11501c080cd1201ca3743cf02ee0aee74bdb5a634183bc0987dc8dc582b26316789fd203650319ccc89a66cf88311d64f + languageName: node + linkType: hard + +"@types/body-parser@npm:*": + version: 1.19.5 + resolution: "@types/body-parser@npm:1.19.5" + dependencies: + "@types/connect": "npm:*" + "@types/node": "npm:*" + checksum: 10c0/aebeb200f25e8818d8cf39cd0209026750d77c9b85381cdd8deeb50913e4d18a1ebe4b74ca9b0b4d21952511eeaba5e9fbbf739b52731a2061e206ec60d568df + languageName: node + linkType: hard + +"@types/connect@npm:*": + version: 3.4.38 + resolution: "@types/connect@npm:3.4.38" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/2e1cdba2c410f25649e77856505cd60223250fa12dff7a503e492208dbfdd25f62859918f28aba95315251fd1f5e1ffbfca1e25e73037189ab85dd3f8d0a148c + languageName: node + linkType: hard + +"@types/connect@npm:3.4.36": + version: 3.4.36 + resolution: "@types/connect@npm:3.4.36" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/0dd8fcf576e178e69cbc00d47be69d3198dca4d86734a00fc55de0df147982e0a5f34592117571c5979e92ce8f3e0596e31aa454496db8a43ab90c5ab1068f40 + languageName: node + linkType: hard + +"@types/content-disposition@npm:*": + version: 0.5.8 + resolution: "@types/content-disposition@npm:0.5.8" + checksum: 10c0/f10baeab2ec44579012c1170763851687e740ea30531a80cd7a403475730ce7d7ead4f88927cea6970cc2d5e74fa7af38cdf4f039c5f115fba1bb98ec0014977 + languageName: node + linkType: hard + +"@types/cookies@npm:*": + version: 0.9.0 + resolution: "@types/cookies@npm:0.9.0" + dependencies: + "@types/connect": "npm:*" + "@types/express": "npm:*" + "@types/keygrip": "npm:*" + "@types/node": "npm:*" + checksum: 10c0/ce95c1968532af674185efd4092cbdec8d5d3bda72f729e512bf37fa77877f466ad4bd5f00fca299f94c6e3d2a3875744ae5a705ffc5113183f5e46b76d8846a + languageName: node + linkType: hard + +"@types/estree@npm:1.0.5, @types/estree@npm:^1.0.0": + version: 1.0.5 + resolution: "@types/estree@npm:1.0.5" + checksum: 10c0/b3b0e334288ddb407c7b3357ca67dbee75ee22db242ca7c56fe27db4e1a31989cb8af48a84dd401deb787fe10cc6b2ab1ee82dc4783be87ededbe3d53c79c70d + languageName: node + linkType: hard + +"@types/express-serve-static-core@npm:^4.17.33": + version: 4.19.5 + resolution: "@types/express-serve-static-core@npm:4.19.5" + dependencies: + "@types/node": "npm:*" + "@types/qs": "npm:*" + "@types/range-parser": "npm:*" + "@types/send": "npm:*" + checksum: 10c0/ba8d8d976ab797b2602c60e728802ff0c98a00f13d420d82770f3661b67fa36ea9d3be0b94f2ddd632afe1fbc6e41620008b01db7e4fabdd71a2beb5539b0725 + languageName: node + linkType: hard + +"@types/express@npm:*": + version: 4.17.21 + resolution: "@types/express@npm:4.17.21" + dependencies: + "@types/body-parser": "npm:*" + "@types/express-serve-static-core": "npm:^4.17.33" + "@types/qs": "npm:*" + "@types/serve-static": "npm:*" + checksum: 10c0/12e562c4571da50c7d239e117e688dc434db1bac8be55613294762f84fd77fbd0658ccd553c7d3ab02408f385bc93980992369dd30e2ecd2c68c358e6af8fabf + languageName: node + linkType: hard + +"@types/http-assert@npm:*": + version: 1.5.5 + resolution: "@types/http-assert@npm:1.5.5" + checksum: 10c0/02e7ba584d6d14bdb4dad05dd36ecbc4a2f4209472287e6d558e222c93182214445a0c6cd096f114bfc88446be03d82ef6db24ecda13922b0d697918c76b4067 + languageName: node + linkType: hard + +"@types/http-errors@npm:*": + version: 2.0.4 + resolution: "@types/http-errors@npm:2.0.4" + checksum: 10c0/494670a57ad4062fee6c575047ad5782506dd35a6b9ed3894cea65830a94367bd84ba302eb3dde331871f6d70ca287bfedb1b2cf658e6132cd2cbd427ab56836 + languageName: node + linkType: hard + +"@types/keygrip@npm:*": + version: 1.0.6 + resolution: "@types/keygrip@npm:1.0.6" + checksum: 10c0/1045a79913259f539ac1d04384ea8f61cf29f1d299040eb4b67d92304ec3bcea59b7e4b83cf95a73aa251ff62e55924e380d0c563a21fe8f6e91de20cc610386 + languageName: node + linkType: hard + +"@types/koa-compose@npm:*": + version: 3.2.8 + resolution: "@types/koa-compose@npm:3.2.8" + dependencies: + "@types/koa": "npm:*" + checksum: 10c0/f2bfb7376c1e9075e8df7a46a5fce073159b01b94ec7dcca6e9f68627d48ea86a726bcfbd06491e1c99f68c0f27b8174b498081f9a3e4f976694452b5d0b5f01 + languageName: node + linkType: hard + +"@types/koa@npm:*": + version: 2.15.0 + resolution: "@types/koa@npm:2.15.0" + dependencies: + "@types/accepts": "npm:*" + "@types/content-disposition": "npm:*" + "@types/cookies": "npm:*" + "@types/http-assert": "npm:*" + "@types/http-errors": "npm:*" + "@types/keygrip": "npm:*" + "@types/koa-compose": "npm:*" + "@types/node": "npm:*" + checksum: 10c0/3fd591e25ecffc32ffa7cb152d2c5caeccefe5a72cb09d187102d8f41101bdaeeb802a07a6672eac58f805fa59892e79c1cc203ca7b27b0de75d7eac508c2b47 + languageName: node + linkType: hard + +"@types/koa@npm:2.14.0": + version: 2.14.0 + resolution: "@types/koa@npm:2.14.0" + dependencies: + "@types/accepts": "npm:*" + "@types/content-disposition": "npm:*" + "@types/cookies": "npm:*" + "@types/http-assert": "npm:*" + "@types/http-errors": "npm:*" + "@types/keygrip": "npm:*" + "@types/koa-compose": "npm:*" + "@types/node": "npm:*" + checksum: 10c0/783536ea905244ec8edcda5f6063f34b3f4bfe16ff75f4d8faaa9b3ce58455ff140b20e63064a31491c1e7f34de4e869bce410fb116012887b9792c98591f744 + languageName: node + linkType: hard + +"@types/koa__router@npm:12.0.3": + version: 12.0.3 + resolution: "@types/koa__router@npm:12.0.3" + dependencies: + "@types/koa": "npm:*" + checksum: 10c0/f9e2c360bed2f326df2d2cb3d1dd508a14360ae2299516579bed9690084f92056df09f28946d3245e9ef67ee0688af4a27990036f0e9afc9eb31241852884612 + languageName: node + linkType: hard + +"@types/lodash@npm:^4.14.195": + version: 4.14.195 + resolution: "@types/lodash@npm:4.14.195" + checksum: 10c0/6d733276df592614a0943a0053056140398b3c263cdf2557d4301b3a47b07ff561926cb9339a4725acbc7d8766f91ded218df11e0a4288cee369eafb5141d94d + languageName: node + linkType: hard + +"@types/mime@npm:^1": + version: 1.3.5 + resolution: "@types/mime@npm:1.3.5" + checksum: 10c0/c2ee31cd9b993804df33a694d5aa3fa536511a49f2e06eeab0b484fef59b4483777dbb9e42a4198a0809ffbf698081fdbca1e5c2218b82b91603dfab10a10fbc + languageName: node + linkType: hard + +"@types/mysql@npm:2.15.22": + version: 2.15.22 + resolution: "@types/mysql@npm:2.15.22" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/872e7389985c954e7bf507cbe8f62f33c779d28e456b711d18133eaf9636487d0521e7f2c32e22eae0aa71f2d1c6e10d6212fbace50f73ab0a803949cc71f2cc + languageName: node + linkType: hard + +"@types/node-cron@npm:^3.0.7": + version: 3.0.7 + resolution: "@types/node-cron@npm:3.0.7" + checksum: 10c0/870f78035794e196c18ff06545dc2ef084f1e8fcce60614223a73afddc84e5055a9f14c174247141ce9f077011baeb9572627a51a0706bdb37e9ef90eaa2f846 + languageName: node + linkType: hard + +"@types/node-fetch@npm:^2.6.1": + version: 2.6.1 + resolution: "@types/node-fetch@npm:2.6.1" + dependencies: + "@types/node": "npm:*" + form-data: "npm:^3.0.0" + checksum: 10c0/033945215fa6b36d5597b21bbdd7d946a751dcd066f8fa71e5f5ab5698390bc2e76e38932ec6ac709def39b6371dfd61844e6804409c7ba812e24d1c820fbff8 + languageName: node + linkType: hard + +"@types/node@npm:*": + version: 16.3.3 + resolution: "@types/node@npm:16.3.3" + checksum: 10c0/fb864e8fdb55914bbe271ea1bae83aacd4ea0eb7ddd4f4668002c100f9beb0b14d1426472ade2f2c154853456c5e6b86a5be6dcaf534cc044c1c33e2474432f7 + languageName: node + linkType: hard + +"@types/node@npm:^20.14.9": + version: 20.14.9 + resolution: "@types/node@npm:20.14.9" + dependencies: + undici-types: "npm:~5.26.4" + checksum: 10c0/911ffa444dc032897f4a23ed580c67903bd38ea1c5ec99b1d00fa10b83537a3adddef8e1f29710cbdd8e556a61407ed008e06537d834e48caf449ce59f87d387 + languageName: node + linkType: hard + +"@types/pg-pool@npm:2.0.4": + version: 2.0.4 + resolution: "@types/pg-pool@npm:2.0.4" + dependencies: + "@types/pg": "npm:*" + checksum: 10c0/1c6b83e1c33c66e6b1ee11332ecf74ad393ba2a3966d5ee7ffaa40ddfe1f3cb4df224263515967d39101fa13b10c1f70da45795ca6eaeeea7d8e9edeeb58093f + languageName: node + linkType: hard + +"@types/pg@npm:*": + version: 8.11.6 + resolution: "@types/pg@npm:8.11.6" + dependencies: + "@types/node": "npm:*" + pg-protocol: "npm:*" + pg-types: "npm:^4.0.1" + checksum: 10c0/e68e057d9500b25cd776f4fcc547b4880c4f3b0c7b6e03c8a0e5e262b6189dd7a00f4edc8937ffc55a9f6a136a78d7e4a9b6bbe6a46122a95c134f7be66f6842 + languageName: node + linkType: hard + +"@types/pg@npm:8.6.1": + version: 8.6.1 + resolution: "@types/pg@npm:8.6.1" + dependencies: + "@types/node": "npm:*" + pg-protocol: "npm:*" + pg-types: "npm:^2.2.0" + checksum: 10c0/8d16660c9a4f050d6d5e391c59f9a62e9d377a2a6a7eb5865f8828082dbdfeab700fd707e585f42d67b29e796b32863aea5bd6d5cbb8ceda2d598da5d0c61693 + languageName: node + linkType: hard + +"@types/qs@npm:*": + version: 6.9.15 + resolution: "@types/qs@npm:6.9.15" + checksum: 10c0/49c5ff75ca3adb18a1939310042d273c9fc55920861bd8e5100c8a923b3cda90d759e1a95e18334092da1c8f7b820084687770c83a1ccef04fb2c6908117c823 + languageName: node + linkType: hard + +"@types/range-parser@npm:*": + version: 1.2.7 + resolution: "@types/range-parser@npm:1.2.7" + checksum: 10c0/361bb3e964ec5133fa40644a0b942279ed5df1949f21321d77de79f48b728d39253e5ce0408c9c17e4e0fd95ca7899da36841686393b9f7a1e209916e9381a3c + languageName: node + linkType: hard + +"@types/send@npm:*": + version: 0.17.4 + resolution: "@types/send@npm:0.17.4" + dependencies: + "@types/mime": "npm:^1" + "@types/node": "npm:*" + checksum: 10c0/7f17fa696cb83be0a104b04b424fdedc7eaba1c9a34b06027239aba513b398a0e2b7279778af521f516a397ced417c96960e5f50fcfce40c4bc4509fb1a5883c + languageName: node + linkType: hard + +"@types/serve-static@npm:*": + version: 1.15.7 + resolution: "@types/serve-static@npm:1.15.7" + dependencies: + "@types/http-errors": "npm:*" + "@types/node": "npm:*" + "@types/send": "npm:*" + checksum: 10c0/26ec864d3a626ea627f8b09c122b623499d2221bbf2f470127f4c9ebfe92bd8a6bb5157001372d4c4bd0dd37a1691620217d9dc4df5aa8f779f3fd996b1c60ae + languageName: node + linkType: hard + +"@types/shimmer@npm:^1.0.2": + version: 1.0.5 + resolution: "@types/shimmer@npm:1.0.5" + checksum: 10c0/bbdcfebd46732267a7f861ca2e25b8f113ca303bfb1ebf891aeeb509b0507f816f95611fa82acdd4d1d534472a54754f6be3301b2cf77659654671e3e31318ec + languageName: node + linkType: hard + +"@types/ws@npm:^8.5.10": + version: 8.5.10 + resolution: "@types/ws@npm:8.5.10" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/e9af279b984c4a04ab53295a40aa95c3e9685f04888df5c6920860d1dd073fcc57c7bd33578a04b285b2c655a0b52258d34bee0a20569dca8defb8393e1e5d29 + languageName: node + linkType: hard + +"@vitest/coverage-v8@npm:^2.0.3": + version: 2.0.3 + resolution: "@vitest/coverage-v8@npm:2.0.3" + dependencies: + "@ampproject/remapping": "npm:^2.3.0" + "@bcoe/v8-coverage": "npm:^0.2.3" + debug: "npm:^4.3.5" + istanbul-lib-coverage: "npm:^3.2.2" + istanbul-lib-report: "npm:^3.0.1" + istanbul-lib-source-maps: "npm:^5.0.6" + istanbul-reports: "npm:^3.1.7" + magic-string: "npm:^0.30.10" + magicast: "npm:^0.3.4" + std-env: "npm:^3.7.0" + strip-literal: "npm:^2.1.0" + test-exclude: "npm:^7.0.1" + tinyrainbow: "npm:^1.2.0" + peerDependencies: + vitest: 2.0.3 + checksum: 10c0/ac3bbe2ff7cb41a71d22d7174347bba81b789b8293da54a1cf266eef12b9b0f92a04ef3cdaddff176b11fa8db9ec3cbdc423bf51a5e847714a643a38b3da370f + languageName: node + linkType: hard + +"@vitest/expect@npm:2.0.3": + version: 2.0.3 + resolution: "@vitest/expect@npm:2.0.3" + dependencies: + "@vitest/spy": "npm:2.0.3" + "@vitest/utils": "npm:2.0.3" + chai: "npm:^5.1.1" + tinyrainbow: "npm:^1.2.0" + checksum: 10c0/bc8dead850a8aeb84a0d5d8620e1437752cbfe10908c2d5ec9f80fc6d9c387d70c964abfd2d6caf76da2882022c0dd05b0fa09b7c2a44d65abdde2b6c73517fe + languageName: node + linkType: hard + +"@vitest/pretty-format@npm:2.0.3, @vitest/pretty-format@npm:^2.0.3": + version: 2.0.3 + resolution: "@vitest/pretty-format@npm:2.0.3" + dependencies: + tinyrainbow: "npm:^1.2.0" + checksum: 10c0/217fd176fa4d1e64e04bc6a187d146381e99921f46007f98f7132d0e31e2c14b9c6d050a150331b3368ee8004bbeab5b1b7d477522a4e4d71ad822d046debc16 + languageName: node + linkType: hard + +"@vitest/runner@npm:2.0.3": + version: 2.0.3 + resolution: "@vitest/runner@npm:2.0.3" + dependencies: + "@vitest/utils": "npm:2.0.3" + pathe: "npm:^1.1.2" + checksum: 10c0/efbf646457c29268f0d370985d8cbfcfc7d181693dfc2e061dd05ce911f43592957f2c866cde1b5b2e3078ae5d74b94dc28453e1c70b80e8467440223431e863 + languageName: node + linkType: hard + +"@vitest/snapshot@npm:2.0.3": + version: 2.0.3 + resolution: "@vitest/snapshot@npm:2.0.3" + dependencies: + "@vitest/pretty-format": "npm:2.0.3" + magic-string: "npm:^0.30.10" + pathe: "npm:^1.1.2" + checksum: 10c0/dc7e2e8f60d40c308c487effe2cd94c42bffa795c2d8c740c30b880b451637763891609a052afe29f0c9872e71141d439cb03118595e4a461fe6b4877ae99878 + languageName: node + linkType: hard + +"@vitest/spy@npm:2.0.3": + version: 2.0.3 + resolution: "@vitest/spy@npm:2.0.3" + dependencies: + tinyspy: "npm:^3.0.0" + checksum: 10c0/4780aeed692c52756d70735b633ad58f201b2b8729b9e46c4cf968b8e4174e2c2cddd099de669019771bcd8e1ca32d0b9fa42d962e431fdf473b62393b9d2a0a + languageName: node + linkType: hard + +"@vitest/utils@npm:2.0.3": + version: 2.0.3 + resolution: "@vitest/utils@npm:2.0.3" + dependencies: + "@vitest/pretty-format": "npm:2.0.3" + estree-walker: "npm:^3.0.3" + loupe: "npm:^3.1.1" + tinyrainbow: "npm:^1.2.0" + checksum: 10c0/41b64c07814e7d576ebe7d11d277eb104a2aafb986497855a59f641b45fa53a30a2bfea525cd913e91b695f444a7a48b1f1e5909c27d5a989b0aea68f2242bd9 + languageName: node + linkType: hard + +"@vladfrangu/async_event_emitter@npm:^2.2.4": + version: 2.4.1 + resolution: "@vladfrangu/async_event_emitter@npm:2.4.1" + checksum: 10c0/b2d58d7b30a2b7d94043872b7faab5bc6d2b2f358180a0eccf2c368be6a0f2000053551927d7cd6c51f7e3531e45a6e3409e21e5ce55807e9519ef1247ab580d + languageName: node + linkType: hard + +"abbrev@npm:^2.0.0": + version: 2.0.0 + resolution: "abbrev@npm:2.0.0" + checksum: 10c0/f742a5a107473946f426c691c08daba61a1d15942616f300b5d32fd735be88fef5cba24201757b6c407fd564555fb48c751cfa33519b2605c8a7aadd22baf372 + languageName: node + linkType: hard + +"acorn-import-assertions@npm:^1.9.0": + version: 1.9.0 + resolution: "acorn-import-assertions@npm:1.9.0" + peerDependencies: + acorn: ^8 + checksum: 10c0/3b4a194e128efdc9b86c2b1544f623aba4c1aa70d638f8ab7dc3971a5b4aa4c57bd62f99af6e5325bb5973c55863b4112e708a6f408bad7a138647ca72283afe + languageName: node + linkType: hard + +"acorn-import-attributes@npm:^1.9.5": + version: 1.9.5 + resolution: "acorn-import-attributes@npm:1.9.5" + peerDependencies: + acorn: ^8 + checksum: 10c0/5926eaaead2326d5a86f322ff1b617b0f698aa61dc719a5baa0e9d955c9885cc71febac3fb5bacff71bbf2c4f9c12db2056883c68c53eb962c048b952e1e013d + languageName: node + linkType: hard + +"acorn@npm:^8.8.2": + version: 8.8.2 + resolution: "acorn@npm:8.8.2" + bin: + acorn: bin/acorn + checksum: 10c0/b5c54e736af5ed753911c6752fafd02d0a74cf4d55be606bd81fe71faba4f986dc090952329931ac2aba165803fd0005c59eeef08f9c6c689e8dc420031f3df0 + languageName: node + linkType: hard + +"agent-base@npm:^7.0.2, agent-base@npm:^7.1.0, agent-base@npm:^7.1.1": + version: 7.1.1 + resolution: "agent-base@npm:7.1.1" + dependencies: + debug: "npm:^4.3.4" + checksum: 10c0/e59ce7bed9c63bf071a30cc471f2933862044c97fd9958967bfe22521d7a0f601ce4ed5a8c011799d0c726ca70312142ae193bbebb60f576b52be19d4a363b50 + languageName: node + linkType: hard + +"aggregate-error@npm:^3.0.0": + version: 3.1.0 + resolution: "aggregate-error@npm:3.1.0" + dependencies: + clean-stack: "npm:^2.0.0" + indent-string: "npm:^4.0.0" + checksum: 10c0/a42f67faa79e3e6687a4923050e7c9807db3848a037076f791d10e092677d65c1d2d863b7848560699f40fc0502c19f40963fb1cd1fb3d338a7423df8e45e039 + languageName: node + linkType: hard + +"ansi-regex@npm:^5.0.1": + version: 5.0.1 + resolution: "ansi-regex@npm:5.0.1" + checksum: 10c0/9a64bb8627b434ba9327b60c027742e5d17ac69277960d041898596271d992d4d52ba7267a63ca10232e29f6107fc8a835f6ce8d719b88c5f8493f8254813737 + languageName: node + linkType: hard + +"ansi-regex@npm:^6.0.1": + version: 6.0.1 + resolution: "ansi-regex@npm:6.0.1" + checksum: 10c0/cbe16dbd2c6b2735d1df7976a7070dd277326434f0212f43abf6d87674095d247968209babdaad31bb00882fa68807256ba9be340eec2f1004de14ca75f52a08 + languageName: node + linkType: hard + +"ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": + version: 4.3.0 + resolution: "ansi-styles@npm:4.3.0" + dependencies: + color-convert: "npm:^2.0.1" + checksum: 10c0/895a23929da416f2bd3de7e9cb4eabd340949328ab85ddd6e484a637d8f6820d485f53933446f5291c3b760cbc488beb8e88573dd0f9c7daf83dccc8fe81b041 + languageName: node + linkType: hard + +"ansi-styles@npm:^6.1.0": + version: 6.2.1 + resolution: "ansi-styles@npm:6.2.1" + checksum: 10c0/5d1ec38c123984bcedd996eac680d548f31828bd679a66db2bdf11844634dde55fec3efa9c6bb1d89056a5e79c1ac540c4c784d592ea1d25028a92227d2f2d5c + languageName: node + linkType: hard + +"anymatch@npm:~3.1.2": + version: 3.1.3 + resolution: "anymatch@npm:3.1.3" + dependencies: + normalize-path: "npm:^3.0.0" + picomatch: "npm:^2.0.4" + checksum: 10c0/57b06ae984bc32a0d22592c87384cd88fe4511b1dd7581497831c56d41939c8a001b28e7b853e1450f2bf61992dfcaa8ae2d0d161a0a90c4fb631ef07098fbac + languageName: node + linkType: hard + +"ascii-table3@npm:^0.9.0": + version: 0.9.0 + resolution: "ascii-table3@npm:0.9.0" + dependencies: + printable-characters: "npm:^1.0.42" + checksum: 10c0/b9651142923d5ae0276e2a754ad398774dabd98939d1c8c5f11fe3082a2cd2610f62af86fa94c63365342a076949251a4cfef22e485f98e863df18e3c1d48d4d + languageName: node + linkType: hard + +"assertion-error@npm:^2.0.1": + version: 2.0.1 + resolution: "assertion-error@npm:2.0.1" + checksum: 10c0/bbbcb117ac6480138f8c93cf7f535614282dea9dc828f540cdece85e3c665e8f78958b96afac52f29ff883c72638e6a87d469ecc9fe5bc902df03ed24a55dba8 + languageName: node + linkType: hard + +"asynckit@npm:^0.4.0": + version: 0.4.0 + resolution: "asynckit@npm:0.4.0" + checksum: 10c0/d73e2ddf20c4eb9337e1b3df1a0f6159481050a5de457c55b14ea2e5cb6d90bb69e004c9af54737a5ee0917fcf2c9e25de67777bbe58261847846066ba75bc9d + languageName: node + linkType: hard + +"atomic-sleep@npm:^1.0.0": + version: 1.0.0 + resolution: "atomic-sleep@npm:1.0.0" + checksum: 10c0/e329a6665512736a9bbb073e1761b4ec102f7926cce35037753146a9db9c8104f5044c1662e4a863576ce544fb8be27cd2be6bc8c1a40147d03f31eb1cfb6e8a + languageName: node + linkType: hard + +"balanced-match@npm:^1.0.0": + version: 1.0.2 + resolution: "balanced-match@npm:1.0.2" + checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee + languageName: node + linkType: hard + +"base64-js@npm:^1.3.1": + version: 1.5.1 + resolution: "base64-js@npm:1.5.1" + checksum: 10c0/f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf + languageName: node + linkType: hard + +"binary-extensions@npm:^2.0.0": + version: 2.3.0 + resolution: "binary-extensions@npm:2.3.0" + checksum: 10c0/75a59cafc10fb12a11d510e77110c6c7ae3f4ca22463d52487709ca7f18f69d886aa387557cc9864fbdb10153d0bdb4caacabf11541f55e89ed6e18d12ece2b5 + languageName: node + linkType: hard + +"bl@npm:^4.1.0": + version: 4.1.0 + resolution: "bl@npm:4.1.0" + dependencies: + buffer: "npm:^5.5.0" + inherits: "npm:^2.0.4" + readable-stream: "npm:^3.4.0" + checksum: 10c0/02847e1d2cb089c9dc6958add42e3cdeaf07d13f575973963335ac0fdece563a50ac770ac4c8fa06492d2dd276f6cc3b7f08c7cd9c7a7ad0f8d388b2a28def5f + languageName: node + linkType: hard + +"brace-expansion@npm:^1.1.7": + version: 1.1.11 + resolution: "brace-expansion@npm:1.1.11" + dependencies: + balanced-match: "npm:^1.0.0" + concat-map: "npm:0.0.1" + checksum: 10c0/695a56cd058096a7cb71fb09d9d6a7070113c7be516699ed361317aca2ec169f618e28b8af352e02ab4233fb54eb0168460a40dc320bab0034b36ab59aaad668 + languageName: node + linkType: hard + +"brace-expansion@npm:^2.0.1": + version: 2.0.1 + resolution: "brace-expansion@npm:2.0.1" + dependencies: + balanced-match: "npm:^1.0.0" + checksum: 10c0/b358f2fe060e2d7a87aa015979ecea07f3c37d4018f8d6deb5bd4c229ad3a0384fe6029bb76cd8be63c81e516ee52d1a0673edbe2023d53a5191732ae3c3e49f + languageName: node + linkType: hard + +"braces@npm:^3.0.1, braces@npm:~3.0.2": + version: 3.0.3 + resolution: "braces@npm:3.0.3" + dependencies: + fill-range: "npm:^7.1.1" + checksum: 10c0/7c6dfd30c338d2997ba77500539227b9d1f85e388a5f43220865201e407e076783d0881f2d297b9f80951b4c957fcf0b51c1d2d24227631643c3f7c284b0aa04 + languageName: node + linkType: hard + +"buffer@npm:^5.5.0": + version: 5.7.1 + resolution: "buffer@npm:5.7.1" + dependencies: + base64-js: "npm:^1.3.1" + ieee754: "npm:^1.1.13" + checksum: 10c0/27cac81cff434ed2876058d72e7c4789d11ff1120ef32c9de48f59eab58179b66710c488987d295ae89a228f835fc66d088652dffeb8e3ba8659f80eb091d55e + languageName: node + linkType: hard + +"bufferutil@npm:^4.0.8": + version: 4.0.8 + resolution: "bufferutil@npm:4.0.8" + dependencies: + node-gyp: "npm:latest" + node-gyp-build: "npm:^4.3.0" + checksum: 10c0/36cdc5b53a38d9f61f89fdbe62029a2ebcd020599862253fefebe31566155726df9ff961f41b8c97b02b4c12b391ef97faf94e2383392654cf8f0ed68f76e47c + languageName: node + linkType: hard + +"cac@npm:^6.7.14": + version: 6.7.14 + resolution: "cac@npm:6.7.14" + checksum: 10c0/4ee06aaa7bab8981f0d54e5f5f9d4adcd64058e9697563ce336d8a3878ed018ee18ebe5359b2430eceae87e0758e62ea2019c3f52ae6e211b1bd2e133856cd10 + languageName: node + linkType: hard + +"cacache@npm:^18.0.0": + version: 18.0.3 + resolution: "cacache@npm:18.0.3" + dependencies: + "@npmcli/fs": "npm:^3.1.0" + fs-minipass: "npm:^3.0.0" + glob: "npm:^10.2.2" + lru-cache: "npm:^10.0.1" + minipass: "npm:^7.0.3" + minipass-collect: "npm:^2.0.1" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + p-map: "npm:^4.0.0" + ssri: "npm:^10.0.0" + tar: "npm:^6.1.11" + unique-filename: "npm:^3.0.0" + checksum: 10c0/dfda92840bb371fb66b88c087c61a74544363b37a265023223a99965b16a16bbb87661fe4948718d79df6e0cc04e85e62784fbcf1832b2a5e54ff4c46fbb45b7 + languageName: node + linkType: hard + +"chai@npm:^5.1.1": + version: 5.1.1 + resolution: "chai@npm:5.1.1" + dependencies: + assertion-error: "npm:^2.0.1" + check-error: "npm:^2.1.1" + deep-eql: "npm:^5.0.1" + loupe: "npm:^3.1.0" + pathval: "npm:^2.0.0" + checksum: 10c0/e7f00e5881e3d5224f08fe63966ed6566bd9fdde175863c7c16dd5240416de9b34c4a0dd925f4fd64ad56256ca6507d32cf6131c49e1db65c62578eb31d4566c + languageName: node + linkType: hard + +"chalk@npm:^4.1.0, chalk@npm:^4.1.2": + version: 4.1.2 + resolution: "chalk@npm:4.1.2" + dependencies: + ansi-styles: "npm:^4.1.0" + supports-color: "npm:^7.1.0" + checksum: 10c0/4a3fef5cc34975c898ffe77141450f679721df9dde00f6c304353fa9c8b571929123b26a0e4617bde5018977eb655b31970c297b91b63ee83bb82aeb04666880 + languageName: node + linkType: hard + +"check-error@npm:^2.1.1": + version: 2.1.1 + resolution: "check-error@npm:2.1.1" + checksum: 10c0/979f13eccab306cf1785fa10941a590b4e7ea9916ea2a4f8c87f0316fc3eab07eabefb6e587424ef0f88cbcd3805791f172ea739863ca3d7ce2afc54641c7f0e + languageName: node + linkType: hard + +"chokidar@npm:^3.5.2": + version: 3.6.0 + resolution: "chokidar@npm:3.6.0" + dependencies: + anymatch: "npm:~3.1.2" + braces: "npm:~3.0.2" + fsevents: "npm:~2.3.2" + glob-parent: "npm:~5.1.2" + is-binary-path: "npm:~2.1.0" + is-glob: "npm:~4.0.1" + normalize-path: "npm:~3.0.0" + readdirp: "npm:~3.6.0" + dependenciesMeta: + fsevents: + optional: true + checksum: 10c0/8361dcd013f2ddbe260eacb1f3cb2f2c6f2b0ad118708a343a5ed8158941a39cb8fb1d272e0f389712e74ee90ce8ba864eece9e0e62b9705cb468a2f6d917462 + languageName: node + linkType: hard + +"chownr@npm:^2.0.0": + version: 2.0.0 + resolution: "chownr@npm:2.0.0" + checksum: 10c0/594754e1303672171cc04e50f6c398ae16128eb134a88f801bf5354fd96f205320f23536a045d9abd8b51024a149696e51231565891d4efdab8846021ecf88e6 + languageName: node + linkType: hard + +"cjs-module-lexer@npm:^1.2.2": + version: 1.3.1 + resolution: "cjs-module-lexer@npm:1.3.1" + checksum: 10c0/cd98fbf3c7f4272fb0ebf71d08d0c54bc75ce0e30b9d186114e15b4ba791f3d310af65a339eea2a0318599af2818cdd8886d353b43dfab94468f72987397ad16 + languageName: node + linkType: hard + +"clean-stack@npm:^2.0.0": + version: 2.2.0 + resolution: "clean-stack@npm:2.2.0" + checksum: 10c0/1f90262d5f6230a17e27d0c190b09d47ebe7efdd76a03b5a1127863f7b3c9aec4c3e6c8bb3a7bbf81d553d56a1fd35728f5a8ef4c63f867ac8d690109742a8c1 + languageName: node + linkType: hard + +"cli-cursor@npm:^3.1.0": + version: 3.1.0 + resolution: "cli-cursor@npm:3.1.0" + dependencies: + restore-cursor: "npm:^3.1.0" + checksum: 10c0/92a2f98ff9037d09be3dfe1f0d749664797fb674bf388375a2207a1203b69d41847abf16434203e0089212479e47a358b13a0222ab9fccfe8e2644a7ccebd111 + languageName: node + linkType: hard + +"cli-spinners@npm:^2.5.0": + version: 2.9.2 + resolution: "cli-spinners@npm:2.9.2" + checksum: 10c0/907a1c227ddf0d7a101e7ab8b300affc742ead4b4ebe920a5bf1bc6d45dce2958fcd195eb28fa25275062fe6fa9b109b93b63bc8033396ed3bcb50297008b3a3 + languageName: node + linkType: hard + +"cliui@npm:^8.0.1": + version: 8.0.1 + resolution: "cliui@npm:8.0.1" + dependencies: + string-width: "npm:^4.2.0" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^7.0.0" + checksum: 10c0/4bda0f09c340cbb6dfdc1ed508b3ca080f12992c18d68c6be4d9cf51756033d5266e61ec57529e610dacbf4da1c634423b0c1b11037709cc6b09045cbd815df5 + languageName: node + linkType: hard + +"clone@npm:^1.0.2": + version: 1.0.4 + resolution: "clone@npm:1.0.4" + checksum: 10c0/2176952b3649293473999a95d7bebfc9dc96410f6cbd3d2595cf12fd401f63a4bf41a7adbfd3ab2ff09ed60cb9870c58c6acdd18b87767366fabfc163700f13b + languageName: node + linkType: hard + +"cluster-key-slot@npm:^1.1.0": + version: 1.1.2 + resolution: "cluster-key-slot@npm:1.1.2" + checksum: 10c0/d7d39ca28a8786e9e801eeb8c770e3c3236a566625d7299a47bb71113fb2298ce1039596acb82590e598c52dbc9b1f088c8f587803e697cb58e1867a95ff94d3 + languageName: node + linkType: hard + +"color-convert@npm:^2.0.1": + version: 2.0.1 + resolution: "color-convert@npm:2.0.1" + dependencies: + color-name: "npm:~1.1.4" + checksum: 10c0/37e1150172f2e311fe1b2df62c6293a342ee7380da7b9cfdba67ea539909afbd74da27033208d01d6d5cfc65ee7868a22e18d7e7648e004425441c0f8a15a7d7 + languageName: node + linkType: hard + +"color-name@npm:~1.1.4": + version: 1.1.4 + resolution: "color-name@npm:1.1.4" + checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95 + languageName: node + linkType: hard + +"combined-stream@npm:^1.0.8": + version: 1.0.8 + resolution: "combined-stream@npm:1.0.8" + dependencies: + delayed-stream: "npm:~1.0.0" + checksum: 10c0/0dbb829577e1b1e839fa82b40c07ffaf7de8a09b935cadd355a73652ae70a88b4320db322f6634a4ad93424292fa80973ac6480986247f1734a1137debf271d5 + languageName: node + linkType: hard + +"concat-map@npm:0.0.1": + version: 0.0.1 + resolution: "concat-map@npm:0.0.1" + checksum: 10c0/c996b1cfdf95b6c90fee4dae37e332c8b6eb7d106430c17d538034c0ad9a1630cb194d2ab37293b1bdd4d779494beee7786d586a50bd9376fd6f7bcc2bd4c98f + languageName: node + linkType: hard + +"concurrently@npm:^8.2.2": + version: 8.2.2 + resolution: "concurrently@npm:8.2.2" + dependencies: + chalk: "npm:^4.1.2" + date-fns: "npm:^2.30.0" + lodash: "npm:^4.17.21" + rxjs: "npm:^7.8.1" + shell-quote: "npm:^1.8.1" + spawn-command: "npm:0.0.2" + supports-color: "npm:^8.1.1" + tree-kill: "npm:^1.2.2" + yargs: "npm:^17.7.2" + bin: + conc: dist/bin/concurrently.js + concurrently: dist/bin/concurrently.js + checksum: 10c0/0e9683196fe9c071d944345d21d8f34aa6c0cc50c0dd897e95619f2f1c9eb4871dca851b2569da17888235b7335b4c821ca19deed35bebcd9a131ee5d247f34c + languageName: node + linkType: hard + +"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.3": + version: 7.0.3 + resolution: "cross-spawn@npm:7.0.3" + dependencies: + path-key: "npm:^3.1.0" + shebang-command: "npm:^2.0.0" + which: "npm:^2.0.1" + checksum: 10c0/5738c312387081c98d69c98e105b6327b069197f864a60593245d64c8089c8a0a744e16349281210d56835bb9274130d825a78b2ad6853ca13cfbeffc0c31750 + languageName: node + linkType: hard + +"date-fns@npm:^2.30.0": + version: 2.30.0 + resolution: "date-fns@npm:2.30.0" + dependencies: + "@babel/runtime": "npm:^7.21.0" + checksum: 10c0/e4b521fbf22bc8c3db332bbfb7b094fd3e7627de0259a9d17c7551e2d2702608a7307a449206065916538e384f37b181565447ce2637ae09828427aed9cb5581 + languageName: node + linkType: hard + +"debug@npm:4, debug@npm:^4, debug@npm:^4.3.5": + version: 4.3.5 + resolution: "debug@npm:4.3.5" + dependencies: + ms: "npm:2.1.2" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/082c375a2bdc4f4469c99f325ff458adad62a3fc2c482d59923c260cb08152f34e2659f72b3767db8bb2f21ca81a60a42d1019605a412132d7b9f59363a005cc + languageName: node + linkType: hard + +"debug@npm:^4.1.1, debug@npm:^4.3.4": + version: 4.3.4 + resolution: "debug@npm:4.3.4" + dependencies: + ms: "npm:2.1.2" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/cedbec45298dd5c501d01b92b119cd3faebe5438c3917ff11ae1bff86a6c722930ac9c8659792824013168ba6db7c4668225d845c633fbdafbbf902a6389f736 + languageName: node + linkType: hard + +"decimal.js@npm:^10.4.3": + version: 10.4.3 + resolution: "decimal.js@npm:10.4.3" + checksum: 10c0/6d60206689ff0911f0ce968d40f163304a6c1bc739927758e6efc7921cfa630130388966f16bf6ef6b838cb33679fbe8e7a78a2f3c478afce841fd55ac8fb8ee + languageName: node + linkType: hard + +"deep-eql@npm:^5.0.1": + version: 5.0.2 + resolution: "deep-eql@npm:5.0.2" + checksum: 10c0/7102cf3b7bb719c6b9c0db2e19bf0aa9318d141581befe8c7ce8ccd39af9eaa4346e5e05adef7f9bd7015da0f13a3a25dcfe306ef79dc8668aedbecb658dd247 + languageName: node + linkType: hard + +"deep-object-diff@npm:^1.1.9": + version: 1.1.9 + resolution: "deep-object-diff@npm:1.1.9" + checksum: 10c0/12cfd1b000d16c9192fc649923c972f8aac2ddca4f71a292f8f2c1e2d5cf3c9c16c85e73ab3e7d8a89a5ec6918d6460677d0b05bd160f7bd50bb4816d496dc24 + languageName: node + linkType: hard + +"deepmerge@npm:4.3.1": + version: 4.3.1 + resolution: "deepmerge@npm:4.3.1" + checksum: 10c0/e53481aaf1aa2c4082b5342be6b6d8ad9dfe387bc92ce197a66dea08bd4265904a087e75e464f14d1347cf2ac8afe1e4c16b266e0561cc5df29382d3c5f80044 + languageName: node + linkType: hard + +"defaults@npm:^1.0.3": + version: 1.0.4 + resolution: "defaults@npm:1.0.4" + dependencies: + clone: "npm:^1.0.2" + checksum: 10c0/9cfbe498f5c8ed733775db62dfd585780387d93c17477949e1670bfcfb9346e0281ce8c4bf9f4ac1fc0f9b851113bd6dc9e41182ea1644ccd97de639fa13c35a + languageName: node + linkType: hard + +"delayed-stream@npm:~1.0.0": + version: 1.0.0 + resolution: "delayed-stream@npm:1.0.0" + checksum: 10c0/d758899da03392e6712f042bec80aa293bbe9e9ff1b2634baae6a360113e708b91326594c8a486d475c69d6259afb7efacdc3537bfcda1c6c648e390ce601b19 + languageName: node + linkType: hard + +"denque@npm:^2.1.0": + version: 2.1.0 + resolution: "denque@npm:2.1.0" + checksum: 10c0/f9ef81aa0af9c6c614a727cb3bd13c5d7db2af1abf9e6352045b86e85873e629690f6222f4edd49d10e4ccf8f078bbeec0794fafaf61b659c0589d0c511ec363 + languageName: node + linkType: hard + +"discord-api-types@npm:0.37.83": + version: 0.37.83 + resolution: "discord-api-types@npm:0.37.83" + checksum: 10c0/cc1fd6c27a4076a47b1dae71212da52dd2265785edc3b099e3f0b05dae1df469e1a90e8159beff8fbdb37d65324cae4196625568541df6b9afdc80c300936ed6 + languageName: node + linkType: hard + +"discord.js@npm:^14.15.3": + version: 14.15.3 + resolution: "discord.js@npm:14.15.3" + dependencies: + "@discordjs/builders": "npm:^1.8.2" + "@discordjs/collection": "npm:1.5.3" + "@discordjs/formatters": "npm:^0.4.0" + "@discordjs/rest": "npm:^2.3.0" + "@discordjs/util": "npm:^1.1.0" + "@discordjs/ws": "npm:^1.1.1" + "@sapphire/snowflake": "npm:3.5.3" + discord-api-types: "npm:0.37.83" + fast-deep-equal: "npm:3.1.3" + lodash.snakecase: "npm:4.1.1" + tslib: "npm:2.6.2" + undici: "npm:6.13.0" + checksum: 10c0/21a4e28541c29bf7170835f42cebc684e83615221f3c1262677e1f363e8fd28e7367e35bceabaea15873d965aecf0a832df6d572790ddec104bd0e025d11bdb8 + languageName: node + linkType: hard + +"dotenv@npm:^16.4.5": + version: 16.4.5 + resolution: "dotenv@npm:16.4.5" + checksum: 10c0/48d92870076832af0418b13acd6e5a5a3e83bb00df690d9812e94b24aff62b88ade955ac99a05501305b8dc8f1b0ee7638b18493deb6fe93d680e5220936292f + languageName: node + linkType: hard + +"dpdm@npm:^3.14.0": + version: 3.14.0 + resolution: "dpdm@npm:3.14.0" + dependencies: + chalk: "npm:^4.1.2" + fs-extra: "npm:^11.1.1" + glob: "npm:^10.3.4" + ora: "npm:^5.4.1" + tslib: "npm:^2.6.2" + typescript: "npm:^5.2.2" + yargs: "npm:^17.7.2" + bin: + dpdm: lib/bin/dpdm.js + checksum: 10c0/2d98064230d68bcc545da80783e9fab0cfb62e2862173af9d3c1648144376f6f7722bfccb5df91b59ed727928ed81d104ae74b53f35565cc5079e44d4d3a706c + languageName: node + linkType: hard + +"e@npm:0.2.33, e@npm:^0.2.33": + version: 0.2.33 + resolution: "e@npm:0.2.33" + checksum: 10c0/28c12ee98d5587686765831ac71ea5676e138c43491a305c9dc28edc10efc53ac2aedec8e0266f0bcc5ff414f909e0ccf1cc37032754660f1c07d8ed2b6652d1 + languageName: node + linkType: hard + +"eastasianwidth@npm:^0.2.0": + version: 0.2.0 + resolution: "eastasianwidth@npm:0.2.0" + checksum: 10c0/26f364ebcdb6395f95124fda411f63137a4bfb5d3a06453f7f23dfe52502905bd84e0488172e0f9ec295fdc45f05c23d5d91baf16bd26f0fe9acd777a188dc39 + languageName: node + linkType: hard + +"emoji-regex@npm:^10.2.1": + version: 10.2.1 + resolution: "emoji-regex@npm:10.2.1" + checksum: 10c0/88f70a75a2889d968925b283e120f111c8ebb92c7961068d8897b16087820c358d22d72755b811906513762bb2b58255a5fca4f47ef8464a7f34c1e54523ccdf + languageName: node + linkType: hard + +"emoji-regex@npm:^8.0.0": + version: 8.0.0 + resolution: "emoji-regex@npm:8.0.0" + checksum: 10c0/b6053ad39951c4cf338f9092d7bfba448cdfd46fe6a2a034700b149ac9ffbc137e361cbd3c442297f86bed2e5f7576c1b54cc0a6bf8ef5106cc62f496af35010 + languageName: node + linkType: hard + +"emoji-regex@npm:^9.2.2": + version: 9.2.2 + resolution: "emoji-regex@npm:9.2.2" + checksum: 10c0/af014e759a72064cf66e6e694a7fc6b0ed3d8db680427b021a89727689671cefe9d04151b2cad51dbaf85d5ba790d061cd167f1cf32eb7b281f6368b3c181639 + languageName: node + linkType: hard + +"encoding@npm:^0.1.13": + version: 0.1.13 + resolution: "encoding@npm:0.1.13" + dependencies: + iconv-lite: "npm:^0.6.2" + checksum: 10c0/36d938712ff00fe1f4bac88b43bcffb5930c1efa57bbcdca9d67e1d9d6c57cfb1200fb01efe0f3109b2ce99b231f90779532814a81370a1bd3274a0f58585039 + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 + languageName: node + linkType: hard + +"err-code@npm:^2.0.2": + version: 2.0.3 + resolution: "err-code@npm:2.0.3" + checksum: 10c0/b642f7b4dd4a376e954947550a3065a9ece6733ab8e51ad80db727aaae0817c2e99b02a97a3d6cecc648a97848305e728289cf312d09af395403a90c9d4d8a66 + languageName: node + linkType: hard + +"esbuild@npm:0.21.5": + version: 0.21.5 + resolution: "esbuild@npm:0.21.5" + dependencies: + "@esbuild/aix-ppc64": "npm:0.21.5" + "@esbuild/android-arm": "npm:0.21.5" + "@esbuild/android-arm64": "npm:0.21.5" + "@esbuild/android-x64": "npm:0.21.5" + "@esbuild/darwin-arm64": "npm:0.21.5" + "@esbuild/darwin-x64": "npm:0.21.5" + "@esbuild/freebsd-arm64": "npm:0.21.5" + "@esbuild/freebsd-x64": "npm:0.21.5" + "@esbuild/linux-arm": "npm:0.21.5" + "@esbuild/linux-arm64": "npm:0.21.5" + "@esbuild/linux-ia32": "npm:0.21.5" + "@esbuild/linux-loong64": "npm:0.21.5" + "@esbuild/linux-mips64el": "npm:0.21.5" + "@esbuild/linux-ppc64": "npm:0.21.5" + "@esbuild/linux-riscv64": "npm:0.21.5" + "@esbuild/linux-s390x": "npm:0.21.5" + "@esbuild/linux-x64": "npm:0.21.5" + "@esbuild/netbsd-x64": "npm:0.21.5" + "@esbuild/openbsd-x64": "npm:0.21.5" + "@esbuild/sunos-x64": "npm:0.21.5" + "@esbuild/win32-arm64": "npm:0.21.5" + "@esbuild/win32-ia32": "npm:0.21.5" + "@esbuild/win32-x64": "npm:0.21.5" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/fa08508adf683c3f399e8a014a6382a6b65542213431e26206c0720e536b31c09b50798747c2a105a4bbba1d9767b8d3615a74c2f7bf1ddf6d836cd11eb672de + languageName: node + linkType: hard + +"escalade@npm:^3.1.1": + version: 3.1.1 + resolution: "escalade@npm:3.1.1" + checksum: 10c0/afd02e6ca91ffa813e1108b5e7756566173d6bc0d1eb951cb44d6b21702ec17c1cf116cfe75d4a2b02e05acb0b808a7a9387d0d1ca5cf9c04ad03a8445c3e46d + languageName: node + linkType: hard + +"estree-walker@npm:^3.0.3": + version: 3.0.3 + resolution: "estree-walker@npm:3.0.3" + dependencies: + "@types/estree": "npm:^1.0.0" + checksum: 10c0/c12e3c2b2642d2bcae7d5aa495c60fa2f299160946535763969a1c83fc74518ffa9c2cd3a8b69ac56aea547df6a8aac25f729a342992ef0bbac5f1c73e78995d + languageName: node + linkType: hard + +"eventemitter3@npm:^4.0.4": + version: 4.0.7 + resolution: "eventemitter3@npm:4.0.7" + checksum: 10c0/5f6d97cbcbac47be798e6355e3a7639a84ee1f7d9b199a07017f1d2f1e2fe236004d14fa5dfaeba661f94ea57805385e326236a6debbc7145c8877fbc0297c6b + languageName: node + linkType: hard + +"execa@npm:^8.0.1": + version: 8.0.1 + resolution: "execa@npm:8.0.1" + dependencies: + cross-spawn: "npm:^7.0.3" + get-stream: "npm:^8.0.1" + human-signals: "npm:^5.0.0" + is-stream: "npm:^3.0.0" + merge-stream: "npm:^2.0.0" + npm-run-path: "npm:^5.1.0" + onetime: "npm:^6.0.0" + signal-exit: "npm:^4.1.0" + strip-final-newline: "npm:^3.0.0" + checksum: 10c0/2c52d8775f5bf103ce8eec9c7ab3059909ba350a5164744e9947ed14a53f51687c040a250bda833f906d1283aa8803975b84e6c8f7a7c42f99dc8ef80250d1af + languageName: node + linkType: hard + +"exit-hook@npm:^4.0.0": + version: 4.0.0 + resolution: "exit-hook@npm:4.0.0" + checksum: 10c0/7fb33eaeb9050aee9479da9c93d42b796fb409c40e1d2b6ea2f40786ae7d7db6dc6a0f6ecc7bc24e479f957b7844bcb880044ded73320334743c64e3ecef48d7 + languageName: node + linkType: hard + +"exponential-backoff@npm:^3.1.1": + version: 3.1.1 + resolution: "exponential-backoff@npm:3.1.1" + checksum: 10c0/160456d2d647e6019640bd07111634d8c353038d9fa40176afb7cd49b0548bdae83b56d05e907c2cce2300b81cae35d800ef92fefb9d0208e190fa3b7d6bb579 + languageName: node + linkType: hard + +"fast-deep-equal@npm:3.1.3, fast-deep-equal@npm:^3.1.3": + version: 3.1.3 + resolution: "fast-deep-equal@npm:3.1.3" + checksum: 10c0/40dedc862eb8992c54579c66d914635afbec43350afbbe991235fdcb4e3a8d5af1b23ae7e79bef7d4882d0ecee06c3197488026998fb19f72dc95acff1d1b1d0 + languageName: node + linkType: hard + +"fast-glob@npm:^3.3.2": + version: 3.3.2 + resolution: "fast-glob@npm:3.3.2" + dependencies: + "@nodelib/fs.stat": "npm:^2.0.2" + "@nodelib/fs.walk": "npm:^1.2.3" + glob-parent: "npm:^5.1.2" + merge2: "npm:^1.3.0" + micromatch: "npm:^4.0.4" + checksum: 10c0/42baad7b9cd40b63e42039132bde27ca2cb3a4950d0a0f9abe4639ea1aa9d3e3b40f98b1fe31cbc0cc17b664c9ea7447d911a152fa34ec5b72977b125a6fc845 + languageName: node + linkType: hard + +"fastq@npm:^1.6.0": + version: 1.17.1 + resolution: "fastq@npm:1.17.1" + dependencies: + reusify: "npm:^1.0.4" + checksum: 10c0/1095f16cea45fb3beff558bb3afa74ca7a9250f5a670b65db7ed585f92b4b48381445cd328b3d87323da81e43232b5d5978a8201bde84e0cd514310f1ea6da34 + languageName: node + linkType: hard + +"fengari-interop@npm:^0.1.3": + version: 0.1.3 + resolution: "fengari-interop@npm:0.1.3" + peerDependencies: + fengari: ^0.1.0 + checksum: 10c0/4e32a8731e8e4de9a7e1697e4c2c82bf586242e8067c83bff340c3207a135e8df354d74df924bf04e52d7486f54ed1c184c68fc0387460a9b2af4763503f7eae + languageName: node + linkType: hard + +"fengari@npm:^0.1.4": + version: 0.1.4 + resolution: "fengari@npm:0.1.4" + dependencies: + readline-sync: "npm:^1.4.9" + sprintf-js: "npm:^1.1.1" + tmp: "npm:^0.0.33" + checksum: 10c0/c5b4ba983ca9a0d0875fc4b3e7f1a0285662540d69ce9ab4a83c16f00450e6cea1e8e7eedd250fa54586b537c91bd0238a1774063f38cdeace946db1a4439ab7 + languageName: node + linkType: hard + +"fill-range@npm:^7.1.1": + version: 7.1.1 + resolution: "fill-range@npm:7.1.1" + dependencies: + to-regex-range: "npm:^5.0.1" + checksum: 10c0/b75b691bbe065472f38824f694c2f7449d7f5004aa950426a2c28f0306c60db9b880c0b0e4ed819997ffb882d1da02cfcfc819bddc94d71627f5269682edf018 + languageName: node + linkType: hard + +"foreground-child@npm:^3.1.0": + version: 3.1.1 + resolution: "foreground-child@npm:3.1.1" + dependencies: + cross-spawn: "npm:^7.0.0" + signal-exit: "npm:^4.0.1" + checksum: 10c0/9700a0285628abaeb37007c9a4d92bd49f67210f09067638774338e146c8e9c825c5c877f072b2f75f41dc6a2d0be8664f79ffc03f6576649f54a84fb9b47de0 + languageName: node + linkType: hard + +"form-data@npm:^3.0.0": + version: 3.0.1 + resolution: "form-data@npm:3.0.1" + dependencies: + asynckit: "npm:^0.4.0" + combined-stream: "npm:^1.0.8" + mime-types: "npm:^2.1.12" + checksum: 10c0/1ccc3ae064a080a799923f754d49fcebdd90515a8924f0f54de557540b50e7f1fe48ba5f2bd0435a5664aa2d49729107e6aaf2155a9abf52339474c5638b4485 + languageName: node + linkType: hard + +"fs-extra@npm:^11.1.1": + version: 11.2.0 + resolution: "fs-extra@npm:11.2.0" + dependencies: + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^6.0.1" + universalify: "npm:^2.0.0" + checksum: 10c0/d77a9a9efe60532d2e790e938c81a02c1b24904ef7a3efb3990b835514465ba720e99a6ea56fd5e2db53b4695319b644d76d5a0e9988a2beef80aa7b1da63398 + languageName: node + linkType: hard + +"fs-minipass@npm:^2.0.0": + version: 2.1.0 + resolution: "fs-minipass@npm:2.1.0" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/703d16522b8282d7299337539c3ed6edddd1afe82435e4f5b76e34a79cd74e488a8a0e26a636afc2440e1a23b03878e2122e3a2cfe375a5cf63c37d92b86a004 + languageName: node + linkType: hard + +"fs-minipass@npm:^3.0.0": + version: 3.0.3 + resolution: "fs-minipass@npm:3.0.3" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/63e80da2ff9b621e2cb1596abcb9207f1cf82b968b116ccd7b959e3323144cce7fb141462200971c38bbf2ecca51695069db45265705bed09a7cd93ae5b89f94 + languageName: node + linkType: hard + +"fsevents@npm:~2.3.2": + version: 2.3.2 + resolution: "fsevents@npm:2.3.2" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/be78a3efa3e181cda3cf7a4637cb527bcebb0bd0ea0440105a3bb45b86f9245b307dc10a2507e8f4498a7d4ec349d1910f4d73e4d4495b16103106e07eee735b + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@npm:~2.3.3": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin": + version: 2.3.2 + resolution: "fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin::version=2.3.2&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 10c0/d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5 + languageName: node + linkType: hard + +"get-caller-file@npm:^2.0.5": + version: 2.0.5 + resolution: "get-caller-file@npm:2.0.5" + checksum: 10c0/c6c7b60271931fa752aeb92f2b47e355eac1af3a2673f47c9589e8f8a41adc74d45551c1bc57b5e66a80609f10ffb72b6f575e4370d61cc3f7f3aaff01757cde + languageName: node + linkType: hard + +"get-func-name@npm:^2.0.1": + version: 2.0.2 + resolution: "get-func-name@npm:2.0.2" + checksum: 10c0/89830fd07623fa73429a711b9daecdb304386d237c71268007f788f113505ef1d4cc2d0b9680e072c5082490aec9df5d7758bf5ac6f1c37062855e8e3dc0b9df + languageName: node + linkType: hard + +"get-stream@npm:^8.0.1": + version: 8.0.1 + resolution: "get-stream@npm:8.0.1" + checksum: 10c0/5c2181e98202b9dae0bb4a849979291043e5892eb40312b47f0c22b9414fc9b28a3b6063d2375705eb24abc41ecf97894d9a51f64ff021511b504477b27b4290 + languageName: node + linkType: hard + +"get-tsconfig@npm:^4.7.5": + version: 4.7.5 + resolution: "get-tsconfig@npm:4.7.5" + dependencies: + resolve-pkg-maps: "npm:^1.0.0" + checksum: 10c0/a917dff2ba9ee187c41945736bf9bbab65de31ce5bc1effd76267be483a7340915cff232199406379f26517d2d0a4edcdbcda8cca599c2480a0f2cf1e1de3efa + languageName: node + linkType: hard + +"glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": + version: 5.1.2 + resolution: "glob-parent@npm:5.1.2" + dependencies: + is-glob: "npm:^4.0.1" + checksum: 10c0/cab87638e2112bee3f839ef5f6e0765057163d39c66be8ec1602f3823da4692297ad4e972de876ea17c44d652978638d2fd583c6713d0eb6591706825020c9ee + languageName: node + linkType: hard + +"glob@npm:^10.2.2, glob@npm:^10.3.10": + version: 10.4.2 + resolution: "glob@npm:10.4.2" + dependencies: + foreground-child: "npm:^3.1.0" + jackspeak: "npm:^3.1.2" + minimatch: "npm:^9.0.4" + minipass: "npm:^7.1.2" + package-json-from-dist: "npm:^1.0.0" + path-scurry: "npm:^1.11.1" + bin: + glob: dist/esm/bin.mjs + checksum: 10c0/2c7296695fa75a935f3ad17dc62e4e170a8bb8752cf64d328be8992dd6ad40777939003754e10e9741ff8fbe43aa52fba32d6930d0ffa0e3b74bc3fb5eebaa2f + languageName: node + linkType: hard + +"glob@npm:^10.3.4, glob@npm:^10.4.1": + version: 10.4.5 + resolution: "glob@npm:10.4.5" + dependencies: + foreground-child: "npm:^3.1.0" + jackspeak: "npm:^3.1.2" + minimatch: "npm:^9.0.4" + minipass: "npm:^7.1.2" + package-json-from-dist: "npm:^1.0.0" + path-scurry: "npm:^1.11.1" + bin: + glob: dist/esm/bin.mjs + checksum: 10c0/19a9759ea77b8e3ca0a43c2f07ecddc2ad46216b786bb8f993c445aee80d345925a21e5280c7b7c6c59e860a0154b84e4b2b60321fea92cd3c56b4a7489f160e + languageName: node + linkType: hard + +"graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.6": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 + languageName: node + linkType: hard + +"has-flag@npm:^3.0.0": + version: 3.0.0 + resolution: "has-flag@npm:3.0.0" + checksum: 10c0/1c6c83b14b8b1b3c25b0727b8ba3e3b647f99e9e6e13eb7322107261de07a4c1be56fc0d45678fc376e09772a3a1642ccdaf8fc69bdf123b6c086598397ce473 + languageName: node + linkType: hard + +"has-flag@npm:^4.0.0": + version: 4.0.0 + resolution: "has-flag@npm:4.0.0" + checksum: 10c0/2e789c61b7888d66993e14e8331449e525ef42aac53c627cc53d1c3334e768bcb6abdc4f5f0de1478a25beec6f0bd62c7549058b7ac53e924040d4f301f02fd1 + languageName: node + linkType: hard + +"hasown@npm:^2.0.2": + version: 2.0.2 + resolution: "hasown@npm:2.0.2" + dependencies: + function-bind: "npm:^1.1.2" + checksum: 10c0/3769d434703b8ac66b209a4cca0737519925bbdb61dd887f93a16372b14694c63ff4e797686d87c90f08168e81082248b9b028bad60d4da9e0d1148766f56eb9 + languageName: node + linkType: hard + +"html-escaper@npm:^2.0.0": + version: 2.0.2 + resolution: "html-escaper@npm:2.0.2" + checksum: 10c0/208e8a12de1a6569edbb14544f4567e6ce8ecc30b9394fcaa4e7bb1e60c12a7c9a1ed27e31290817157e8626f3a4f29e76c8747030822eb84a6abb15c255f0a0 + languageName: node + linkType: hard + +"http-cache-semantics@npm:^4.1.1": + version: 4.1.1 + resolution: "http-cache-semantics@npm:4.1.1" + checksum: 10c0/ce1319b8a382eb3cbb4a37c19f6bfe14e5bb5be3d09079e885e8c513ab2d3cd9214902f8a31c9dc4e37022633ceabfc2d697405deeaf1b8f3552bb4ed996fdfc + languageName: node + linkType: hard + +"http-proxy-agent@npm:^7.0.0": + version: 7.0.2 + resolution: "http-proxy-agent@npm:7.0.2" + dependencies: + agent-base: "npm:^7.1.0" + debug: "npm:^4.3.4" + checksum: 10c0/4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921 + languageName: node + linkType: hard + +"https-proxy-agent@npm:^7.0.1": + version: 7.0.5 + resolution: "https-proxy-agent@npm:7.0.5" + dependencies: + agent-base: "npm:^7.0.2" + debug: "npm:4" + checksum: 10c0/2490e3acec397abeb88807db52cac59102d5ed758feee6df6112ab3ccd8325e8a1ce8bce6f4b66e5470eca102d31e425ace904242e4fa28dbe0c59c4bafa7b2c + languageName: node + linkType: hard + +"human-signals@npm:^5.0.0": + version: 5.0.0 + resolution: "human-signals@npm:5.0.0" + checksum: 10c0/5a9359073fe17a8b58e5a085e9a39a950366d9f00217c4ff5878bd312e09d80f460536ea6a3f260b5943a01fe55c158d1cea3fc7bee3d0520aeef04f6d915c82 + languageName: node + linkType: hard + +"iconv-lite@npm:^0.6.2": + version: 0.6.3 + resolution: "iconv-lite@npm:0.6.3" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10c0/98102bc66b33fcf5ac044099d1257ba0b7ad5e3ccd3221f34dd508ab4070edff183276221684e1e0555b145fce0850c9f7d2b60a9fcac50fbb4ea0d6e845a3b1 + languageName: node + linkType: hard + +"ieee754@npm:^1.1.13": + version: 1.2.1 + resolution: "ieee754@npm:1.2.1" + checksum: 10c0/b0782ef5e0935b9f12883a2e2aa37baa75da6e66ce6515c168697b42160807d9330de9a32ec1ed73149aea02e0d822e572bca6f1e22bdcbd2149e13b050b17bb + languageName: node + linkType: hard + +"ignore-by-default@npm:^1.0.1": + version: 1.0.1 + resolution: "ignore-by-default@npm:1.0.1" + checksum: 10c0/9ab6e70e80f7cc12735def7ecb5527cfa56ab4e1152cd64d294522827f2dcf1f6d85531241537dc3713544e88dd888f65cb3c49c7b2cddb9009087c75274e533 + languageName: node + linkType: hard + +"import-in-the-middle@npm:1.4.2": + version: 1.4.2 + resolution: "import-in-the-middle@npm:1.4.2" + dependencies: + acorn: "npm:^8.8.2" + acorn-import-assertions: "npm:^1.9.0" + cjs-module-lexer: "npm:^1.2.2" + module-details-from-path: "npm:^1.0.3" + checksum: 10c0/499c8abc90fa5197b9f5823c221fadbb2f6a467650761151519d855c160c2a55fae5e55334634ca9fc0c42fe97b94754fb17ccd61132e8507760de6e9bf33795 + languageName: node + linkType: hard + +"import-in-the-middle@npm:^1.8.1": + version: 1.8.1 + resolution: "import-in-the-middle@npm:1.8.1" + dependencies: + acorn: "npm:^8.8.2" + acorn-import-attributes: "npm:^1.9.5" + cjs-module-lexer: "npm:^1.2.2" + module-details-from-path: "npm:^1.0.3" + checksum: 10c0/1938fcc762c1b251dd6e98dcfad7e89ec5bcdd3ddc809822e37213d0d9cf8a2d117c1f25c5226650f324f4e571ea935230348ceb55d16091f24e0b44068e59e7 + languageName: node + linkType: hard + +"imurmurhash@npm:^0.1.4": + version: 0.1.4 + resolution: "imurmurhash@npm:0.1.4" + checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 + languageName: node + linkType: hard + +"indent-string@npm:^4.0.0": + version: 4.0.0 + resolution: "indent-string@npm:4.0.0" + checksum: 10c0/1e1904ddb0cb3d6cce7cd09e27a90184908b7a5d5c21b92e232c93579d314f0b83c246ffb035493d0504b1e9147ba2c9b21df0030f48673fba0496ecd698161f + languageName: node + linkType: hard + +"inherits@npm:^2.0.3, inherits@npm:^2.0.4": + version: 2.0.4 + resolution: "inherits@npm:2.0.4" + checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 + languageName: node + linkType: hard + +"ioredis-mock@npm:^8.9.0": + version: 8.9.0 + resolution: "ioredis-mock@npm:8.9.0" + dependencies: + "@ioredis/as-callback": "npm:^3.0.0" + "@ioredis/commands": "npm:^1.2.0" + fengari: "npm:^0.1.4" + fengari-interop: "npm:^0.1.3" + semver: "npm:^7.5.4" + peerDependencies: + "@types/ioredis-mock": ^8 + ioredis: ^5 + checksum: 10c0/d5d3fddf6cdd8880d7ed7693505e48c2fa345c0d04ac6c7dc00101ba3da042ec58d4063e2350dbaf52b821b7f2e974175f1aaa89eda5d154d2a647266aea4157 + languageName: node + linkType: hard + +"ioredis@npm:^5.4.1": + version: 5.4.1 + resolution: "ioredis@npm:5.4.1" + dependencies: + "@ioredis/commands": "npm:^1.1.1" + cluster-key-slot: "npm:^1.1.0" + debug: "npm:^4.3.4" + denque: "npm:^2.1.0" + lodash.defaults: "npm:^4.2.0" + lodash.isarguments: "npm:^3.1.0" + redis-errors: "npm:^1.2.0" + redis-parser: "npm:^3.0.0" + standard-as-callback: "npm:^2.1.0" + checksum: 10c0/5d28b7c89a3cab5b76d75923d7d4ce79172b3a1ca9be690133f6e8e393a7a4b4ffd55513e618bbb5504fed80d9e1395c9d9531a7c5c5c84aa4c4e765cca75456 + languageName: node + linkType: hard + +"ip-address@npm:^9.0.5": + version: 9.0.5 + resolution: "ip-address@npm:9.0.5" + dependencies: + jsbn: "npm:1.1.0" + sprintf-js: "npm:^1.1.3" + checksum: 10c0/331cd07fafcb3b24100613e4b53e1a2b4feab11e671e655d46dc09ee233da5011284d09ca40c4ecbdfe1d0004f462958675c224a804259f2f78d2465a87824bc + languageName: node + linkType: hard + +"is-binary-path@npm:~2.1.0": + version: 2.1.0 + resolution: "is-binary-path@npm:2.1.0" + dependencies: + binary-extensions: "npm:^2.0.0" + checksum: 10c0/a16eaee59ae2b315ba36fad5c5dcaf8e49c3e27318f8ab8fa3cdb8772bf559c8d1ba750a589c2ccb096113bb64497084361a25960899cb6172a6925ab6123d38 + languageName: node + linkType: hard + +"is-core-module@npm:^2.13.0": + version: 2.14.0 + resolution: "is-core-module@npm:2.14.0" + dependencies: + hasown: "npm:^2.0.2" + checksum: 10c0/ae8dbc82bd20426558bc8d20ce290ce301c1cfd6ae4446266d10cacff4c63c67ab16440ade1d72ced9ec41c569fbacbcee01e293782ce568527c4cdf35936e4c + languageName: node + linkType: hard + +"is-extglob@npm:^2.1.1": + version: 2.1.1 + resolution: "is-extglob@npm:2.1.1" + checksum: 10c0/5487da35691fbc339700bbb2730430b07777a3c21b9ebaecb3072512dfd7b4ba78ac2381a87e8d78d20ea08affb3f1971b4af629173a6bf435ff8a4c47747912 + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^3.0.0": + version: 3.0.0 + resolution: "is-fullwidth-code-point@npm:3.0.0" + checksum: 10c0/bb11d825e049f38e04c06373a8d72782eee0205bda9d908cc550ccb3c59b99d750ff9537982e01733c1c94a58e35400661f57042158ff5e8f3e90cf936daf0fc + languageName: node + linkType: hard + +"is-glob@npm:^4.0.1, is-glob@npm:~4.0.1": + version: 4.0.3 + resolution: "is-glob@npm:4.0.3" + dependencies: + is-extglob: "npm:^2.1.1" + checksum: 10c0/17fb4014e22be3bbecea9b2e3a76e9e34ff645466be702f1693e8f1ee1adac84710d0be0bd9f967d6354036fd51ab7c2741d954d6e91dae6bb69714de92c197a + languageName: node + linkType: hard + +"is-interactive@npm:^1.0.0": + version: 1.0.0 + resolution: "is-interactive@npm:1.0.0" + checksum: 10c0/dd47904dbf286cd20aa58c5192161be1a67138485b9836d5a70433b21a45442e9611b8498b8ab1f839fc962c7620667a50535fdfb4a6bc7989b8858645c06b4d + languageName: node + linkType: hard + +"is-lambda@npm:^1.0.1": + version: 1.0.1 + resolution: "is-lambda@npm:1.0.1" + checksum: 10c0/85fee098ae62ba6f1e24cf22678805473c7afd0fb3978a3aa260e354cb7bcb3a5806cf0a98403188465efedec41ab4348e8e4e79305d409601323855b3839d4d + languageName: node + linkType: hard + +"is-number@npm:^7.0.0": + version: 7.0.0 + resolution: "is-number@npm:7.0.0" + checksum: 10c0/b4686d0d3053146095ccd45346461bc8e53b80aeb7671cc52a4de02dbbf7dc0d1d2a986e2fe4ae206984b4d34ef37e8b795ebc4f4295c978373e6575e295d811 + languageName: node + linkType: hard + +"is-stream@npm:^3.0.0": + version: 3.0.0 + resolution: "is-stream@npm:3.0.0" + checksum: 10c0/eb2f7127af02ee9aa2a0237b730e47ac2de0d4e76a4a905a50a11557f2339df5765eaea4ceb8029f1efa978586abe776908720bfcb1900c20c6ec5145f6f29d8 + languageName: node + linkType: hard + +"is-unicode-supported@npm:^0.1.0": + version: 0.1.0 + resolution: "is-unicode-supported@npm:0.1.0" + checksum: 10c0/00cbe3455c3756be68d2542c416cab888aebd5012781d6819749fefb15162ff23e38501fe681b3d751c73e8ff561ac09a5293eba6f58fdf0178462ce6dcb3453 + languageName: node + linkType: hard + +"isexe@npm:^2.0.0": + version: 2.0.0 + resolution: "isexe@npm:2.0.0" + checksum: 10c0/228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d + languageName: node + linkType: hard + +"isexe@npm:^3.1.1": + version: 3.1.1 + resolution: "isexe@npm:3.1.1" + checksum: 10c0/9ec257654093443eb0a528a9c8cbba9c0ca7616ccb40abd6dde7202734d96bb86e4ac0d764f0f8cd965856aacbff2f4ce23e730dc19dfb41e3b0d865ca6fdcc7 + languageName: node + linkType: hard + +"istanbul-lib-coverage@npm:^3.0.0": + version: 3.2.0 + resolution: "istanbul-lib-coverage@npm:3.2.0" + checksum: 10c0/10ecb00a50cac2f506af8231ce523ffa1ac1310db0435c8ffaabb50c1d72539906583aa13c84f8835dc103998b9989edc3c1de989d2e2a96a91a9ba44e5db6b9 + languageName: node + linkType: hard + +"istanbul-lib-coverage@npm:^3.2.2": + version: 3.2.2 + resolution: "istanbul-lib-coverage@npm:3.2.2" + checksum: 10c0/6c7ff2106769e5f592ded1fb418f9f73b4411fd5a084387a5410538332b6567cd1763ff6b6cadca9b9eb2c443cce2f7ea7d7f1b8d315f9ce58539793b1e0922b + languageName: node + linkType: hard + +"istanbul-lib-report@npm:^3.0.0": + version: 3.0.0 + resolution: "istanbul-lib-report@npm:3.0.0" + dependencies: + istanbul-lib-coverage: "npm:^3.0.0" + make-dir: "npm:^3.0.0" + supports-color: "npm:^7.1.0" + checksum: 10c0/81b0d5187c7603ed71bdea0b701a7329f8146549ca19aa26d91b4a163aea756f9d55c1a6dc1dcd087e24dfcb99baa69e266a68644fbfd5dc98107d6f6f5948d2 + languageName: node + linkType: hard + +"istanbul-lib-report@npm:^3.0.1": + version: 3.0.1 + resolution: "istanbul-lib-report@npm:3.0.1" + dependencies: + istanbul-lib-coverage: "npm:^3.0.0" + make-dir: "npm:^4.0.0" + supports-color: "npm:^7.1.0" + checksum: 10c0/84323afb14392de8b6a5714bd7e9af845cfbd56cfe71ed276cda2f5f1201aea673c7111901227ee33e68e4364e288d73861eb2ed48f6679d1e69a43b6d9b3ba7 + languageName: node + linkType: hard + +"istanbul-lib-source-maps@npm:^5.0.6": + version: 5.0.6 + resolution: "istanbul-lib-source-maps@npm:5.0.6" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.23" + debug: "npm:^4.1.1" + istanbul-lib-coverage: "npm:^3.0.0" + checksum: 10c0/ffe75d70b303a3621ee4671554f306e0831b16f39ab7f4ab52e54d356a5d33e534d97563e318f1333a6aae1d42f91ec49c76b6cd3f3fb378addcb5c81da0255f + languageName: node + linkType: hard + +"istanbul-reports@npm:^3.1.7": + version: 3.1.7 + resolution: "istanbul-reports@npm:3.1.7" + dependencies: + html-escaper: "npm:^2.0.0" + istanbul-lib-report: "npm:^3.0.0" + checksum: 10c0/a379fadf9cf8dc5dfe25568115721d4a7eb82fbd50b005a6672aff9c6989b20cc9312d7865814e0859cd8df58cbf664482e1d3604be0afde1f7fc3ccc1394a51 + languageName: node + linkType: hard + +"jackspeak@npm:^3.1.2": + version: 3.4.0 + resolution: "jackspeak@npm:3.4.0" + dependencies: + "@isaacs/cliui": "npm:^8.0.2" + "@pkgjs/parseargs": "npm:^0.11.0" + dependenciesMeta: + "@pkgjs/parseargs": + optional: true + checksum: 10c0/7e42d1ea411b4d57d43ea8a6afbca9224382804359cb72626d0fc45bb8db1de5ad0248283c3db45fe73e77210750d4fcc7c2b4fe5d24fda94aaa24d658295c5f + languageName: node + linkType: hard + +"js-tokens@npm:^9.0.0": + version: 9.0.0 + resolution: "js-tokens@npm:9.0.0" + checksum: 10c0/4ad1c12f47b8c8b2a3a99e29ef338c1385c7b7442198a425f3463f3537384dab6032012791bfc2f056ea5ecdb06b1ed4f70e11a3ab3f388d3dcebfe16a52b27d + languageName: node + linkType: hard + +"jsbn@npm:1.1.0": + version: 1.1.0 + resolution: "jsbn@npm:1.1.0" + checksum: 10c0/4f907fb78d7b712e11dea8c165fe0921f81a657d3443dde75359ed52eb2b5d33ce6773d97985a089f09a65edd80b11cb75c767b57ba47391fee4c969f7215c96 + languageName: node + linkType: hard + +"jsonfile@npm:^6.0.1": + version: 6.1.0 + resolution: "jsonfile@npm:6.1.0" + dependencies: + graceful-fs: "npm:^4.1.6" + universalify: "npm:^2.0.0" + dependenciesMeta: + graceful-fs: + optional: true + checksum: 10c0/4f95b5e8a5622b1e9e8f33c96b7ef3158122f595998114d1e7f03985649ea99cb3cd99ce1ed1831ae94c8c8543ab45ebd044207612f31a56fd08462140e46865 + languageName: node + linkType: hard + +"lodash.defaults@npm:^4.2.0": + version: 4.2.0 + resolution: "lodash.defaults@npm:4.2.0" + checksum: 10c0/d5b77aeb702caa69b17be1358faece33a84497bcca814897383c58b28a2f8dfc381b1d9edbec239f8b425126a3bbe4916223da2a576bb0411c2cefd67df80707 + languageName: node + linkType: hard + +"lodash.isarguments@npm:^3.1.0": + version: 3.1.0 + resolution: "lodash.isarguments@npm:3.1.0" + checksum: 10c0/5e8f95ba10975900a3920fb039a3f89a5a79359a1b5565e4e5b4310ed6ebe64011e31d402e34f577eca983a1fc01ff86c926e3cbe602e1ddfc858fdd353e62d8 + languageName: node + linkType: hard + +"lodash.merge@npm:^4.6.2": + version: 4.6.2 + resolution: "lodash.merge@npm:4.6.2" + checksum: 10c0/402fa16a1edd7538de5b5903a90228aa48eb5533986ba7fa26606a49db2572bf414ff73a2c9f5d5fd36b31c46a5d5c7e1527749c07cbcf965ccff5fbdf32c506 + languageName: node + linkType: hard + +"lodash.snakecase@npm:4.1.1": + version: 4.1.1 + resolution: "lodash.snakecase@npm:4.1.1" + checksum: 10c0/f0b3f2497eb20eea1a1cfc22d645ecaeb78ac14593eb0a40057977606d2f35f7aaff0913a06553c783b535aafc55b718f523f9eb78f8d5293f492af41002eaf9 + languageName: node + linkType: hard + +"lodash@npm:^4.17.21": + version: 4.17.21 + resolution: "lodash@npm:4.17.21" + checksum: 10c0/d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c + languageName: node + linkType: hard + +"log-symbols@npm:^4.1.0": + version: 4.1.0 + resolution: "log-symbols@npm:4.1.0" + dependencies: + chalk: "npm:^4.1.0" + is-unicode-supported: "npm:^0.1.0" + checksum: 10c0/67f445a9ffa76db1989d0fa98586e5bc2fd5247260dafb8ad93d9f0ccd5896d53fb830b0e54dade5ad838b9de2006c826831a3c528913093af20dff8bd24aca6 + languageName: node + linkType: hard + +"loupe@npm:^3.1.0, loupe@npm:^3.1.1": + version: 3.1.1 + resolution: "loupe@npm:3.1.1" + dependencies: + get-func-name: "npm:^2.0.1" + checksum: 10c0/99f88badc47e894016df0c403de846fedfea61154aadabbf776c8428dd59e8d8378007135d385d737de32ae47980af07d22ba7bec5ef7beebd721de9baa0a0af + languageName: node + linkType: hard + +"lru-cache@npm:^10.0.1, lru-cache@npm:^10.3.0": + version: 10.3.0 + resolution: "lru-cache@npm:10.3.0" + checksum: 10c0/02d57024d90672774d66e0b76328a8975483b782c68118078363be17b8e0efb4f2bee89d98ce87e72f42d68fe7cb4ad14b1205d43e4f9954f5c91e3be4eaceb8 + languageName: node + linkType: hard + +"lru-cache@npm:^10.2.0": + version: 10.2.2 + resolution: "lru-cache@npm:10.2.2" + checksum: 10c0/402d31094335851220d0b00985084288136136992979d0e015f0f1697e15d1c86052d7d53ae86b614e5b058425606efffc6969a31a091085d7a2b80a8a1e26d6 + languageName: node + linkType: hard + +"lru-cache@npm:^6.0.0": + version: 6.0.0 + resolution: "lru-cache@npm:6.0.0" + dependencies: + yallist: "npm:^4.0.0" + checksum: 10c0/cb53e582785c48187d7a188d3379c181b5ca2a9c78d2bce3e7dee36f32761d1c42983da3fe12b55cb74e1779fa94cdc2e5367c028a9b35317184ede0c07a30a9 + languageName: node + linkType: hard + +"magic-bytes.js@npm:^1.10.0": + version: 1.10.0 + resolution: "magic-bytes.js@npm:1.10.0" + checksum: 10c0/aa751a9be6baa80da19b395a4c538e1dcf652a3760a2e507b9e04c4cb3832635b718f8a947e5b0a434a1b323792632002f98815b3389f0beb3af1c4d51eb339e + languageName: node + linkType: hard + +"magic-string@npm:^0.30.10": + version: 0.30.10 + resolution: "magic-string@npm:0.30.10" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.4.15" + checksum: 10c0/aa9ca17eae571a19bce92c8221193b6f93ee8511abb10f085e55ffd398db8e4c089a208d9eac559deee96a08b7b24d636ea4ab92f09c6cf42a7d1af51f7fd62b + languageName: node + linkType: hard + +"magicast@npm:^0.3.4": + version: 0.3.4 + resolution: "magicast@npm:0.3.4" + dependencies: + "@babel/parser": "npm:^7.24.4" + "@babel/types": "npm:^7.24.0" + source-map-js: "npm:^1.2.0" + checksum: 10c0/7ebaaac397b13c31ca05e6d9649296751d76749b945d10a0800107872119fbdf267acdb604571d25e38ec6fd7ab3568a951b6e76eaef1caba9eaa11778fd9783 + languageName: node + linkType: hard + +"make-dir@npm:^3.0.0": + version: 3.1.0 + resolution: "make-dir@npm:3.1.0" + dependencies: + semver: "npm:^6.0.0" + checksum: 10c0/56aaafefc49c2dfef02c5c95f9b196c4eb6988040cf2c712185c7fe5c99b4091591a7fc4d4eafaaefa70ff763a26f6ab8c3ff60b9e75ea19876f49b18667ecaa + languageName: node + linkType: hard + +"make-dir@npm:^4.0.0": + version: 4.0.0 + resolution: "make-dir@npm:4.0.0" + dependencies: + semver: "npm:^7.5.3" + checksum: 10c0/69b98a6c0b8e5c4fe9acb61608a9fbcfca1756d910f51e5dbe7a9e5cfb74fca9b8a0c8a0ffdf1294a740826c1ab4871d5bf3f62f72a3049e5eac6541ddffed68 + languageName: node + linkType: hard + +"make-fetch-happen@npm:^13.0.0": + version: 13.0.1 + resolution: "make-fetch-happen@npm:13.0.1" + dependencies: + "@npmcli/agent": "npm:^2.0.0" + cacache: "npm:^18.0.0" + http-cache-semantics: "npm:^4.1.1" + is-lambda: "npm:^1.0.1" + minipass: "npm:^7.0.2" + minipass-fetch: "npm:^3.0.0" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + negotiator: "npm:^0.6.3" + proc-log: "npm:^4.2.0" + promise-retry: "npm:^2.0.1" + ssri: "npm:^10.0.0" + checksum: 10c0/df5f4dbb6d98153b751bccf4dc4cc500de85a96a9331db9805596c46aa9f99d9555983954e6c1266d9f981ae37a9e4647f42b9a4bb5466f867f4012e582c9e7e + languageName: node + linkType: hard + +"math-expression-evaluator@npm:^1.3.14": + version: 1.3.14 + resolution: "math-expression-evaluator@npm:1.3.14" + checksum: 10c0/a98dcb54510f57d9d78c66f73c6ee5e8e3a69d7dcfd3f0e611bc6957a764f23fed3b342888fca79ed49b865b09338581d19bb3acc4d788ee308618c7c7cdca10 + languageName: node + linkType: hard + +"merge-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "merge-stream@npm:2.0.0" + checksum: 10c0/867fdbb30a6d58b011449b8885601ec1690c3e41c759ecd5a9d609094f7aed0096c37823ff4a7190ef0b8f22cc86beb7049196ff68c016e3b3c671d0dac91ce5 + languageName: node + linkType: hard + +"merge2@npm:^1.3.0": + version: 1.4.1 + resolution: "merge2@npm:1.4.1" + checksum: 10c0/254a8a4605b58f450308fc474c82ac9a094848081bf4c06778200207820e5193726dc563a0d2c16468810516a5c97d9d3ea0ca6585d23c58ccfff2403e8dbbeb + languageName: node + linkType: hard + +"micromatch@npm:^4.0.4": + version: 4.0.4 + resolution: "micromatch@npm:4.0.4" + dependencies: + braces: "npm:^3.0.1" + picomatch: "npm:^2.2.3" + checksum: 10c0/87bc95e3e52ebe413dbadd43c96e797c736bf238f154e3b546859493e83781b6f7fa4dfa54e423034fb9aeea65259ee6480551581271c348d8e19214910a5a64 + languageName: node + linkType: hard + +"mime-db@npm:1.48.0": + version: 1.48.0 + resolution: "mime-db@npm:1.48.0" + checksum: 10c0/58a17590ba92d4de1c88cb41199f8746baaeed5de7aaa76d94925d3f015f37cce65614e6621b545f382ddaa0063e8d8e8aeaf205fd6422be0f04d6aa56e3f8aa + languageName: node + linkType: hard + +"mime-types@npm:^2.1.12": + version: 2.1.31 + resolution: "mime-types@npm:2.1.31" + dependencies: + mime-db: "npm:1.48.0" + checksum: 10c0/8adf6de32bf5be25049a0816c751bf69aa7d245abbeffd1d594b692159616762d30831dae21d37d5e433cd5b824f759ce0e6286c436e140dd71ab8a00d90cdea + languageName: node + linkType: hard + +"mimic-fn@npm:^2.1.0": + version: 2.1.0 + resolution: "mimic-fn@npm:2.1.0" + checksum: 10c0/b26f5479d7ec6cc2bce275a08f146cf78f5e7b661b18114e2506dd91ec7ec47e7a25bf4360e5438094db0560bcc868079fb3b1fb3892b833c1ecbf63f80c95a4 + languageName: node + linkType: hard + +"mimic-fn@npm:^4.0.0": + version: 4.0.0 + resolution: "mimic-fn@npm:4.0.0" + checksum: 10c0/de9cc32be9996fd941e512248338e43407f63f6d497abe8441fa33447d922e927de54d4cc3c1a3c6d652857acd770389d5a3823f311a744132760ce2be15ccbf + languageName: node + linkType: hard + +"minimatch@npm:^3.1.2": + version: 3.1.2 + resolution: "minimatch@npm:3.1.2" + dependencies: + brace-expansion: "npm:^1.1.7" + checksum: 10c0/0262810a8fc2e72cca45d6fd86bd349eee435eb95ac6aa45c9ea2180e7ee875ef44c32b55b5973ceabe95ea12682f6e3725cbb63d7a2d1da3ae1163c8b210311 + languageName: node + linkType: hard + +"minimatch@npm:^9.0.4": + version: 9.0.5 + resolution: "minimatch@npm:9.0.5" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10c0/de96cf5e35bdf0eab3e2c853522f98ffbe9a36c37797778d2665231ec1f20a9447a7e567cb640901f89e4daaa95ae5d70c65a9e8aa2bb0019b6facbc3c0575ed + languageName: node + linkType: hard + +"minipass-collect@npm:^2.0.1": + version: 2.0.1 + resolution: "minipass-collect@npm:2.0.1" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/5167e73f62bb74cc5019594709c77e6a742051a647fe9499abf03c71dca75515b7959d67a764bdc4f8b361cf897fbf25e2d9869ee039203ed45240f48b9aa06e + languageName: node + linkType: hard + +"minipass-fetch@npm:^3.0.0": + version: 3.0.5 + resolution: "minipass-fetch@npm:3.0.5" + dependencies: + encoding: "npm:^0.1.13" + minipass: "npm:^7.0.3" + minipass-sized: "npm:^1.0.3" + minizlib: "npm:^2.1.2" + dependenciesMeta: + encoding: + optional: true + checksum: 10c0/9d702d57f556274286fdd97e406fc38a2f5c8d15e158b498d7393b1105974b21249289ec571fa2b51e038a4872bfc82710111cf75fae98c662f3d6f95e72152b + languageName: node + linkType: hard + +"minipass-flush@npm:^1.0.5": + version: 1.0.5 + resolution: "minipass-flush@npm:1.0.5" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/2a51b63feb799d2bb34669205eee7c0eaf9dce01883261a5b77410c9408aa447e478efd191b4de6fc1101e796ff5892f8443ef20d9544385819093dbb32d36bd + languageName: node + linkType: hard + +"minipass-pipeline@npm:^1.2.4": + version: 1.2.4 + resolution: "minipass-pipeline@npm:1.2.4" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/cbda57cea20b140b797505dc2cac71581a70b3247b84480c1fed5ca5ba46c25ecc25f68bfc9e6dcb1a6e9017dab5c7ada5eab73ad4f0a49d84e35093e0c643f2 + languageName: node + linkType: hard + +"minipass-sized@npm:^1.0.3": + version: 1.0.3 + resolution: "minipass-sized@npm:1.0.3" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/298f124753efdc745cfe0f2bdfdd81ba25b9f4e753ca4a2066eb17c821f25d48acea607dfc997633ee5bf7b6dfffb4eee4f2051eb168663f0b99fad2fa4829cb + languageName: node + linkType: hard + +"minipass@npm:^3.0.0": + version: 3.3.6 + resolution: "minipass@npm:3.3.6" + dependencies: + yallist: "npm:^4.0.0" + checksum: 10c0/a114746943afa1dbbca8249e706d1d38b85ed1298b530f5808ce51f8e9e941962e2a5ad2e00eae7dd21d8a4aae6586a66d4216d1a259385e9d0358f0c1eba16c + languageName: node + linkType: hard + +"minipass@npm:^5.0.0": + version: 5.0.0 + resolution: "minipass@npm:5.0.0" + checksum: 10c0/a91d8043f691796a8ac88df039da19933ef0f633e3d7f0d35dcd5373af49131cf2399bfc355f41515dc495e3990369c3858cd319e5c2722b4753c90bf3152462 + languageName: node + linkType: hard + +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0": + version: 7.0.4 + resolution: "minipass@npm:7.0.4" + checksum: 10c0/6c7370a6dfd257bf18222da581ba89a5eaedca10e158781232a8b5542a90547540b4b9b7e7f490e4cda43acfbd12e086f0453728ecf8c19e0ef6921bc5958ac5 + languageName: node + linkType: hard + +"minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.1.2": + version: 7.1.2 + resolution: "minipass@npm:7.1.2" + checksum: 10c0/b0fd20bb9fb56e5fa9a8bfac539e8915ae07430a619e4b86ff71f5fc757ef3924b23b2c4230393af1eda647ed3d75739e4e0acb250a6b1eb277cf7f8fe449557 + languageName: node + linkType: hard + +"minizlib@npm:^2.1.1, minizlib@npm:^2.1.2": + version: 2.1.2 + resolution: "minizlib@npm:2.1.2" + dependencies: + minipass: "npm:^3.0.0" + yallist: "npm:^4.0.0" + checksum: 10c0/64fae024e1a7d0346a1102bb670085b17b7f95bf6cfdf5b128772ec8faf9ea211464ea4add406a3a6384a7d87a0cd1a96263692134323477b4fb43659a6cab78 + languageName: node + linkType: hard + +"mkdirp@npm:^1.0.3": + version: 1.0.4 + resolution: "mkdirp@npm:1.0.4" + bin: + mkdirp: bin/cmd.js + checksum: 10c0/46ea0f3ffa8bc6a5bc0c7081ffc3907777f0ed6516888d40a518c5111f8366d97d2678911ad1a6882bf592fa9de6c784fea32e1687bb94e1f4944170af48a5cf + languageName: node + linkType: hard + +"module-details-from-path@npm:^1.0.3": + version: 1.0.3 + resolution: "module-details-from-path@npm:1.0.3" + checksum: 10c0/3d881f3410c142e4c2b1307835a2862ba04e5b3ec6e90655614a0ee2c4b299b4c1d117fb525d2435bf436990026f18d338a197b54ad6bd36252f465c336ff423 + languageName: node + linkType: hard + +"ms@npm:2.1.2": + version: 2.1.2 + resolution: "ms@npm:2.1.2" + checksum: 10c0/a437714e2f90dbf881b5191d35a6db792efbca5badf112f87b9e1c712aace4b4b9b742dd6537f3edf90fd6f684de897cec230abde57e87883766712ddda297cc + languageName: node + linkType: hard + +"murmurhash@npm:^2.0.1": + version: 2.0.1 + resolution: "murmurhash@npm:2.0.1" + checksum: 10c0/f6c7cb12d6ebc9c1cfd232fe9406089e1ceb128d24245e852866ba28967271925d915140f77fef7c92ee29b13165f4537ce80a85c3d0550b1b5cdb9f8bcaa19f + languageName: node + linkType: hard + +"nan@npm:^2.18.0": + version: 2.20.0 + resolution: "nan@npm:2.20.0" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/75775309a21ad179a55250d62ce47322c33ca03d8ddb5ad4c555bd820dd72484b3c59253dd9f41cc68dd63453ef04017407fbd081a549bc030d977079bb798b7 + languageName: node + linkType: hard + +"nanoid@npm:^3.3.7": + version: 3.3.7 + resolution: "nanoid@npm:3.3.7" + bin: + nanoid: bin/nanoid.cjs + checksum: 10c0/e3fb661aa083454f40500473bb69eedb85dc160e763150b9a2c567c7e9ff560ce028a9f833123b618a6ea742e311138b591910e795614a629029e86e180660f3 + languageName: node + linkType: hard + +"negotiator@npm:^0.6.3": + version: 0.6.3 + resolution: "negotiator@npm:0.6.3" + checksum: 10c0/3ec9fd413e7bf071c937ae60d572bc67155262068ed522cf4b3be5edbe6ddf67d095ec03a3a14ebf8fc8e95f8e1d61be4869db0dbb0de696f6b837358bd43fc2 + languageName: node + linkType: hard + +"nice-napi@npm:^1.0.2": + version: 1.0.2 + resolution: "nice-napi@npm:1.0.2" + dependencies: + node-addon-api: "npm:^3.0.0" + node-gyp: "npm:latest" + node-gyp-build: "npm:^4.2.2" + conditions: "!os=win32" + languageName: node + linkType: hard + +"node-addon-api@npm:^3.0.0": + version: 3.2.1 + resolution: "node-addon-api@npm:3.2.1" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/41f21c9d12318875a2c429befd06070ce367065a3ef02952cfd4ea17ef69fa14012732f510b82b226e99c254da8d671847ea018cad785f839a5366e02dd56302 + languageName: node + linkType: hard + +"node-cron@npm:^3.0.3": + version: 3.0.3 + resolution: "node-cron@npm:3.0.3" + dependencies: + uuid: "npm:8.3.2" + checksum: 10c0/e6e817c5bf28cca69f256ecc1a2cb737cf895cb0fe22f8ec58ce614cb74dcc22e93f495984a26ece51322473238ccc0ef53b4f373035697017b8c28bddacb9b9 + languageName: node + linkType: hard + +"node-fetch@npm:2.6.7, node-fetch@npm:^2.6.7": + version: 2.6.7 + resolution: "node-fetch@npm:2.6.7" + dependencies: + whatwg-url: "npm:^5.0.0" + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + checksum: 10c0/fcae80f5ac52fbf5012f5e19df2bd3915e67d3b3ad51cb5942943df2238d32ba15890fecabd0e166876a9f98a581ab50f3f10eb942b09405c49ef8da36b826c7 + languageName: node + linkType: hard + +"node-gyp-build@npm:^4.2.2": + version: 4.3.0 + resolution: "node-gyp-build@npm:4.3.0" + bin: + node-gyp-build: bin.js + node-gyp-build-optional: optional.js + node-gyp-build-test: build-test.js + checksum: 10c0/817917b256e5193c1b2f832a8e41e82cfb9915e44dfc01a9b2745ddda203344c82e27c177c1e4da39083a08d6e30859a0ce6672801ac4f0cd1df94a8c43f22b5 + languageName: node + linkType: hard + +"node-gyp-build@npm:^4.3.0": + version: 4.8.1 + resolution: "node-gyp-build@npm:4.8.1" + bin: + node-gyp-build: bin.js + node-gyp-build-optional: optional.js + node-gyp-build-test: build-test.js + checksum: 10c0/e36ca3d2adf2b9cca316695d7687207c19ac6ed326d6d7c68d7112cebe0de4f82d6733dff139132539fcc01cf5761f6c9082a21864ab9172edf84282bc849ce7 + languageName: node + linkType: hard + +"node-gyp@npm:latest": + version: 10.1.0 + resolution: "node-gyp@npm:10.1.0" + dependencies: + env-paths: "npm:^2.2.0" + exponential-backoff: "npm:^3.1.1" + glob: "npm:^10.3.10" + graceful-fs: "npm:^4.2.6" + make-fetch-happen: "npm:^13.0.0" + nopt: "npm:^7.0.0" + proc-log: "npm:^3.0.0" + semver: "npm:^7.3.5" + tar: "npm:^6.1.2" + which: "npm:^4.0.0" + bin: + node-gyp: bin/node-gyp.js + checksum: 10c0/9cc821111ca244a01fb7f054db7523ab0a0cd837f665267eb962eb87695d71fb1e681f9e21464cc2fd7c05530dc4c81b810bca1a88f7d7186909b74477491a3c + languageName: node + linkType: hard + +"nodemon@npm:^3.1.4": + version: 3.1.4 + resolution: "nodemon@npm:3.1.4" + dependencies: + chokidar: "npm:^3.5.2" + debug: "npm:^4" + ignore-by-default: "npm:^1.0.1" + minimatch: "npm:^3.1.2" + pstree.remy: "npm:^1.1.8" + semver: "npm:^7.5.3" + simple-update-notifier: "npm:^2.0.0" + supports-color: "npm:^5.5.0" + touch: "npm:^3.1.0" + undefsafe: "npm:^2.0.5" + bin: + nodemon: bin/nodemon.js + checksum: 10c0/be2335396a4c25549f86e9c69bb57e6a21758a9649d74182a359d88b80afbe84f67a1651e293a08c6d77ecdf5c6224d02990de9de225f70d7c659021206c877f + languageName: node + linkType: hard + +"nopt@npm:^7.0.0": + version: 7.2.1 + resolution: "nopt@npm:7.2.1" + dependencies: + abbrev: "npm:^2.0.0" + bin: + nopt: bin/nopt.js + checksum: 10c0/a069c7c736767121242037a22a788863accfa932ab285a1eb569eb8cd534b09d17206f68c37f096ae785647435e0c5a5a0a67b42ec743e481a455e5ae6a6df81 + languageName: node + linkType: hard + +"normalize-path@npm:^3.0.0, normalize-path@npm:~3.0.0": + version: 3.0.0 + resolution: "normalize-path@npm:3.0.0" + checksum: 10c0/e008c8142bcc335b5e38cf0d63cfd39d6cf2d97480af9abdbe9a439221fd4d749763bab492a8ee708ce7a194bb00c9da6d0a115018672310850489137b3da046 + languageName: node + linkType: hard + +"npm-run-path@npm:^5.1.0": + version: 5.3.0 + resolution: "npm-run-path@npm:5.3.0" + dependencies: + path-key: "npm:^4.0.0" + checksum: 10c0/124df74820c40c2eb9a8612a254ea1d557ddfab1581c3e751f825e3e366d9f00b0d76a3c94ecd8398e7f3eee193018622677e95816e8491f0797b21e30b2deba + languageName: node + linkType: hard + +"obuf@npm:~1.1.2": + version: 1.1.2 + resolution: "obuf@npm:1.1.2" + checksum: 10c0/520aaac7ea701618eacf000fc96ae458e20e13b0569845800fc582f81b386731ab22d55354b4915d58171db00e79cfcd09c1638c02f89577ef092b38c65b7d81 + languageName: node + linkType: hard + +"oldschooljs@npm:^2.5.10": + version: 2.5.10 + resolution: "oldschooljs@npm:2.5.10" + dependencies: + deepmerge: "npm:4.3.1" + e: "npm:^0.2.33" + node-fetch: "npm:2.6.7" + checksum: 10c0/c8ff7fcaee60ebcc27c918fb315e226c3b0de93d45d2779733b6530156761a94aca760f7b247c8d4812cc46b865bc9a9916359be0fee996e018c37c927ec2696 + languageName: node + linkType: hard + +"onetime@npm:^5.1.0": + version: 5.1.2 + resolution: "onetime@npm:5.1.2" + dependencies: + mimic-fn: "npm:^2.1.0" + checksum: 10c0/ffcef6fbb2692c3c40749f31ea2e22677a876daea92959b8a80b521d95cca7a668c884d8b2045d1d8ee7d56796aa405c405462af112a1477594cc63531baeb8f + languageName: node + linkType: hard + +"onetime@npm:^6.0.0": + version: 6.0.0 + resolution: "onetime@npm:6.0.0" + dependencies: + mimic-fn: "npm:^4.0.0" + checksum: 10c0/4eef7c6abfef697dd4479345a4100c382d73c149d2d56170a54a07418c50816937ad09500e1ed1e79d235989d073a9bade8557122aee24f0576ecde0f392bb6c + languageName: node + linkType: hard + +"opentelemetry-instrumentation-fetch-node@npm:1.2.0": + version: 1.2.0 + resolution: "opentelemetry-instrumentation-fetch-node@npm:1.2.0" + dependencies: + "@opentelemetry/api": "npm:^1.6.0" + "@opentelemetry/instrumentation": "npm:^0.43.0" + "@opentelemetry/semantic-conventions": "npm:^1.17.0" + checksum: 10c0/59c1bdd568476a9204334eb19a297b4cb95f1c7000b9174511baa60ffb526ea6508f179558e56f14f808f7f213a6c60150f74a94d695f94e67ef42c4fd8aa262 + languageName: node + linkType: hard + +"ora@npm:^5.4.1": + version: 5.4.1 + resolution: "ora@npm:5.4.1" + dependencies: + bl: "npm:^4.1.0" + chalk: "npm:^4.1.0" + cli-cursor: "npm:^3.1.0" + cli-spinners: "npm:^2.5.0" + is-interactive: "npm:^1.0.0" + is-unicode-supported: "npm:^0.1.0" + log-symbols: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + wcwidth: "npm:^1.0.1" + checksum: 10c0/10ff14aace236d0e2f044193362b22edce4784add08b779eccc8f8ef97195cae1248db8ec1ec5f5ff076f91acbe573f5f42a98c19b78dba8c54eefff983cae85 + languageName: node + linkType: hard + +"os-tmpdir@npm:~1.0.2": + version: 1.0.2 + resolution: "os-tmpdir@npm:1.0.2" + checksum: 10c0/f438450224f8e2687605a8dd318f0db694b6293c5d835ae509a69e97c8de38b6994645337e5577f5001115470414638978cc49da1cdcc25106dad8738dc69990 + languageName: node + linkType: hard + +"p-finally@npm:^1.0.0": + version: 1.0.0 + resolution: "p-finally@npm:1.0.0" + checksum: 10c0/6b8552339a71fe7bd424d01d8451eea92d379a711fc62f6b2fe64cad8a472c7259a236c9a22b4733abca0b5666ad503cb497792a0478c5af31ded793d00937e7 + languageName: node + linkType: hard + +"p-map@npm:^4.0.0": + version: 4.0.0 + resolution: "p-map@npm:4.0.0" + dependencies: + aggregate-error: "npm:^3.0.0" + checksum: 10c0/592c05bd6262c466ce269ff172bb8de7c6975afca9b50c975135b974e9bdaafbfe80e61aaaf5be6d1200ba08b30ead04b88cfa7e25ff1e3b93ab28c9f62a2c75 + languageName: node + linkType: hard + +"p-queue@npm:^6.6.2": + version: 6.6.2 + resolution: "p-queue@npm:6.6.2" + dependencies: + eventemitter3: "npm:^4.0.4" + p-timeout: "npm:^3.2.0" + checksum: 10c0/5739ecf5806bbeadf8e463793d5e3004d08bb3f6177bd1a44a005da8fd81bb90f80e4633e1fb6f1dfd35ee663a5c0229abe26aebb36f547ad5a858347c7b0d3e + languageName: node + linkType: hard + +"p-timeout@npm:^3.2.0": + version: 3.2.0 + resolution: "p-timeout@npm:3.2.0" + dependencies: + p-finally: "npm:^1.0.0" + checksum: 10c0/524b393711a6ba8e1d48137c5924749f29c93d70b671e6db761afa784726572ca06149c715632da8f70c090073afb2af1c05730303f915604fd38ee207b70a61 + languageName: node + linkType: hard + +"package-json-from-dist@npm:^1.0.0": + version: 1.0.0 + resolution: "package-json-from-dist@npm:1.0.0" + checksum: 10c0/e3ffaf6ac1040ab6082a658230c041ad14e72fabe99076a2081bb1d5d41210f11872403fc09082daf4387fc0baa6577f96c9c0e94c90c394fd57794b66aa4033 + languageName: node + linkType: hard + +"path-key@npm:^3.1.0": + version: 3.1.1 + resolution: "path-key@npm:3.1.1" + checksum: 10c0/748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c + languageName: node + linkType: hard + +"path-key@npm:^4.0.0": + version: 4.0.0 + resolution: "path-key@npm:4.0.0" + checksum: 10c0/794efeef32863a65ac312f3c0b0a99f921f3e827ff63afa5cb09a377e202c262b671f7b3832a4e64731003fa94af0263713962d317b9887bd1e0c48a342efba3 + languageName: node + linkType: hard + +"path-parse@npm:^1.0.7": + version: 1.0.7 + resolution: "path-parse@npm:1.0.7" + checksum: 10c0/11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1 + languageName: node + linkType: hard + +"path-scurry@npm:^1.11.1": + version: 1.11.1 + resolution: "path-scurry@npm:1.11.1" + dependencies: + lru-cache: "npm:^10.2.0" + minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" + checksum: 10c0/32a13711a2a505616ae1cc1b5076801e453e7aae6ac40ab55b388bb91b9d0547a52f5aaceff710ea400205f18691120d4431e520afbe4266b836fadede15872d + languageName: node + linkType: hard + +"pathe@npm:^1.1.2": + version: 1.1.2 + resolution: "pathe@npm:1.1.2" + checksum: 10c0/64ee0a4e587fb0f208d9777a6c56e4f9050039268faaaaecd50e959ef01bf847b7872785c36483fa5cdcdbdfdb31fef2ff222684d4fc21c330ab60395c681897 + languageName: node + linkType: hard + +"pathval@npm:^2.0.0": + version: 2.0.0 + resolution: "pathval@npm:2.0.0" + checksum: 10c0/602e4ee347fba8a599115af2ccd8179836a63c925c23e04bd056d0674a64b39e3a081b643cc7bc0b84390517df2d800a46fcc5598d42c155fe4977095c2f77c5 + languageName: node + linkType: hard + +"pg-int8@npm:1.0.1": + version: 1.0.1 + resolution: "pg-int8@npm:1.0.1" + checksum: 10c0/be6a02d851fc2a4ae3e9de81710d861de3ba35ac927268973eb3cb618873a05b9424656df464dd43bd7dc3fc5295c3f5b3c8349494f87c7af50ec59ef14e0b98 + languageName: node + linkType: hard + +"pg-numeric@npm:1.0.2": + version: 1.0.2 + resolution: "pg-numeric@npm:1.0.2" + checksum: 10c0/43dd9884e7b52c79ddc28d2d282d7475fce8bba13452d33c04ceb2e0a65f561edf6699694e8e1c832ff9093770496363183c950dd29608e1bdd98f344b25bca9 + languageName: node + linkType: hard + +"pg-protocol@npm:*": + version: 1.6.1 + resolution: "pg-protocol@npm:1.6.1" + checksum: 10c0/7eadef4010ac0a3925c460be7332ca4098a5c6d5181725a62193fcfa800000ae6632d98d814f3989b42cf5fdc3b45e34c714a1959d29174e81e30730e140ae5f + languageName: node + linkType: hard + +"pg-types@npm:^2.2.0": + version: 2.2.0 + resolution: "pg-types@npm:2.2.0" + dependencies: + pg-int8: "npm:1.0.1" + postgres-array: "npm:~2.0.0" + postgres-bytea: "npm:~1.0.0" + postgres-date: "npm:~1.0.4" + postgres-interval: "npm:^1.1.0" + checksum: 10c0/ab3f8069a323f601cd2d2279ca8c425447dab3f9b61d933b0601d7ffc00d6200df25e26a4290b2b0783b59278198f7dd2ed03e94c4875797919605116a577c65 + languageName: node + linkType: hard + +"pg-types@npm:^4.0.1": + version: 4.0.2 + resolution: "pg-types@npm:4.0.2" + dependencies: + pg-int8: "npm:1.0.1" + pg-numeric: "npm:1.0.2" + postgres-array: "npm:~3.0.1" + postgres-bytea: "npm:~3.0.0" + postgres-date: "npm:~2.1.0" + postgres-interval: "npm:^3.0.0" + postgres-range: "npm:^1.1.1" + checksum: 10c0/780fccda2f3fa2a34e85a72e8e7dadb7d88fbe71ce88f126cb3313f333ad836d02488ec4ff3d94d0c1e5846f735d6e6c6281f8059e6b8919d2180429acaec3e2 + languageName: node + linkType: hard + +"picocolors@npm:^1.0.0": + version: 1.0.0 + resolution: "picocolors@npm:1.0.0" + checksum: 10c0/20a5b249e331c14479d94ec6817a182fd7a5680debae82705747b2db7ec50009a5f6648d0621c561b0572703f84dbef0858abcbd5856d3c5511426afcb1961f7 + languageName: node + linkType: hard + +"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1": + version: 2.3.1 + resolution: "picomatch@npm:2.3.1" + checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be + languageName: node + linkType: hard + +"picomatch@npm:^2.2.3": + version: 2.3.0 + resolution: "picomatch@npm:2.3.0" + checksum: 10c0/a65bde78212368e16afb82429a0ea033d20a836270446acb53ec6e31d939bccf1213f788bc49361f7aff47b67c1fb74d898f99964f67f26ca07a3cd815ddbcbb + languageName: node + linkType: hard + +"piscina@npm:^4.6.1": + version: 4.6.1 + resolution: "piscina@npm:4.6.1" + dependencies: + nice-napi: "npm:^1.0.2" + dependenciesMeta: + nice-napi: + optional: true + checksum: 10c0/2225fb42806f8d72bf09f2528bd65721b440dcc63ece957a9542a28b3b958be353dc48802fb11a8af66fdfd28a419300ed28e04573b8bf420e6dcfe63d6f58b5 + languageName: node + linkType: hard + +"postcss@npm:^8.4.38": + version: 8.4.38 + resolution: "postcss@npm:8.4.38" + dependencies: + nanoid: "npm:^3.3.7" + picocolors: "npm:^1.0.0" + source-map-js: "npm:^1.2.0" + checksum: 10c0/955407b8f70cf0c14acf35dab3615899a2a60a26718a63c848cf3c29f2467b0533991b985a2b994430d890bd7ec2b1963e36352b0774a19143b5f591540f7c06 + languageName: node + linkType: hard + +"postgres-array@npm:~2.0.0": + version: 2.0.0 + resolution: "postgres-array@npm:2.0.0" + checksum: 10c0/cbd56207e4141d7fbf08c86f2aebf21fa7064943d3f808ec85f442ff94b48d891e7a144cc02665fb2de5dbcb9b8e3183a2ac749959e794b4a4cfd379d7a21d08 + languageName: node + linkType: hard + +"postgres-array@npm:~3.0.1": + version: 3.0.2 + resolution: "postgres-array@npm:3.0.2" + checksum: 10c0/644aa071f67a66a59f641f8e623887d2b915bc102a32643e2aa8b54c11acd343c5ad97831ea444dd37bd4b921ba35add4aa2cb0c6b76700a8252c2324aeba5b4 + languageName: node + linkType: hard + +"postgres-bytea@npm:~1.0.0": + version: 1.0.0 + resolution: "postgres-bytea@npm:1.0.0" + checksum: 10c0/febf2364b8a8953695cac159eeb94542ead5886792a9627b97e33f6b5bb6e263bc0706ab47ec221516e79fbd6b2452d668841830fb3b49ec6c0fc29be61892ce + languageName: node + linkType: hard + +"postgres-bytea@npm:~3.0.0": + version: 3.0.0 + resolution: "postgres-bytea@npm:3.0.0" + dependencies: + obuf: "npm:~1.1.2" + checksum: 10c0/41c79cc48aa730c5ba3eda6ab989a940034f07a1f57b8f2777dce56f1b8cca16c5870582932b5b10cc605048aef9b6157e06253c871b4717cafc6d00f55376aa + languageName: node + linkType: hard + +"postgres-date@npm:~1.0.4": + version: 1.0.7 + resolution: "postgres-date@npm:1.0.7" + checksum: 10c0/0ff91fccc64003e10b767fcfeefb5eaffbc522c93aa65d5051c49b3c4ce6cb93ab091a7d22877a90ad60b8874202c6f1d0f935f38a7235ed3b258efd54b97ca9 + languageName: node + linkType: hard + +"postgres-date@npm:~2.1.0": + version: 2.1.0 + resolution: "postgres-date@npm:2.1.0" + checksum: 10c0/00a7472c10788f6b0d08d24108bf1eb80858de1bd6317740198a564918ea4a69b80c98148167b92ae688abd606483020d0de0dd3a36f3ea9a3e26bbeef3464f4 + languageName: node + linkType: hard + +"postgres-interval@npm:^1.1.0": + version: 1.2.0 + resolution: "postgres-interval@npm:1.2.0" + dependencies: + xtend: "npm:^4.0.0" + checksum: 10c0/c1734c3cb79e7f22579af0b268a463b1fa1d084e742a02a7a290c4f041e349456f3bee3b4ee0bb3f226828597f7b76deb615c1b857db9a742c45520100456272 + languageName: node + linkType: hard + +"postgres-interval@npm:^3.0.0": + version: 3.0.0 + resolution: "postgres-interval@npm:3.0.0" + checksum: 10c0/8b570b30ea37c685e26d136d34460f246f98935a1533defc4b53bb05ee23ae3dc7475b718ec7ea607a57894d8c6b4f1adf67ca9cc83a75bdacffd427d5c68de8 + languageName: node + linkType: hard + +"postgres-range@npm:^1.1.1": + version: 1.1.4 + resolution: "postgres-range@npm:1.1.4" + checksum: 10c0/254494ef81df208e0adeae6b66ce394aba37914ea14c7ece55a45fb6691b7db04bee74c825380a47c887a9f87158fd3d86f758f9cc60b76d3a38ce5aca7912e8 + languageName: node + linkType: hard + +"prettier@npm:^3.3.2": + version: 3.3.2 + resolution: "prettier@npm:3.3.2" + bin: + prettier: bin/prettier.cjs + checksum: 10c0/39ed27d17f0238da6dd6571d63026566bd790d3d0edac57c285fbab525982060c8f1e01955fe38134ab10f0951a6076da37f015db8173c02f14bc7f0803a384c + languageName: node + linkType: hard + +"printable-characters@npm:^1.0.42": + version: 1.0.42 + resolution: "printable-characters@npm:1.0.42" + checksum: 10c0/7c94d94c6041a37c385af770c7402ad5a2e8a3429ca4d2505a9f19fde39bac9a8fd1edfbfa02f1eae5b4b0f3536b6b8ee6c84621f7c0fcb41476b2df6ee20e4b + languageName: node + linkType: hard + +"prisma@npm:^5.17.0": + version: 5.17.0 + resolution: "prisma@npm:5.17.0" + dependencies: + "@prisma/engines": "npm:5.17.0" + bin: + prisma: build/index.js + checksum: 10c0/30546a8576ffadf66d6f34cd833e25e21eec99847db92c4d88f6c9dbbc401abbd3f699f9e0f0dbcd9d5229ccba47c6aadb42ba6cd6e29afb7335689c7257c964 + languageName: node + linkType: hard + +"proc-log@npm:^3.0.0": + version: 3.0.0 + resolution: "proc-log@npm:3.0.0" + checksum: 10c0/f66430e4ff947dbb996058f6fd22de2c66612ae1a89b097744e17fb18a4e8e7a86db99eda52ccf15e53f00b63f4ec0b0911581ff2aac0355b625c8eac509b0dc + languageName: node + linkType: hard + +"proc-log@npm:^4.2.0": + version: 4.2.0 + resolution: "proc-log@npm:4.2.0" + checksum: 10c0/17db4757c2a5c44c1e545170e6c70a26f7de58feb985091fb1763f5081cab3d01b181fb2dd240c9f4a4255a1d9227d163d5771b7e69c9e49a561692db865efb9 + languageName: node + linkType: hard + +"promise-retry@npm:^2.0.1": + version: 2.0.1 + resolution: "promise-retry@npm:2.0.1" + dependencies: + err-code: "npm:^2.0.2" + retry: "npm:^0.12.0" + checksum: 10c0/9c7045a1a2928094b5b9b15336dcd2a7b1c052f674550df63cc3f36cd44028e5080448175b6f6ca32b642de81150f5e7b1a98b728f15cb069f2dd60ac2616b96 + languageName: node + linkType: hard + +"pstree.remy@npm:^1.1.8": + version: 1.1.8 + resolution: "pstree.remy@npm:1.1.8" + checksum: 10c0/30f78c88ce6393cb3f7834216cb6e282eb83c92ccb227430d4590298ab2811bc4a4745f850a27c5178e79a8f3e316591de0fec87abc19da648c2b3c6eb766d14 + languageName: node + linkType: hard + +"pure-rand@npm:^6.1.0": + version: 6.1.0 + resolution: "pure-rand@npm:6.1.0" + checksum: 10c0/1abe217897bf74dcb3a0c9aba3555fe975023147b48db540aa2faf507aee91c03bf54f6aef0eb2bf59cc259a16d06b28eca37f0dc426d94f4692aeff02fb0e65 + languageName: node + linkType: hard + +"queue-microtask@npm:^1.2.2": + version: 1.2.3 + resolution: "queue-microtask@npm:1.2.3" + checksum: 10c0/900a93d3cdae3acd7d16f642c29a642aea32c2026446151f0778c62ac089d4b8e6c986811076e1ae180a694cedf077d453a11b58ff0a865629a4f82ab558e102 + languageName: node + linkType: hard + +"random-js@npm:^2.1.0": + version: 2.1.0 + resolution: "random-js@npm:2.1.0" + checksum: 10c0/900cb56b8cf17295a74ae17f4dca2fcb1e25fba8bc784f839810d23990746dca909b266407bf08a68a7b194b87ebbc66b56071b94ac175e20258b927f24191c4 + languageName: node + linkType: hard + +"readable-stream@npm:^3.4.0": + version: 3.6.2 + resolution: "readable-stream@npm:3.6.2" + dependencies: + inherits: "npm:^2.0.3" + string_decoder: "npm:^1.1.1" + util-deprecate: "npm:^1.0.1" + checksum: 10c0/e37be5c79c376fdd088a45fa31ea2e423e5d48854be7a22a58869b4e84d25047b193f6acb54f1012331e1bcd667ffb569c01b99d36b0bd59658fb33f513511b7 + languageName: node + linkType: hard + +"readdirp@npm:~3.6.0": + version: 3.6.0 + resolution: "readdirp@npm:3.6.0" + dependencies: + picomatch: "npm:^2.2.1" + checksum: 10c0/6fa848cf63d1b82ab4e985f4cf72bd55b7dcfd8e0a376905804e48c3634b7e749170940ba77b32804d5fe93b3cc521aa95a8d7e7d725f830da6d93f3669ce66b + languageName: node + linkType: hard + +"readline-sync@npm:^1.4.9": + version: 1.4.10 + resolution: "readline-sync@npm:1.4.10" + checksum: 10c0/0a4d0fe4ad501f8f005a3c9cbf3cc0ae6ca2ced93e9a1c7c46f226bdfcb6ef5d3f437ae7e9d2e1098ee13524a3739c830e4c8dbc7f543a693eecd293e41093a3 + languageName: node + linkType: hard + +"redis-errors@npm:^1.0.0, redis-errors@npm:^1.2.0": + version: 1.2.0 + resolution: "redis-errors@npm:1.2.0" + checksum: 10c0/5b316736e9f532d91a35bff631335137a4f974927bb2fb42bf8c2f18879173a211787db8ac4c3fde8f75ed6233eb0888e55d52510b5620e30d69d7d719c8b8a7 + languageName: node + linkType: hard + +"redis-parser@npm:^3.0.0": + version: 3.0.0 + resolution: "redis-parser@npm:3.0.0" + dependencies: + redis-errors: "npm:^1.0.0" + checksum: 10c0/ee16ac4c7b2a60b1f42a2cdaee22b005bd4453eb2d0588b8a4939718997ae269da717434da5d570fe0b05030466eeb3f902a58cf2e8e1ca058bf6c9c596f632f + languageName: node + linkType: hard + +"regenerator-runtime@npm:^0.14.0": + version: 0.14.1 + resolution: "regenerator-runtime@npm:0.14.1" + checksum: 10c0/1b16eb2c4bceb1665c89de70dcb64126a22bc8eb958feef3cd68fe11ac6d2a4899b5cd1b80b0774c7c03591dc57d16631a7f69d2daa2ec98100e2f29f7ec4cc4 + languageName: node + linkType: hard + +"remeda@npm:^2.7.0": + version: 2.7.0 + resolution: "remeda@npm:2.7.0" + dependencies: + type-fest: "npm:^4.21.0" + checksum: 10c0/4e7d0dc616f00961653244ea9df3f297720fc9346ac8ec7502abf4c434741af4a4750d5bd83ea9938ee406089b37e3a2270b8f022d48b345ba83218e47dd8918 + languageName: node + linkType: hard + +"require-directory@npm:^2.1.1": + version: 2.1.1 + resolution: "require-directory@npm:2.1.1" + checksum: 10c0/83aa76a7bc1531f68d92c75a2ca2f54f1b01463cb566cf3fbc787d0de8be30c9dbc211d1d46be3497dac5785fe296f2dd11d531945ac29730643357978966e99 + languageName: node + linkType: hard + +"require-in-the-middle@npm:^7.1.1": + version: 7.3.0 + resolution: "require-in-the-middle@npm:7.3.0" + dependencies: + debug: "npm:^4.1.1" + module-details-from-path: "npm:^1.0.3" + resolve: "npm:^1.22.1" + checksum: 10c0/a8e29393b647f7109f8c07e9276123482ba19d7be4b749d247036573ef4539eaf527bdb66c2e2730b99a74e7badc36f7b6d4a75c6e5b5f798a5f304e78ade797 + languageName: node + linkType: hard + +"resolve-pkg-maps@npm:^1.0.0": + version: 1.0.0 + resolution: "resolve-pkg-maps@npm:1.0.0" + checksum: 10c0/fb8f7bbe2ca281a73b7ef423a1cbc786fb244bd7a95cbe5c3fba25b27d327150beca8ba02f622baea65919a57e061eb5005204daa5f93ed590d9b77463a567ab + languageName: node + linkType: hard + +"resolve@npm:^1.22.1": + version: 1.22.8 + resolution: "resolve@npm:1.22.8" + dependencies: + is-core-module: "npm:^2.13.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/07e179f4375e1fd072cfb72ad66d78547f86e6196c4014b31cb0b8bb1db5f7ca871f922d08da0fbc05b94e9fd42206f819648fa3b5b873ebbc8e1dc68fec433a + languageName: node + linkType: hard + +"resolve@patch:resolve@npm%3A^1.22.1#optional!builtin": + version: 1.22.8 + resolution: "resolve@patch:resolve@npm%3A1.22.8#optional!builtin::version=1.22.8&hash=c3c19d" + dependencies: + is-core-module: "npm:^2.13.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/0446f024439cd2e50c6c8fa8ba77eaa8370b4180f401a96abf3d1ebc770ac51c1955e12764cde449fde3fff480a61f84388e3505ecdbab778f4bef5f8212c729 + languageName: node + linkType: hard + +"restore-cursor@npm:^3.1.0": + version: 3.1.0 + resolution: "restore-cursor@npm:3.1.0" + dependencies: + onetime: "npm:^5.1.0" + signal-exit: "npm:^3.0.2" + checksum: 10c0/8051a371d6aa67ff21625fa94e2357bd81ffdc96267f3fb0fc4aaf4534028343836548ef34c240ffa8c25b280ca35eb36be00b3cb2133fa4f51896d7e73c6b4f + languageName: node + linkType: hard + +"retry@npm:^0.12.0": + version: 0.12.0 + resolution: "retry@npm:0.12.0" + checksum: 10c0/59933e8501727ba13ad73ef4a04d5280b3717fd650408460c987392efe9d7be2040778ed8ebe933c5cbd63da3dcc37919c141ef8af0a54a6e4fca5a2af177bfe + languageName: node + linkType: hard + +"reusify@npm:^1.0.4": + version: 1.0.4 + resolution: "reusify@npm:1.0.4" + checksum: 10c0/c19ef26e4e188f408922c46f7ff480d38e8dfc55d448310dfb518736b23ed2c4f547fb64a6ed5bdba92cd7e7ddc889d36ff78f794816d5e71498d645ef476107 + languageName: node + linkType: hard + +"rollup@npm:^4.13.0": + version: 4.14.2 + resolution: "rollup@npm:4.14.2" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.14.2" + "@rollup/rollup-android-arm64": "npm:4.14.2" + "@rollup/rollup-darwin-arm64": "npm:4.14.2" + "@rollup/rollup-darwin-x64": "npm:4.14.2" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.14.2" + "@rollup/rollup-linux-arm64-gnu": "npm:4.14.2" + "@rollup/rollup-linux-arm64-musl": "npm:4.14.2" + "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.14.2" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.14.2" + "@rollup/rollup-linux-s390x-gnu": "npm:4.14.2" + "@rollup/rollup-linux-x64-gnu": "npm:4.14.2" + "@rollup/rollup-linux-x64-musl": "npm:4.14.2" + "@rollup/rollup-win32-arm64-msvc": "npm:4.14.2" + "@rollup/rollup-win32-ia32-msvc": "npm:4.14.2" + "@rollup/rollup-win32-x64-msvc": "npm:4.14.2" + "@types/estree": "npm:1.0.5" + fsevents: "npm:~2.3.2" + dependenciesMeta: + "@rollup/rollup-android-arm-eabi": + optional: true + "@rollup/rollup-android-arm64": + optional: true + "@rollup/rollup-darwin-arm64": + optional: true + "@rollup/rollup-darwin-x64": + optional: true + "@rollup/rollup-linux-arm-gnueabihf": + optional: true + "@rollup/rollup-linux-arm64-gnu": + optional: true + "@rollup/rollup-linux-arm64-musl": + optional: true + "@rollup/rollup-linux-powerpc64le-gnu": + optional: true + "@rollup/rollup-linux-riscv64-gnu": + optional: true + "@rollup/rollup-linux-s390x-gnu": + optional: true + "@rollup/rollup-linux-x64-gnu": + optional: true + "@rollup/rollup-linux-x64-musl": + optional: true + "@rollup/rollup-win32-arm64-msvc": + optional: true + "@rollup/rollup-win32-ia32-msvc": + optional: true + "@rollup/rollup-win32-x64-msvc": + optional: true + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 10c0/3826aaa847a16e8ac0459e20e3ec624295110c0aac2c7b4f970f75ca804d7437bbd002a37079410f71137691aab64649e65361018647b0412911358a17b97902 + languageName: node + linkType: hard + +"root-workspace-0b6124@workspace:.": + version: 0.0.0-use.local + resolution: "root-workspace-0b6124@workspace:." + dependencies: + "@biomejs/biome": "npm:^1.8.3" + "@napi-rs/canvas": "npm:^0.1.53" + "@oldschoolgg/toolkit": "git+https://github.com/oldschoolgg/toolkit.git#cd7c6865229ca7dc4a66b3816586f2d3f4a4fbed" + "@prisma/client": "npm:^5.17.0" + "@sapphire/ratelimits": "npm:^2.4.9" + "@sapphire/snowflake": "npm:^3.5.3" + "@sapphire/time-utilities": "npm:^1.6.0" + "@sapphire/timer-manager": "npm:^1.0.2" + "@sentry/node": "npm:^8.15.0" + "@types/lodash": "npm:^4.14.195" + "@types/node": "npm:^20.14.9" + "@types/node-cron": "npm:^3.0.7" + "@types/node-fetch": "npm:^2.6.1" + "@vitest/coverage-v8": "npm:^2.0.3" + ascii-table3: "npm:^0.9.0" + bufferutil: "npm:^4.0.8" + concurrently: "npm:^8.2.2" + discord.js: "npm:^14.15.3" + dotenv: "npm:^16.4.5" + dpdm: "npm:^3.14.0" + e: "npm:0.2.33" + esbuild: "npm:0.21.5" + exit-hook: "npm:^4.0.0" + fast-deep-equal: "npm:^3.1.3" + fast-glob: "npm:^3.3.2" + lodash: "npm:^4.17.21" + lru-cache: "npm:^10.3.0" + murmurhash: "npm:^2.0.1" + node-cron: "npm:^3.0.3" + node-fetch: "npm:^2.6.7" + nodemon: "npm:^3.1.4" + oldschooljs: "npm:^2.5.10" + p-queue: "npm:^6.6.2" + piscina: "npm:^4.6.1" + prettier: "npm:^3.3.2" + prisma: "npm:^5.17.0" + random-js: "npm:^2.1.0" + remeda: "npm:^2.7.0" + simple-statistics: "npm:^7.8.3" + sonic-boom: "npm:^4.0.1" + tsx: "npm:^4.16.2" + typescript: "npm:^5.5.3" + vitest: "npm:^2.0.3" + zlib-sync: "npm:^0.1.9" + zod: "npm:^3.23.8" + languageName: unknown + linkType: soft + +"run-parallel@npm:^1.1.9": + version: 1.2.0 + resolution: "run-parallel@npm:1.2.0" + dependencies: + queue-microtask: "npm:^1.2.2" + checksum: 10c0/200b5ab25b5b8b7113f9901bfe3afc347e19bb7475b267d55ad0eb86a62a46d77510cb0f232507c9e5d497ebda569a08a9867d0d14f57a82ad5564d991588b39 + languageName: node + linkType: hard + +"rxjs@npm:^7.8.1": + version: 7.8.1 + resolution: "rxjs@npm:7.8.1" + dependencies: + tslib: "npm:^2.1.0" + checksum: 10c0/3c49c1ecd66170b175c9cacf5cef67f8914dcbc7cd0162855538d365c83fea631167cacb644b3ce533b2ea0e9a4d0b12175186985f89d75abe73dbd8f7f06f68 + languageName: node + linkType: hard + +"safe-buffer@npm:~5.2.0": + version: 5.2.1 + resolution: "safe-buffer@npm:5.2.1" + checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 + languageName: node + linkType: hard + +"safer-buffer@npm:>= 2.1.2 < 3.0.0": + version: 2.1.2 + resolution: "safer-buffer@npm:2.1.2" + checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 + languageName: node + linkType: hard + +"semver@npm:^6.0.0": + version: 6.3.1 + resolution: "semver@npm:6.3.1" + bin: + semver: bin/semver.js + checksum: 10c0/e3d79b609071caa78bcb6ce2ad81c7966a46a7431d9d58b8800cfa9cb6a63699b3899a0e4bcce36167a284578212d9ae6942b6929ba4aa5015c079a67751d42d + languageName: node + linkType: hard + +"semver@npm:^7.3.5, semver@npm:^7.5.2, semver@npm:^7.5.4": + version: 7.6.2 + resolution: "semver@npm:7.6.2" + bin: + semver: bin/semver.js + checksum: 10c0/97d3441e97ace8be4b1976433d1c32658f6afaff09f143e52c593bae7eef33de19e3e369c88bd985ce1042c6f441c80c6803078d1de2a9988080b66684cbb30c + languageName: node + linkType: hard + +"semver@npm:^7.5.3": + version: 7.6.0 + resolution: "semver@npm:7.6.0" + dependencies: + lru-cache: "npm:^6.0.0" + bin: + semver: bin/semver.js + checksum: 10c0/fbfe717094ace0aa8d6332d7ef5ce727259815bd8d8815700853f4faf23aacbd7192522f0dc5af6df52ef4fa85a355ebd2f5d39f554bd028200d6cf481ab9b53 + languageName: node + linkType: hard + +"shebang-command@npm:^2.0.0": + version: 2.0.0 + resolution: "shebang-command@npm:2.0.0" + dependencies: + shebang-regex: "npm:^3.0.0" + checksum: 10c0/a41692e7d89a553ef21d324a5cceb5f686d1f3c040759c50aab69688634688c5c327f26f3ecf7001ebfd78c01f3c7c0a11a7c8bfd0a8bc9f6240d4f40b224e4e + languageName: node + linkType: hard + +"shebang-regex@npm:^3.0.0": + version: 3.0.0 + resolution: "shebang-regex@npm:3.0.0" + checksum: 10c0/1dbed0726dd0e1152a92696c76c7f06084eb32a90f0528d11acd764043aacf76994b2fb30aa1291a21bd019d6699164d048286309a278855ee7bec06cf6fb690 + languageName: node + linkType: hard + +"shell-quote@npm:^1.8.1": + version: 1.8.1 + resolution: "shell-quote@npm:1.8.1" + checksum: 10c0/8cec6fd827bad74d0a49347057d40dfea1e01f12a6123bf82c4649f3ef152fc2bc6d6176e6376bffcd205d9d0ccb4f1f9acae889384d20baff92186f01ea455a + languageName: node + linkType: hard + +"shimmer@npm:^1.2.1": + version: 1.2.1 + resolution: "shimmer@npm:1.2.1" + checksum: 10c0/ae8b27c389db2a00acfc8da90240f11577685a8f3e40008f826a3bea8b4f3b3ecd305c26be024b4a0fd3b123d132c1569d6e238097960a9a543b6c60760fb46a + languageName: node + linkType: hard + +"siginfo@npm:^2.0.0": + version: 2.0.0 + resolution: "siginfo@npm:2.0.0" + checksum: 10c0/3def8f8e516fbb34cb6ae415b07ccc5d9c018d85b4b8611e3dc6f8be6d1899f693a4382913c9ed51a06babb5201639d76453ab297d1c54a456544acf5c892e34 + languageName: node + linkType: hard + +"signal-exit@npm:^3.0.2": + version: 3.0.7 + resolution: "signal-exit@npm:3.0.7" + checksum: 10c0/25d272fa73e146048565e08f3309d5b942c1979a6f4a58a8c59d5fa299728e9c2fcd1a759ec870863b1fd38653670240cd420dad2ad9330c71f36608a6a1c912 + languageName: node + linkType: hard + +"signal-exit@npm:^4.0.1, signal-exit@npm:^4.1.0": + version: 4.1.0 + resolution: "signal-exit@npm:4.1.0" + checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83 + languageName: node + linkType: hard + +"simple-statistics@npm:^7.8.3": + version: 7.8.3 + resolution: "simple-statistics@npm:7.8.3" + checksum: 10c0/a33953334ad444c0cfdb5fe00e02fb8c860466856c0583925ae42e2668b3f08a9b9ac01988efb4304a79a776886d89897632f43a919396884be6906052132683 + languageName: node + linkType: hard + +"simple-update-notifier@npm:^2.0.0": + version: 2.0.0 + resolution: "simple-update-notifier@npm:2.0.0" + dependencies: + semver: "npm:^7.5.3" + checksum: 10c0/2a00bd03bfbcbf8a737c47ab230d7920f8bfb92d1159d421bdd194479f6d01ebc995d13fbe13d45dace23066a78a3dc6642999b4e3b38b847e6664191575b20c + languageName: node + linkType: hard + +"smart-buffer@npm:^4.2.0": + version: 4.2.0 + resolution: "smart-buffer@npm:4.2.0" + checksum: 10c0/a16775323e1404dd43fabafe7460be13a471e021637bc7889468eb45ce6a6b207261f454e4e530a19500cc962c4cc5348583520843b363f4193cee5c00e1e539 + languageName: node + linkType: hard + +"socks-proxy-agent@npm:^8.0.3": + version: 8.0.4 + resolution: "socks-proxy-agent@npm:8.0.4" + dependencies: + agent-base: "npm:^7.1.1" + debug: "npm:^4.3.4" + socks: "npm:^2.8.3" + checksum: 10c0/345593bb21b95b0508e63e703c84da11549f0a2657d6b4e3ee3612c312cb3a907eac10e53b23ede3557c6601d63252103494caa306b66560f43af7b98f53957a + languageName: node + linkType: hard + +"socks@npm:^2.8.3": + version: 2.8.3 + resolution: "socks@npm:2.8.3" + dependencies: + ip-address: "npm:^9.0.5" + smart-buffer: "npm:^4.2.0" + checksum: 10c0/d54a52bf9325165770b674a67241143a3d8b4e4c8884560c4e0e078aace2a728dffc7f70150660f51b85797c4e1a3b82f9b7aa25e0a0ceae1a243365da5c51a7 + languageName: node + linkType: hard + +"sonic-boom@npm:^4.0.1": + version: 4.0.1 + resolution: "sonic-boom@npm:4.0.1" + dependencies: + atomic-sleep: "npm:^1.0.0" + checksum: 10c0/7b467f2bc8af7ff60bf210382f21c59728cc4b769af9b62c31dd88723f5cc472752d2320736cc366acc7c765ddd5bec3072c033b0faf249923f576a7453ba9d3 + languageName: node + linkType: hard + +"source-map-js@npm:^1.2.0": + version: 1.2.0 + resolution: "source-map-js@npm:1.2.0" + checksum: 10c0/7e5f896ac10a3a50fe2898e5009c58ff0dc102dcb056ed27a354623a0ece8954d4b2649e1a1b2b52ef2e161d26f8859c7710350930751640e71e374fe2d321a4 + languageName: node + linkType: hard + +"spawn-command@npm:0.0.2": + version: 0.0.2 + resolution: "spawn-command@npm:0.0.2" + checksum: 10c0/b22f2d71239e6e628a400831861ba747750bbb40c0a53323754cf7b84330b73d81e40ff1f9055e6d1971818679510208a9302e13d9ff3b32feb67e74d7a1b3ef + languageName: node + linkType: hard + +"sprintf-js@npm:^1.1.1, sprintf-js@npm:^1.1.3": + version: 1.1.3 + resolution: "sprintf-js@npm:1.1.3" + checksum: 10c0/09270dc4f30d479e666aee820eacd9e464215cdff53848b443964202bf4051490538e5dd1b42e1a65cf7296916ca17640aebf63dae9812749c7542ee5f288dec + languageName: node + linkType: hard + +"ssri@npm:^10.0.0": + version: 10.0.6 + resolution: "ssri@npm:10.0.6" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/e5a1e23a4057a86a97971465418f22ea89bd439ac36ade88812dd920e4e61873e8abd6a9b72a03a67ef50faa00a2daf1ab745c5a15b46d03e0544a0296354227 + languageName: node + linkType: hard + +"stackback@npm:0.0.2": + version: 0.0.2 + resolution: "stackback@npm:0.0.2" + checksum: 10c0/89a1416668f950236dd5ac9f9a6b2588e1b9b62b1b6ad8dff1bfc5d1a15dbf0aafc9b52d2226d00c28dffff212da464eaeebfc6b7578b9d180cef3e3782c5983 + languageName: node + linkType: hard + +"standard-as-callback@npm:^2.1.0": + version: 2.1.0 + resolution: "standard-as-callback@npm:2.1.0" + checksum: 10c0/012677236e3d3fdc5689d29e64ea8a599331c4babe86956bf92fc5e127d53f85411c5536ee0079c52c43beb0026b5ce7aa1d834dd35dd026e82a15d1bcaead1f + languageName: node + linkType: hard + +"std-env@npm:^3.7.0": + version: 3.7.0 + resolution: "std-env@npm:3.7.0" + checksum: 10c0/60edf2d130a4feb7002974af3d5a5f3343558d1ccf8d9b9934d225c638606884db4a20d2fe6440a09605bca282af6b042ae8070a10490c0800d69e82e478f41e + languageName: node + linkType: hard + +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": + version: 4.2.3 + resolution: "string-width@npm:4.2.3" + dependencies: + emoji-regex: "npm:^8.0.0" + is-fullwidth-code-point: "npm:^3.0.0" + strip-ansi: "npm:^6.0.1" + checksum: 10c0/1e525e92e5eae0afd7454086eed9c818ee84374bb80328fc41217ae72ff5f065ef1c9d7f72da41de40c75fa8bb3dee63d92373fd492c84260a552c636392a47b + languageName: node + linkType: hard + +"string-width@npm:^5.0.1, string-width@npm:^5.1.2": + version: 5.1.2 + resolution: "string-width@npm:5.1.2" + dependencies: + eastasianwidth: "npm:^0.2.0" + emoji-regex: "npm:^9.2.2" + strip-ansi: "npm:^7.0.1" + checksum: 10c0/ab9c4264443d35b8b923cbdd513a089a60de339216d3b0ed3be3ba57d6880e1a192b70ae17225f764d7adbf5994e9bb8df253a944736c15a0240eff553c678ca + languageName: node + linkType: hard + +"string_decoder@npm:^1.1.1": + version: 1.3.0 + resolution: "string_decoder@npm:1.3.0" + dependencies: + safe-buffer: "npm:~5.2.0" + checksum: 10c0/810614ddb030e271cd591935dcd5956b2410dd079d64ff92a1844d6b7588bf992b3e1b69b0f4d34a3e06e0bd73046ac646b5264c1987b20d0601f81ef35d731d + languageName: node + linkType: hard + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": + version: 6.0.1 + resolution: "strip-ansi@npm:6.0.1" + dependencies: + ansi-regex: "npm:^5.0.1" + checksum: 10c0/1ae5f212a126fe5b167707f716942490e3933085a5ff6c008ab97ab2f272c8025d3aa218b7bd6ab25729ca20cc81cddb252102f8751e13482a5199e873680952 + languageName: node + linkType: hard + +"strip-ansi@npm:^7.0.1": + version: 7.1.0 + resolution: "strip-ansi@npm:7.1.0" + dependencies: + ansi-regex: "npm:^6.0.1" + checksum: 10c0/a198c3762e8832505328cbf9e8c8381de14a4fa50a4f9b2160138158ea88c0f5549fb50cb13c651c3088f47e63a108b34622ec18c0499b6c8c3a5ddf6b305ac4 + languageName: node + linkType: hard + +"strip-final-newline@npm:^3.0.0": + version: 3.0.0 + resolution: "strip-final-newline@npm:3.0.0" + checksum: 10c0/a771a17901427bac6293fd416db7577e2bc1c34a19d38351e9d5478c3c415f523f391003b42ed475f27e33a78233035df183525395f731d3bfb8cdcbd4da08ce + languageName: node + linkType: hard + +"strip-literal@npm:^2.1.0": + version: 2.1.0 + resolution: "strip-literal@npm:2.1.0" + dependencies: + js-tokens: "npm:^9.0.0" + checksum: 10c0/bc8b8c8346125ae3c20fcdaf12e10a498ff85baf6f69597b4ab2b5fbf2e58cfd2827f1a44f83606b852da99a5f6c8279770046ddea974c510c17c98934c9cc24 + languageName: node + linkType: hard + +"supports-color@npm:^5.5.0": + version: 5.5.0 + resolution: "supports-color@npm:5.5.0" + dependencies: + has-flag: "npm:^3.0.0" + checksum: 10c0/6ae5ff319bfbb021f8a86da8ea1f8db52fac8bd4d499492e30ec17095b58af11f0c55f8577390a749b1c4dde691b6a0315dab78f5f54c9b3d83f8fb5905c1c05 + languageName: node + linkType: hard + +"supports-color@npm:^7.1.0": + version: 7.2.0 + resolution: "supports-color@npm:7.2.0" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10c0/afb4c88521b8b136b5f5f95160c98dee7243dc79d5432db7efc27efb219385bbc7d9427398e43dd6cc730a0f87d5085ce1652af7efbe391327bc0a7d0f7fc124 + languageName: node + linkType: hard + +"supports-color@npm:^8.1.1": + version: 8.1.1 + resolution: "supports-color@npm:8.1.1" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10c0/ea1d3c275dd604c974670f63943ed9bd83623edc102430c05adb8efc56ba492746b6e95386e7831b872ec3807fd89dd8eb43f735195f37b5ec343e4234cc7e89 + languageName: node + linkType: hard + +"supports-preserve-symlinks-flag@npm:^1.0.0": + version: 1.0.0 + resolution: "supports-preserve-symlinks-flag@npm:1.0.0" + checksum: 10c0/6c4032340701a9950865f7ae8ef38578d8d7053f5e10518076e6554a9381fa91bd9c6850193695c141f32b21f979c985db07265a758867bac95de05f7d8aeb39 + languageName: node + linkType: hard + +"tar@npm:^6.1.11, tar@npm:^6.1.2": + version: 6.2.1 + resolution: "tar@npm:6.2.1" + dependencies: + chownr: "npm:^2.0.0" + fs-minipass: "npm:^2.0.0" + minipass: "npm:^5.0.0" + minizlib: "npm:^2.1.1" + mkdirp: "npm:^1.0.3" + yallist: "npm:^4.0.0" + checksum: 10c0/a5eca3eb50bc11552d453488344e6507156b9193efd7635e98e867fab275d527af53d8866e2370cd09dfe74378a18111622ace35af6a608e5223a7d27fe99537 + languageName: node + linkType: hard + +"test-exclude@npm:^7.0.1": + version: 7.0.1 + resolution: "test-exclude@npm:7.0.1" + dependencies: + "@istanbuljs/schema": "npm:^0.1.2" + glob: "npm:^10.4.1" + minimatch: "npm:^9.0.4" + checksum: 10c0/6d67b9af4336a2e12b26a68c83308c7863534c65f27ed4ff7068a56f5a58f7ac703e8fc80f698a19bb154fd8f705cdf7ec347d9512b2c522c737269507e7b263 + languageName: node + linkType: hard + +"tinybench@npm:^2.8.0": + version: 2.8.0 + resolution: "tinybench@npm:2.8.0" + checksum: 10c0/5a9a642351fa3e4955e0cbf38f5674be5f3ba6730fd872fd23a5c953ad6c914234d5aba6ea41ef88820180a81829ceece5bd8d3967c490c5171bca1141c2f24d + languageName: node + linkType: hard + +"tinypool@npm:^1.0.0": + version: 1.0.0 + resolution: "tinypool@npm:1.0.0" + checksum: 10c0/71b20b9c54366393831c286a0772380c20f8cad9546d724c484edb47aea3228f274c58e98cf51d28c40869b39f5273209ef3ea94a9d2a23f8b292f4731cd3e4e + languageName: node + linkType: hard + +"tinyrainbow@npm:^1.2.0": + version: 1.2.0 + resolution: "tinyrainbow@npm:1.2.0" + checksum: 10c0/7f78a4b997e5ba0f5ecb75e7ed786f30bab9063716e7dff24dd84013fb338802e43d176cb21ed12480561f5649a82184cf31efb296601a29d38145b1cdb4c192 + languageName: node + linkType: hard + +"tinyspy@npm:^3.0.0": + version: 3.0.0 + resolution: "tinyspy@npm:3.0.0" + checksum: 10c0/eb0dec264aa5370efd3d29743825eb115ed7f1ef8a72a431e9a75d5c9e7d67e99d04b0d61d86b8cd70c79ec27863f241ad0317bc453f78762e0cbd76d2c332d0 + languageName: node + linkType: hard + +"tmp@npm:^0.0.33": + version: 0.0.33 + resolution: "tmp@npm:0.0.33" + dependencies: + os-tmpdir: "npm:~1.0.2" + checksum: 10c0/69863947b8c29cabad43fe0ce65cec5bb4b481d15d4b4b21e036b060b3edbf3bc7a5541de1bacb437bb3f7c4538f669752627fdf9b4aaf034cebd172ba373408 + languageName: node + linkType: hard + +"to-fast-properties@npm:^2.0.0": + version: 2.0.0 + resolution: "to-fast-properties@npm:2.0.0" + checksum: 10c0/b214d21dbfb4bce3452b6244b336806ffea9c05297148d32ebb428d5c43ce7545bdfc65a1ceb58c9ef4376a65c0cb2854d645f33961658b3e3b4f84910ddcdd7 + languageName: node + linkType: hard + +"to-regex-range@npm:^5.0.1": + version: 5.0.1 + resolution: "to-regex-range@npm:5.0.1" + dependencies: + is-number: "npm:^7.0.0" + checksum: 10c0/487988b0a19c654ff3e1961b87f471702e708fa8a8dd02a298ef16da7206692e8552a0250e8b3e8759270f62e9d8314616f6da274734d3b558b1fc7b7724e892 + languageName: node + linkType: hard + +"touch@npm:^3.1.0": + version: 3.1.1 + resolution: "touch@npm:3.1.1" + bin: + nodetouch: bin/nodetouch.js + checksum: 10c0/d2e4d269a42c846a22a29065b9af0b263de58effc85a1764bb7a2e8fc4b47700e9e2fcbd7eb1f5bffbb7c73d860f93600cef282b93ddac8f0b62321cb498b36e + languageName: node + linkType: hard + +"tr46@npm:~0.0.3": + version: 0.0.3 + resolution: "tr46@npm:0.0.3" + checksum: 10c0/047cb209a6b60c742f05c9d3ace8fa510bff609995c129a37ace03476a9b12db4dbf975e74600830ef0796e18882b2381fb5fb1f6b4f96b832c374de3ab91a11 + languageName: node + linkType: hard + +"tree-kill@npm:^1.2.2": + version: 1.2.2 + resolution: "tree-kill@npm:1.2.2" + bin: + tree-kill: cli.js + checksum: 10c0/7b1b7c7f17608a8f8d20a162e7957ac1ef6cd1636db1aba92f4e072dc31818c2ff0efac1e3d91064ede67ed5dc57c565420531a8134090a12ac10cf792ab14d2 + languageName: node + linkType: hard + +"ts-mixer@npm:^6.0.4": + version: 6.0.4 + resolution: "ts-mixer@npm:6.0.4" + checksum: 10c0/4c442fc99cdffd4a3f0ce55c624fb703f4ded5cab6912f97705489565c4a74d3e4213f10c33499ec5150900a628d38537a9a6a9e35b5045b65129a84b4db21ae + languageName: node + linkType: hard + +"tslib@npm:2.6.2, tslib@npm:^2.6.2": + version: 2.6.2 + resolution: "tslib@npm:2.6.2" + checksum: 10c0/e03a8a4271152c8b26604ed45535954c0a45296e32445b4b87f8a5abdb2421f40b59b4ca437c4346af0f28179780d604094eb64546bee2019d903d01c6c19bdb + languageName: node + linkType: hard + +"tslib@npm:^2.1.0": + version: 2.5.0 + resolution: "tslib@npm:2.5.0" + checksum: 10c0/e32fc99cc730dd514e53c44e668d76016e738f0bcc726aad5dbd2d335cf19b87a95a9b1e4f0a9993e370f1d702b5e471cdd4acabcac428a3099d496b9af2021e + languageName: node + linkType: hard + +"tsx@npm:^4.16.2": + version: 4.16.2 + resolution: "tsx@npm:4.16.2" + dependencies: + esbuild: "npm:~0.21.5" + fsevents: "npm:~2.3.3" + get-tsconfig: "npm:^4.7.5" + dependenciesMeta: + fsevents: + optional: true + bin: + tsx: dist/cli.mjs + checksum: 10c0/9df52264f88be00ca473e7d7eda43bb038cc09028514996b864db78645e9cd297c71485f0fdd4985464d6dc46424f8bef9f8c4bd56692c4fcf4d71621ae21763 + languageName: node + linkType: hard + +"type-fest@npm:^4.21.0": + version: 4.23.0 + resolution: "type-fest@npm:4.23.0" + checksum: 10c0/c42bb14e99329ab37983d1f188e307bf0cc705a23807d9b2268d8fb2ae781d610ac6e2058dde8f9ea2b1b8ddc77ceb578d157fa81f69f8f70aef1d42fb002996 + languageName: node + linkType: hard + +"typescript@npm:^5.2.2, typescript@npm:^5.5.3": + version: 5.5.3 + resolution: "typescript@npm:5.5.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/f52c71ccbc7080b034b9d3b72051d563601a4815bf3e39ded188e6ce60813f75dbedf11ad15dd4d32a12996a9ed8c7155b46c93a9b9c9bad1049766fe614bbdd + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A^5.2.2#optional!builtin, typescript@patch:typescript@npm%3A^5.5.3#optional!builtin": + version: 5.5.3 + resolution: "typescript@patch:typescript@npm%3A5.5.3#optional!builtin::version=5.5.3&hash=379a07" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/911c7811d61f57f07df79c4a35f56a0f426a65426a020e5fcd792f66559f399017205f5f10255329ab5a3d8c2d1f1d19530aeceffda70758a521fae1d469432e + languageName: node + linkType: hard + +"undefsafe@npm:^2.0.5": + version: 2.0.5 + resolution: "undefsafe@npm:2.0.5" + checksum: 10c0/96c0466a5fbf395917974a921d5d4eee67bca4b30d3a31ce7e621e0228c479cf893e783a109af6e14329b52fe2f0cb4108665fad2b87b0018c0df6ac771261d5 + languageName: node + linkType: hard + +"undici-types@npm:~5.26.4": + version: 5.26.5 + resolution: "undici-types@npm:5.26.5" + checksum: 10c0/bb673d7876c2d411b6eb6c560e0c571eef4a01c1c19925175d16e3a30c4c428181fb8d7ae802a261f283e4166a0ac435e2f505743aa9e45d893f9a3df017b501 + languageName: node + linkType: hard + +"undici@npm:6.13.0": + version: 6.13.0 + resolution: "undici@npm:6.13.0" + checksum: 10c0/b1b0456e7d4e87fd4f71a2a0716d8ea826f024fbcf16b7c6185194ca138cb822c20db509ed18c837fffcd9ffc3d74004ad6f950ebc895ff81ebb5ca6bf001cb2 + languageName: node + linkType: hard + +"unique-filename@npm:^3.0.0": + version: 3.0.0 + resolution: "unique-filename@npm:3.0.0" + dependencies: + unique-slug: "npm:^4.0.0" + checksum: 10c0/6363e40b2fa758eb5ec5e21b3c7fb83e5da8dcfbd866cc0c199d5534c42f03b9ea9ab069769cc388e1d7ab93b4eeef28ef506ab5f18d910ef29617715101884f + languageName: node + linkType: hard + +"unique-slug@npm:^4.0.0": + version: 4.0.0 + resolution: "unique-slug@npm:4.0.0" + dependencies: + imurmurhash: "npm:^0.1.4" + checksum: 10c0/cb811d9d54eb5821b81b18205750be84cb015c20a4a44280794e915f5a0a70223ce39066781a354e872df3572e8155c228f43ff0cce94c7cbf4da2cc7cbdd635 + languageName: node + linkType: hard + +"universalify@npm:^2.0.0": + version: 2.0.1 + resolution: "universalify@npm:2.0.1" + checksum: 10c0/73e8ee3809041ca8b818efb141801a1004e3fc0002727f1531f4de613ea281b494a40909596dae4a042a4fb6cd385af5d4db2e137b1362e0e91384b828effd3a + languageName: node + linkType: hard + +"util-deprecate@npm:^1.0.1": + version: 1.0.2 + resolution: "util-deprecate@npm:1.0.2" + checksum: 10c0/41a5bdd214df2f6c3ecf8622745e4a366c4adced864bc3c833739791aeeeb1838119af7daed4ba36428114b5c67dcda034a79c882e97e43c03e66a4dd7389942 + languageName: node + linkType: hard + +"uuid@npm:8.3.2": + version: 8.3.2 + resolution: "uuid@npm:8.3.2" + bin: + uuid: dist/bin/uuid + checksum: 10c0/bcbb807a917d374a49f475fae2e87fdca7da5e5530820ef53f65ba1d12131bd81a92ecf259cc7ce317cbe0f289e7d79fdfebcef9bfa3087c8c8a2fa304c9be54 + languageName: node + linkType: hard + +"vite-node@npm:2.0.3": + version: 2.0.3 + resolution: "vite-node@npm:2.0.3" + dependencies: + cac: "npm:^6.7.14" + debug: "npm:^4.3.5" + pathe: "npm:^1.1.2" + tinyrainbow: "npm:^1.2.0" + vite: "npm:^5.0.0" + bin: + vite-node: vite-node.mjs + checksum: 10c0/a1bcc110aeb49e79a50ae0df41ca692d39e0d992702f7c5b095c969f622eb72636543bed79efb7131fdedaa4c44a6c9c19daf6fca909240acc1f27f79b978c11 + languageName: node + linkType: hard + +"vite@npm:^5.0.0": + version: 5.2.8 + resolution: "vite@npm:5.2.8" + dependencies: + esbuild: "npm:^0.20.1" + fsevents: "npm:~2.3.3" + postcss: "npm:^8.4.38" + rollup: "npm:^4.13.0" + peerDependencies: + "@types/node": ^18.0.0 || >=20.0.0 + less: "*" + lightningcss: ^1.21.0 + sass: "*" + stylus: "*" + sugarss: "*" + terser: ^5.4.0 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/b5717bb00c2570c08ff6d8ed917655e79184efcafa9dd62d52eea19c5d6dfc5a708ec3de9ebc670a7165fc5d401c2bdf1563bb39e2748d8e51e1593d286a9a13 + languageName: node + linkType: hard + +"vitest@npm:^2.0.3": + version: 2.0.3 + resolution: "vitest@npm:2.0.3" + dependencies: + "@ampproject/remapping": "npm:^2.3.0" + "@vitest/expect": "npm:2.0.3" + "@vitest/pretty-format": "npm:^2.0.3" + "@vitest/runner": "npm:2.0.3" + "@vitest/snapshot": "npm:2.0.3" + "@vitest/spy": "npm:2.0.3" + "@vitest/utils": "npm:2.0.3" + chai: "npm:^5.1.1" + debug: "npm:^4.3.5" + execa: "npm:^8.0.1" + magic-string: "npm:^0.30.10" + pathe: "npm:^1.1.2" + std-env: "npm:^3.7.0" + tinybench: "npm:^2.8.0" + tinypool: "npm:^1.0.0" + tinyrainbow: "npm:^1.2.0" + vite: "npm:^5.0.0" + vite-node: "npm:2.0.3" + why-is-node-running: "npm:^2.2.2" + peerDependencies: + "@edge-runtime/vm": "*" + "@types/node": ^18.0.0 || >=20.0.0 + "@vitest/browser": 2.0.3 + "@vitest/ui": 2.0.3 + happy-dom: "*" + jsdom: "*" + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@types/node": + optional: true + "@vitest/browser": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + bin: + vitest: vitest.mjs + checksum: 10c0/1801ec31eb144063d14a03d054ff573869732dcaf69abd4fefdabe011d183599a7493e49d8e180b29808675309814421c4a12271fb140c708e7c9f68c4a37a3c + languageName: node + linkType: hard + +"wcwidth@npm:^1.0.1": + version: 1.0.1 + resolution: "wcwidth@npm:1.0.1" + dependencies: + defaults: "npm:^1.0.3" + checksum: 10c0/5b61ca583a95e2dd85d7078400190efd452e05751a64accb8c06ce4db65d7e0b0cde9917d705e826a2e05cc2548f61efde115ffa374c3e436d04be45c889e5b4 + languageName: node + linkType: hard + +"webidl-conversions@npm:^3.0.0": + version: 3.0.1 + resolution: "webidl-conversions@npm:3.0.1" + checksum: 10c0/5612d5f3e54760a797052eb4927f0ddc01383550f542ccd33d5238cfd65aeed392a45ad38364970d0a0f4fea32e1f4d231b3d8dac4a3bdd385e5cf802ae097db + languageName: node + linkType: hard + +"whatwg-url@npm:^5.0.0": + version: 5.0.0 + resolution: "whatwg-url@npm:5.0.0" + dependencies: + tr46: "npm:~0.0.3" + webidl-conversions: "npm:^3.0.0" + checksum: 10c0/1588bed84d10b72d5eec1d0faa0722ba1962f1821e7539c535558fb5398d223b0c50d8acab950b8c488b4ba69043fd833cc2697056b167d8ad46fac3995a55d5 + languageName: node + linkType: hard + +"which@npm:^2.0.1": + version: 2.0.2 + resolution: "which@npm:2.0.2" + dependencies: + isexe: "npm:^2.0.0" + bin: + node-which: ./bin/node-which + checksum: 10c0/66522872a768b60c2a65a57e8ad184e5372f5b6a9ca6d5f033d4b0dc98aff63995655a7503b9c0a2598936f532120e81dd8cc155e2e92ed662a2b9377cc4374f + languageName: node + linkType: hard + +"which@npm:^4.0.0": + version: 4.0.0 + resolution: "which@npm:4.0.0" + dependencies: + isexe: "npm:^3.1.1" + bin: + node-which: bin/which.js + checksum: 10c0/449fa5c44ed120ccecfe18c433296a4978a7583bf2391c50abce13f76878d2476defde04d0f79db8165bdf432853c1f8389d0485ca6e8ebce3bbcded513d5e6a + languageName: node + linkType: hard + +"why-is-node-running@npm:^2.2.2": + version: 2.2.2 + resolution: "why-is-node-running@npm:2.2.2" + dependencies: + siginfo: "npm:^2.0.0" + stackback: "npm:0.0.2" + bin: + why-is-node-running: cli.js + checksum: 10c0/805d57eb5d33f0fb4e36bae5dceda7fd8c6932c2aeb705e30003970488f1a2bc70029ee64be1a0e1531e2268b11e65606e88e5b71d667ea745e6dc48fc9014bd + languageName: node + linkType: hard + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": + version: 7.0.0 + resolution: "wrap-ansi@npm:7.0.0" + dependencies: + ansi-styles: "npm:^4.0.0" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + checksum: 10c0/d15fc12c11e4cbc4044a552129ebc75ee3f57aa9c1958373a4db0292d72282f54373b536103987a4a7594db1ef6a4f10acf92978f79b98c49306a4b58c77d4da + languageName: node + linkType: hard + +"wrap-ansi@npm:^8.1.0": + version: 8.1.0 + resolution: "wrap-ansi@npm:8.1.0" + dependencies: + ansi-styles: "npm:^6.1.0" + string-width: "npm:^5.0.1" + strip-ansi: "npm:^7.0.1" + checksum: 10c0/138ff58a41d2f877eae87e3282c0630fc2789012fc1af4d6bd626eeb9a2f9a65ca92005e6e69a75c7b85a68479fe7443c7dbe1eb8fbaa681a4491364b7c55c60 + languageName: node + linkType: hard + +"ws@npm:^8.16.0": + version: 8.18.0 + resolution: "ws@npm:8.18.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10c0/25eb33aff17edcb90721ed6b0eb250976328533ad3cd1a28a274bd263682e7296a6591ff1436d6cbc50fa67463158b062f9d1122013b361cec99a05f84680e06 + languageName: node + linkType: hard + +"xtend@npm:^4.0.0": + version: 4.0.2 + resolution: "xtend@npm:4.0.2" + checksum: 10c0/366ae4783eec6100f8a02dff02ac907bf29f9a00b82ac0264b4d8b832ead18306797e283cf19de776538babfdcb2101375ec5646b59f08c52128ac4ab812ed0e + languageName: node + linkType: hard + +"y18n@npm:^5.0.5": + version: 5.0.8 + resolution: "y18n@npm:5.0.8" + checksum: 10c0/4df2842c36e468590c3691c894bc9cdbac41f520566e76e24f59401ba7d8b4811eb1e34524d57e54bc6d864bcb66baab7ffd9ca42bf1eda596618f9162b91249 + languageName: node + linkType: hard + +"yallist@npm:^4.0.0": + version: 4.0.0 + resolution: "yallist@npm:4.0.0" + checksum: 10c0/2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a + languageName: node + linkType: hard + +"yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: 10c0/f84b5e48169479d2f402239c59f084cfd1c3acc197a05c59b98bab067452e6b3ea46d4dd8ba2985ba7b3d32a343d77df0debd6b343e5dae3da2aab2cdf5886b2 + languageName: node + linkType: hard + +"yargs@npm:^17.7.2": + version: 17.7.2 + resolution: "yargs@npm:17.7.2" + dependencies: + cliui: "npm:^8.0.1" + escalade: "npm:^3.1.1" + get-caller-file: "npm:^2.0.5" + require-directory: "npm:^2.1.1" + string-width: "npm:^4.2.3" + y18n: "npm:^5.0.5" + yargs-parser: "npm:^21.1.1" + checksum: 10c0/ccd7e723e61ad5965fffbb791366db689572b80cca80e0f96aad968dfff4156cd7cd1ad18607afe1046d8241e6fb2d6c08bf7fa7bfb5eaec818735d8feac8f05 + languageName: node + linkType: hard + +"zlib-sync@npm:^0.1.9": + version: 0.1.9 + resolution: "zlib-sync@npm:0.1.9" + dependencies: + nan: "npm:^2.18.0" + node-gyp: "npm:latest" + checksum: 10c0/1c05fdc5140f4c9afb931bfca01fc9f26ddfe242eb5c8c06ffc93b53d5103f0a99e8b8048697446001eb4a92c12febd270bcb4cda3fa3de9bf822e6ba0664902 + languageName: node + linkType: hard + +"zod@npm:3.23.8, zod@npm:^3.23.8": + version: 3.23.8 + resolution: "zod@npm:3.23.8" + checksum: 10c0/8f14c87d6b1b53c944c25ce7a28616896319d95bc46a9660fe441adc0ed0a81253b02b5abdaeffedbeb23bdd25a0bf1c29d2c12dd919aef6447652dd295e3e69 + languageName: node + linkType: hard