diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 21507b3d..90e179a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ on: jobs: build: - runs-on: windows-latest + runs-on: ubuntu-latest strategy: matrix: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index be42b78e..afe6d577 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,89 +8,163 @@ on: types: [published] jobs: - publish: + build: runs-on: ubuntu-latest + outputs: + mcaddon: ${{ steps.artifact.outputs.mcaddon }} steps: - uses: actions/checkout@v4 - - name: Package mcaddon + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install Regolith run: | - VERSION=${{ github.event.release.tag_name }} - OUTPUT="Canopy-${VERSION}.mcaddon" + DOWNLOAD_URL=$(curl -sSL https://api.github.com/repos/Bedrock-OSS/regolith/releases/latest \ + | jq -r '.assets[] | select(.name | test("Linux_x86_64\\.tar\\.gz")) | .browser_download_url') + curl -sSLf "$DOWNLOAD_URL" | tar -xz -C /usr/local/bin regolith + + - name: Run release profile + run: regolith run release + + - name: Get release artifact path + id: artifact + run: echo "mcaddon=$(ls build/*.mcaddon | head -1)" >> "$GITHUB_OUTPUT" + + - name: Upload build artifact + uses: actions/upload-artifact@v4 + with: + name: mcaddon + path: ${{ steps.artifact.outputs.mcaddon }} - mkdir build - cp -r "Canopy [BP]" "build/Canopy[BP]" - cp -r "Canopy [RP]" "build/Canopy[RP]" - cp LICENSE "build/Canopy[BP]" - cp LICENSE "build/Canopy[RP]" + upload-github: + needs: build + runs-on: ubuntu-latest - cd build - zip -r "$OUTPUT" "Canopy[BP]" "Canopy[RP]" - mv "$OUTPUT" .. - cd .. + steps: + - name: Download build artifact + uses: actions/download-artifact@v4 + with: + name: mcaddon + path: build - name: Upload asset to GitHub Release uses: softprops/action-gh-release@e798e6a1ede8d07b74ac4cdac6bdfa4cc1653907 with: - files: Canopy-${{ github.event.release.tag_name }}.mcaddon + files: ${{ needs.build.outputs.mcaddon }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + upload-curseforge: + needs: build + runs-on: ubuntu-latest + env: + CF_PROJECT_ID: 1062078 + + steps: + - uses: actions/checkout@v4 + + - name: Download build artifact + uses: actions/download-artifact@v4 + with: + name: mcaddon + path: build + - name: Upload to CurseForge env: RELEASE_BODY: ${{ github.event.release.body }} run: | - VERSION=${{ github.event.release.tag_name }} - - # Read the game version from manifest.json - MIN_ENGINE_VERSION=$(jq -r '.header.min_engine_version | @csv' "Canopy [BP]/manifest.json") - IFS=',' read -r LEADING MAJOR MINOR <<< "$MIN_ENGINE_VERSION" - ENGINE_PREFIX="${MAJOR}.${MINOR}" - - # Fetch all Bedrock versions from CurseForge API - CF_VERSIONS_JSON=$(curl -s -H "X-Api-Token: ${{ secrets.CF_API_TOKEN }}" \ - "https://minecraft-bedrock.curseforge.com/api/game/versions") - - # Extract all game version IDs whose name matches the game version from the manifest.json - GAME_VERSION_IDS=$(echo "$CF_VERSIONS_JSON" | jq -c --arg prefix "$ENGINE_PREFIX" ' - [ .[] | select(.name | startswith($prefix)) | .id ] - ') - if [ -z "$GAME_VERSION_IDS" ]; then - echo "No matching CurseForge versions found for engine prefix $ENGINE_PREFIX" - exit 1 - fi - echo "Found CurseForge version IDs for game version $ENGINE_PREFIX: $GAME_VERSION_IDS" - - ADDON_FILENAME=Canopy-$VERSION.mcaddon - - # Build metadata as JSON so the changelog is uploaded with preserved Markdown. - printf "%s" "$RELEASE_BODY" > curseforge-changelog.md - jq -n \ - --rawfile changelog curseforge-changelog.md \ - --arg displayName "$ADDON_FILENAME" \ - --argjson gameVersions "$GAME_VERSION_IDS" \ - '{ - changelog: $changelog, - changelogType: "markdown", - displayName: $displayName, - releaseType: "release", - gameVersions: $gameVersions - }' > curseforge-metadata.json - - # Upload the addon - RESPONSE=$(curl -s --fail-with-body -X POST \ - -H "X-Api-Token: ${{ secrets.CF_API_TOKEN }}" \ - -F "metadata=@curseforge-metadata.json;type=application/json" \ - -F "file=@$ADDON_FILENAME" \ - https://minecraft-bedrock.curseforge.com/api/projects/1062078/upload-file - ) - - UPLOADED_ID=$(echo "$RESPONSE" | jq -r '.id // empty') - if [ -n "$UPLOADED_ID" ]; then - echo "Successfully uploaded release. File ID: $UPLOADED_ID" - else - echo "Upload failed. Response:" - echo "$RESPONSE" - exit 1 - fi + set -euo pipefail + MCADDON="${{ needs.build.outputs.mcaddon }}" + PACK_FILENAME=$(basename "$MCADDON") + BP_SOURCE=$(jq -r '.packs.behaviorPack' config.json | sed 's|^\./||') + + # Read the game version from manifest.json + MIN_ENGINE_VERSION=$(sed -E 's,//.*$,,' "$BP_SOURCE/manifest.json" | jq -r '.header.min_engine_version | @csv') + IFS=',' read -r LEADING MAJOR MINOR <<< "$MIN_ENGINE_VERSION" + ENGINE_PREFIX="${MAJOR}.${MINOR}" + + # Fetch all Bedrock versions from CurseForge API + CF_VERSIONS_HTTP=$(curl -sS -L \ + -o curseforge-versions.json \ + -w "%{http_code}" \ + -H "Accept: application/json" \ + -H "X-Api-Token: ${{ secrets.CF_API_TOKEN }}" \ + "https://minecraft-bedrock.curseforge.com/api/game/versions") + if [ "$CF_VERSIONS_HTTP" -lt 200 ] || [ "$CF_VERSIONS_HTTP" -ge 300 ]; then + echo "CurseForge versions API returned HTTP $CF_VERSIONS_HTTP" + cat curseforge-versions.json + exit 1 + fi + + # Guard against non-JSON responses (HTML/proxy errors), which break jq parsing later. + if ! jq -e . curseforge-versions.json >/dev/null; then + echo "CurseForge versions API response was not valid JSON" + cat curseforge-versions.json + exit 1 + fi + + # Extract all game version IDs whose name matches the game version from the manifest.json + GAME_VERSION_IDS=$(jq -c --arg prefix "$ENGINE_PREFIX" ' + if type == "array" then + [ .[] | select((.name // "") | startswith($prefix)) | .id ] + elif (type == "object" and has("data") and (.data | type == "array")) then + [ .data[] | select((.name // "") | startswith($prefix)) | .id ] + else + [] + end + ' curseforge-versions.json) + if [ "$GAME_VERSION_IDS" = "[]" ]; then + echo "No matching CurseForge versions found for engine prefix $ENGINE_PREFIX" + exit 1 + fi + echo "Found CurseForge version IDs for game version $ENGINE_PREFIX: $GAME_VERSION_IDS" + + # Build metadata as JSON so the changelog is uploaded with preserved Markdown. + printf "%s" "$RELEASE_BODY" > curseforge-changelog.md + jq -n \ + --rawfile changelog curseforge-changelog.md \ + --arg displayName "$PACK_FILENAME" \ + --argjson gameVersions "$GAME_VERSION_IDS" \ + '{ + changelog: $changelog, + changelogType: "markdown", + displayName: $displayName, + releaseType: "release", + gameVersions: $gameVersions + }' > curseforge-metadata.json + METADATA_JSON=$(jq -c . curseforge-metadata.json) + + # Upload the mcaddon + UPLOAD_HTTP=$(curl -sS -X POST \ + -o curseforge-upload-response.json \ + -w "%{http_code}" \ + -H "Accept: application/json" \ + -H "X-Api-Token: ${{ secrets.CF_API_TOKEN }}" \ + --form-string "metadata=$METADATA_JSON" \ + -F "file=@$MCADDON" \ + https://minecraft-bedrock.curseforge.com/api/projects/$CF_PROJECT_ID/upload-file + ) + if [ "$UPLOAD_HTTP" -lt 200 ] || [ "$UPLOAD_HTTP" -ge 300 ]; then + echo "CurseForge upload returned HTTP $UPLOAD_HTTP" + cat curseforge-upload-response.json + exit 1 + fi + + if ! jq -e . curseforge-upload-response.json >/dev/null; then + echo "CurseForge upload response was not valid JSON" + cat curseforge-upload-response.json + exit 1 + fi + + UPLOADED_ID=$(jq -r '.id // empty' curseforge-upload-response.json) + if [ -n "$UPLOADED_ID" ]; then + echo "Successfully uploaded release. File ID: $UPLOADED_ID" + else + echo "Upload failed. Response:" + cat curseforge-upload-response.json + exit 1 + fi diff --git a/.github/workflows/wiki.yml b/.github/workflows/wiki.yml new file mode 100644 index 00000000..40aef461 --- /dev/null +++ b/.github/workflows/wiki.yml @@ -0,0 +1,51 @@ +name: Update Wiki + +on: + push: + branches: + - main + +jobs: + update-wiki: + runs-on: ubuntu-latest + + steps: + - name: Checkout Canopy repo + uses: actions/checkout@v4 + + - name: Checkout Wiki repo (master branch) + uses: actions/checkout@v4 + with: + repository: ${{ github.repository }}.wiki + path: wiki + ref: master + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Use Commands.md template from template branch + working-directory: wiki + run: git fetch origin template && git show origin/template:Commands.md > Commands.md + + - name: Use Node.js 20.14.0 + uses: actions/setup-node@v4 + with: + node-version: 20.14.0 + cache: 'npm' + + - name: Install dependencies + run: npm install + + - name: Generate wiki content + run: WIKI_PATH=./wiki npm run generate-wiki + + - name: Commit and push wiki changes + working-directory: wiki + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add -A + if git diff --staged --quiet; then + echo "No wiki changes to commit." + else + git commit -m "docs: auto-update wiki from source [skip ci]" + git push + fi diff --git a/.gitignore b/.gitignore index 18494b53..17328fc7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,7 @@ node_modules/ coverage/ /build -/.regolith \ No newline at end of file +/.regolith +/docs/superpowers/ +.claude/ +.env \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index a7f82d27..527b441c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,8 +7,8 @@ "name": "Debug with Minecraft", "mode": "listen", "targetModuleUuid": "3d753132-e3c9-4305-a995-eae30b486093", - "localRoot": "${workspaceFolder}/Canopy [BP]/scripts", + "localRoot": "${workspaceFolder}/Canopy[BP]/scripts", "port": 19144 } ] -} \ No newline at end of file +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index a3649d48..a4a4eb2f 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -2,9 +2,9 @@ "version": "2.0.0", "tasks": [ { - "label": "regolith: run", + "label": "regolith: run default", "type": "shell", - "command": "regolith run", + "command": "regolith run default", "problemMatcher": [], "group": { "kind": "build", @@ -16,7 +16,42 @@ } }, { - "label": "regolith: watch", + "label": "regolith: run preview", + "type": "shell", + "command": "regolith run preview", + "problemMatcher": [], + "group": "build", + "presentation": { + "echo": true, + "close": true + } + }, + { + "label": "regolith: run release", + "type": "shell", + "command": "regolith run release", + "problemMatcher": [], + "group": "build", + "presentation": { + "echo": true, + "reveal": "always", + "close": false + } + }, + { + "label": "regolith: bump version", + "type": "shell", + "command": "regolith run bump-version", + "problemMatcher": [], + "presentation": { + "echo": true, + "reveal": "always", + "panel": "new", + "close": false + } + }, + { + "label": "regolith: watch default", "type": "shell", "command": "regolith watch default", "isBackground": true, @@ -66,4 +101,4 @@ } } ] -} \ No newline at end of file +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..b46ae774 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,62 @@ +# Contributing to Canopy + +Thanks for your interest in contributing! There are several ways to help: + +- [Contributing to Canopy](#contributing-to-canopy) + - [Reporting Bugs](#reporting-bugs) + - [Contributing Code](#contributing-code) + - [Setup](#setup) + - [Code Style](#code-style) + - [Tests](#tests) + - [Submitting a PR](#submitting-a-pr) + - [Contributing Translations](#contributing-translations) + +## Reporting Bugs + +Found a bug? Open an [issue](https://github.com/ForestOfLight/Canopy/issues) with clear steps to reproduce it. + +## Contributing Code + +### Setup + +1. [Fork the repository](https://github.com/ForestOfLight/Canopy/fork) and clone your fork locally. +2. Install dependencies: + ```bash + npm install + ``` + +### Code Style + +Canopy uses ESLint to enforce consistent style. Before submitting, run: + +```bash +npm run lint +``` + +Fix all errors before opening a PR. Key style rules: + +- **Object-oriented** — follow the existing class-per-file pattern; extend base classes where applicable (see `Canopy[BP]/scripts/src/` for examples) +- **`const` by default** — use `let` only when reassignment is necessary; never use `var` +- **`camelCase` naming** — for variables, functions, and class members +- **One class per file** — keep files focused and single-purpose +- **No explanatory comments in code** — describe your changes in the PR description, not in the source + +### Tests + +All code contributions must include tests. Tests live in `__tests__/` and mirror the structure of `Canopy[BP]/scripts/`. Run the full suite with: + +```bash +npm test +``` + +All tests must pass before submitting. + +### Submitting a PR + +- Target the **`dev` branch** +- Keep PRs focused — one concern per PR +- Describe what your changes do and why in the PR description + +## Contributing Translations + +See [docs/TRANSLATING.md](docs/TRANSLATING.md) for the full translation guide. diff --git a/Canopy [BP]/scripts/constants.js b/Canopy [BP]/scripts/constants.js deleted file mode 100644 index 5c87a250..00000000 --- a/Canopy [BP]/scripts/constants.js +++ /dev/null @@ -1,4 +0,0 @@ -const PACK_VERSION = '1.5.5'; -const MC_VERSION = '1.26.10.4'; - -export { PACK_VERSION, MC_VERSION }; \ No newline at end of file diff --git a/Canopy [BP]/entities/ender_pearl.json b/Canopy[BP]/entities/ender_pearl.json similarity index 100% rename from Canopy [BP]/entities/ender_pearl.json rename to Canopy[BP]/entities/ender_pearl.json diff --git a/Canopy [BP]/entities/minecart.json b/Canopy[BP]/entities/minecart.json similarity index 100% rename from Canopy [BP]/entities/minecart.json rename to Canopy[BP]/entities/minecart.json diff --git a/Canopy [BP]/entities/rideable.json b/Canopy[BP]/entities/rideable.json similarity index 100% rename from Canopy [BP]/entities/rideable.json rename to Canopy[BP]/entities/rideable.json diff --git a/Canopy [BP]/entities/tnt.json b/Canopy[BP]/entities/tnt.json similarity index 100% rename from Canopy [BP]/entities/tnt.json rename to Canopy[BP]/entities/tnt.json diff --git a/Canopy [BP]/manifest.json b/Canopy[BP]/manifest.json similarity index 70% rename from Canopy [BP]/manifest.json rename to Canopy[BP]/manifest.json index 41e17a0e..19802a31 100644 --- a/Canopy [BP]/manifest.json +++ b/Canopy[BP]/manifest.json @@ -1,50 +1,72 @@ -{ - "format_version": 2, - "header": { - "name": "Canopy [BP] v1.5.5", - "description": "Technical informatics & features addon by §aForestOfLight§r.", - "uuid": "7f6b23df-a583-476b-b0e4-87457e65f7c0", - "version": [1, 5, 5], - "min_engine_version": [1, 26, 10] - }, - "modules": [ - { - "description": "Behavior Pack Module", - "type": "data", - "uuid": "47787f93-ec3c-4b0c-95fb-55a70e029041", - "version": [1, 0, 0] - }, - { - "description": "Gametest Module", - "type": "script", - "language": "javascript", - "entry": "scripts/main.js", - "uuid": "3d753132-e3c9-4305-a995-eae30b486093", - "version": [1, 0, 0] - } - ], - "dependencies": [ - { - "module_name": "@minecraft/server", - "version": "2.7.0-beta" - }, - { - "module_name": "@minecraft/server-ui", - "version": "2.1.0-beta" - }, - { - "module_name": "@minecraft/debug-utilities", - "version": "1.0.0-beta" - }, - { - "uuid": "bcf34368-ed0c-4cf7-938e-582cccf9950d", - "version": [1, 5, 5] - } - ], - "metadata": { - "authors": [ "ForestOfLight" ], - "license": "MIT", - "url": "https://github.com/ForestOfLight/Canopy", - "product_type": "addon" - } -} +{ + "format_version": 2, + "header": { + "name": "Canopy [BP] v1.5.6", + "description": "Technical informatics & features addon by §aForestOfLight§r.", + "uuid": "7f6b23df-a583-476b-b0e4-87457e65f7c0", + "version": [ + 1, + 5, + 6 + ], + "min_engine_version": [ + 1, + 26, + 20 + ] + }, + "modules": [ + { + "description": "Behavior Pack Module", + "type": "data", + "uuid": "47787f93-ec3c-4b0c-95fb-55a70e029041", + "version": [ + 1, + 0, + 0 + ] + }, + { + "description": "Gametest Module", + "type": "script", + "language": "javascript", + "entry": "scripts/main.js", + "uuid": "3d753132-e3c9-4305-a995-eae30b486093", + "version": [ + 1, + 0, + 0 + ] + } + ], + "dependencies": [ + { + "module_name": "@minecraft/server", + "version": "2.8.0-beta" + }, + { + "module_name": "@minecraft/server-ui", + "version": "2.1.0-beta" + }, + { + "module_name": "@minecraft/debug-utilities", + "version": "1.0.0-beta" + }, + { + "uuid": "bcf34368-ed0c-4cf7-938e-582cccf9950d", + "version": [ + 1, + 5, + 6 + ] + } + ], + "metadata": { + "authors": [ + "ForestOfLight" + ], + "license": "MIT", + "url": "https://github.com/ForestOfLight/Canopy", + "product_type": "addon" + } +} diff --git a/Canopy [BP]/pack_icon.png b/Canopy[BP]/pack_icon.png similarity index 100% rename from Canopy [BP]/pack_icon.png rename to Canopy[BP]/pack_icon.png diff --git a/Canopy[BP]/scripts/constants.js b/Canopy[BP]/scripts/constants.js new file mode 100644 index 00000000..7c521efd --- /dev/null +++ b/Canopy[BP]/scripts/constants.js @@ -0,0 +1,4 @@ +const PACK_VERSION = '1.5.6'; +const MC_VERSION = '1.26.20.26'; + +export { PACK_VERSION, MC_VERSION }; \ No newline at end of file diff --git a/Canopy [BP]/scripts/include/data.js b/Canopy[BP]/scripts/include/data.js similarity index 100% rename from Canopy [BP]/scripts/include/data.js rename to Canopy[BP]/scripts/include/data.js diff --git a/Canopy [BP]/scripts/include/utils.js b/Canopy[BP]/scripts/include/utils.js similarity index 100% rename from Canopy [BP]/scripts/include/utils.js rename to Canopy[BP]/scripts/include/utils.js diff --git a/Canopy [BP]/scripts/lib/MCBE-IPC/ipc.d.ts b/Canopy[BP]/scripts/lib/MCBE-IPC/ipc.d.ts similarity index 100% rename from Canopy [BP]/scripts/lib/MCBE-IPC/ipc.d.ts rename to Canopy[BP]/scripts/lib/MCBE-IPC/ipc.d.ts diff --git a/Canopy [BP]/scripts/lib/MCBE-IPC/ipc.js b/Canopy[BP]/scripts/lib/MCBE-IPC/ipc.js similarity index 100% rename from Canopy [BP]/scripts/lib/MCBE-IPC/ipc.js rename to Canopy[BP]/scripts/lib/MCBE-IPC/ipc.js diff --git a/Canopy [BP]/scripts/lib/SRCItemDatabase/DBManager.js b/Canopy[BP]/scripts/lib/SRCItemDatabase/DBManager.js similarity index 100% rename from Canopy [BP]/scripts/lib/SRCItemDatabase/DBManager.js rename to Canopy[BP]/scripts/lib/SRCItemDatabase/DBManager.js diff --git a/Canopy [BP]/scripts/lib/SRCItemDatabase/Database.d.ts b/Canopy[BP]/scripts/lib/SRCItemDatabase/Database.d.ts similarity index 100% rename from Canopy [BP]/scripts/lib/SRCItemDatabase/Database.d.ts rename to Canopy[BP]/scripts/lib/SRCItemDatabase/Database.d.ts diff --git a/Canopy [BP]/scripts/lib/SRCItemDatabase/Database.js b/Canopy[BP]/scripts/lib/SRCItemDatabase/Database.js similarity index 100% rename from Canopy [BP]/scripts/lib/SRCItemDatabase/Database.js rename to Canopy[BP]/scripts/lib/SRCItemDatabase/Database.js diff --git a/Canopy [BP]/scripts/lib/SRCItemDatabase/ItemDatabase.d.ts b/Canopy[BP]/scripts/lib/SRCItemDatabase/ItemDatabase.d.ts similarity index 100% rename from Canopy [BP]/scripts/lib/SRCItemDatabase/ItemDatabase.d.ts rename to Canopy[BP]/scripts/lib/SRCItemDatabase/ItemDatabase.d.ts diff --git a/Canopy [BP]/scripts/lib/SRCItemDatabase/ItemDatabase.js b/Canopy[BP]/scripts/lib/SRCItemDatabase/ItemDatabase.js similarity index 100% rename from Canopy [BP]/scripts/lib/SRCItemDatabase/ItemDatabase.js rename to Canopy[BP]/scripts/lib/SRCItemDatabase/ItemDatabase.js diff --git a/Canopy [BP]/scripts/lib/Vector.js b/Canopy[BP]/scripts/lib/Vector.js similarity index 83% rename from Canopy [BP]/scripts/lib/Vector.js rename to Canopy[BP]/scripts/lib/Vector.js index bc8d56dc..19a6d3f6 100644 --- a/Canopy [BP]/scripts/lib/Vector.js +++ b/Canopy[BP]/scripts/lib/Vector.js @@ -17,20 +17,16 @@ Vector.dot = function dot(a, b) { return a.x * b.x + a.y * b.y + a.z * b.z; } Vector.angleBetween = function angleBetween(a, b) { return Math.acos(Vector.dot(a, b) / (Vector.magnitude(a) * Vector.magnitude(b))); } Vector.subtract = function subtract(a, b) { return { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z, __proto__: Vector.prototype } }; Vector.add = function add(a, b) { return { x: a.x + b.x, y: a.y + b.y, z: a.z + b.z, __proto__: Vector.prototype } }; -Vector.multiply = function multiply(vec, num) { +Vector.scale = function scale(vec, num) { if (typeof num == "number") return { x: vec.x * num, y: vec.y * num, z: vec.z * num, __proto__: Vector.prototype }; return { x: vec.x * num.x, y: vec.y * num.y, z: vec.z * num.z, __proto__: Vector.prototype }; } -Vector.divide = function divide(vec, num) { - if (typeof num == "number") return { x: vec.x / num, y: vec.y / num, z: vec.z / num, __proto__: Vector.prototype }; - return { x: vec.x / num.x, y: vec.y / num.y, z: vec.z / num.z, __proto__: Vector.prototype }; -} Vector.isVec3 = function isVec3(vec) { return vec[isVec3Symbol] === true; } Vector.floor = function floor(vec) { return { x: Math.floor(vec.x), y: Math.floor(vec.y), z: Math.floor(vec.z), __proto__: Vector.prototype }; } -Vector.projection = function projection(a, b) { return Vector.multiply(b, Vector.dot(a, b) / ((b.x * b.x + b.y * b.y + b.z * b.z) ** 2)); } +Vector.projection = function projection(a, b) { return Vector.scale(b, Vector.dot(a, b) / ((b.x * b.x + b.y * b.y + b.z * b.z) ** 2)); } Vector.rejection = function rejection(a, b) { return Vector.subtract(a, Vector.projection(a, b)); } -Vector.reflect = function reflect(v, n) { return Vector.subtract(v, Vector.multiply(n, 2 * Vector.dot(v, n))); } -Vector.lerp = function lerp(a, b, t) { return Vector.multiply(a, 1 - t).add(Vector.multiply(b, t)); } +Vector.reflect = function reflect(v, n) { return Vector.subtract(v, Vector.scale(n, 2 * Vector.dot(v, n))); } +Vector.lerp = function lerp(a, b, t) { return Vector.scale(a, 1 - t).add(Vector.scale(b, t)); } Vector.distance = function distance(a, b) { return Vector.magnitude(Vector.subtract(a, b)); } Vector.from = function from(object) { if (Vector.isVec3(object)) return object; @@ -44,6 +40,7 @@ Vector.sort = function sort(vec1, vec2) { const [z1, z2] = vec1.z < vec2.z ? [vec1.z, vec2.z] : [vec2.z, vec1.z]; return [{ x: x1, y: y1, z: z1, __proto__: Vector.prototype }, { x: x2, y: y2, z: z2, __proto__: Vector.prototype }]; } +Vector.equals = function equals(vec1, vec2) { return vec1.x === vec2.x && vec1.y === vec2.y && vec1.z === vec2.z; } Vector.up = { x: 0, y: 1, z: 0, __proto__: Vector.prototype }; Vector.down = { x: 0, y: -1, z: 0, __proto__: Vector.prototype }; Vector.right = { x: 1, y: 0, z: 0, __proto__: Vector.prototype }; @@ -62,8 +59,8 @@ Vector.prototype = { floor() { return Vector.floor(this); }, add(vec) { return Vector.add(this, vec); }, subtract(vec) { return Vector.subtract(this, vec); }, - multiply(num) { return Vector.multiply(this, num); }, - divide(num) { return Vector.divide(this, num); }, + scale(num) { return Vector.scale(this, num); }, + equals(vec) { return Vector.equals(this, vec); }, get length() { return Vector.magnitude(this); }, get normalized() { return Vector.normalize(this); }, x: 0, diff --git a/Canopy [BP]/scripts/lib/canopy/Canopy.js b/Canopy[BP]/scripts/lib/canopy/Canopy.js similarity index 86% rename from Canopy [BP]/scripts/lib/canopy/Canopy.js rename to Canopy[BP]/scripts/lib/canopy/Canopy.js index 8917fc2d..01b58d66 100644 --- a/Canopy [BP]/scripts/lib/canopy/Canopy.js +++ b/Canopy[BP]/scripts/lib/canopy/Canopy.js @@ -1,6 +1,7 @@ import { Commands } from "./commands/Commands"; import { Command } from "./commands/Command"; import { VanillaCommand } from "./commands/VanillaCommand"; +import { VanillaCommands } from "./commands/VanillaCommands"; import { Rules } from "./rules/Rules"; import { Rule } from "./rules/Rule"; import { BooleanRule } from "./rules/BooleanRule"; @@ -22,6 +23,6 @@ import { EntityCommandOrigin } from "./commands/EntityCommandOrigin"; import { PlayerCommandOrigin } from "./commands/PlayerCommandOrigin"; import { ServerCommandOrigin } from "./commands/ServerCommandOrigin"; -export { Commands, Command, VanillaCommand, BlockCommandOrigin, EntityCommandOrigin, PlayerCommandOrigin, ServerCommandOrigin, - Rules, Rule, BooleanRule, IntegerRule, FloatRule, GlobalRule, InfoDisplayRule, AbilityRule, +export { Commands, Command, VanillaCommand, VanillaCommands, BlockCommandOrigin, EntityCommandOrigin, PlayerCommandOrigin, ServerCommandOrigin, + Rules, Rule, BooleanRule, IntegerRule, FloatRule, GlobalRule, InfoDisplayRule, AbilityRule, RuleHelpEntry, CommandHelpEntry, InfoDisplayRuleHelpEntry, RuleHelpPage, CommandHelpPage, InfoDisplayRuleHelpPage, HelpBook, Extensions }; \ No newline at end of file diff --git a/Canopy [BP]/scripts/lib/canopy/Extension.js b/Canopy[BP]/scripts/lib/canopy/Extension.js similarity index 100% rename from Canopy [BP]/scripts/lib/canopy/Extension.js rename to Canopy[BP]/scripts/lib/canopy/Extension.js diff --git a/Canopy [BP]/scripts/lib/canopy/Extensions.js b/Canopy[BP]/scripts/lib/canopy/Extensions.js similarity index 100% rename from Canopy [BP]/scripts/lib/canopy/Extensions.js rename to Canopy[BP]/scripts/lib/canopy/Extensions.js diff --git a/Canopy [BP]/scripts/lib/canopy/commands/ArgumentParser.js b/Canopy[BP]/scripts/lib/canopy/commands/ArgumentParser.js similarity index 100% rename from Canopy [BP]/scripts/lib/canopy/commands/ArgumentParser.js rename to Canopy[BP]/scripts/lib/canopy/commands/ArgumentParser.js diff --git a/Canopy [BP]/scripts/lib/canopy/commands/BlockCommandOrigin.js b/Canopy[BP]/scripts/lib/canopy/commands/BlockCommandOrigin.js similarity index 100% rename from Canopy [BP]/scripts/lib/canopy/commands/BlockCommandOrigin.js rename to Canopy[BP]/scripts/lib/canopy/commands/BlockCommandOrigin.js diff --git a/Canopy [BP]/scripts/lib/canopy/commands/Command.js b/Canopy[BP]/scripts/lib/canopy/commands/Command.js similarity index 92% rename from Canopy [BP]/scripts/lib/canopy/commands/Command.js rename to Canopy[BP]/scripts/lib/canopy/commands/Command.js index 67cf596f..4b0b4f40 100644 --- a/Canopy [BP]/scripts/lib/canopy/commands/Command.js +++ b/Canopy[BP]/scripts/lib/canopy/commands/Command.js @@ -12,8 +12,9 @@ class Command { #helpEntries; #helpHidden; #extension; + #wikiDescription; - constructor({ name, description = { text: '' }, usage, callback, args = [], contingentRules = [], opOnly = false, helpEntries = [], helpHidden = false, extensionName = undefined }) { + constructor({ name, description = { text: '' }, usage, callback, args = [], contingentRules = [], opOnly = false, helpEntries = [], helpHidden = false, extensionName = undefined, wikiDescription = undefined }) { this.#name = name; this.#description = description; this.#usage = usage; @@ -24,6 +25,7 @@ class Command { this.#helpEntries = helpEntries; this.#helpHidden = helpHidden; this.#extension = Extensions.getFromName(extensionName); + this.#wikiDescription = wikiDescription; this.#checkMembers(extensionName); if (typeof this.#description === 'string') @@ -81,6 +83,10 @@ class Command { isHelpHidden() { return this.#helpHidden; } + + getWikiDescription() { + return this.#wikiDescription; + } runCallback(sender, args) { if (this.#extension) diff --git a/Canopy [BP]/scripts/lib/canopy/commands/CommandOrigin.js b/Canopy[BP]/scripts/lib/canopy/commands/CommandOrigin.js similarity index 100% rename from Canopy [BP]/scripts/lib/canopy/commands/CommandOrigin.js rename to Canopy[BP]/scripts/lib/canopy/commands/CommandOrigin.js diff --git a/Canopy [BP]/scripts/lib/canopy/commands/Commands.js b/Canopy[BP]/scripts/lib/canopy/commands/Commands.js similarity index 100% rename from Canopy [BP]/scripts/lib/canopy/commands/Commands.js rename to Canopy[BP]/scripts/lib/canopy/commands/Commands.js diff --git a/Canopy [BP]/scripts/lib/canopy/commands/EntityCommandOrigin.js b/Canopy[BP]/scripts/lib/canopy/commands/EntityCommandOrigin.js similarity index 100% rename from Canopy [BP]/scripts/lib/canopy/commands/EntityCommandOrigin.js rename to Canopy[BP]/scripts/lib/canopy/commands/EntityCommandOrigin.js diff --git a/Canopy [BP]/scripts/lib/canopy/commands/FeedbackMessageType.js b/Canopy[BP]/scripts/lib/canopy/commands/FeedbackMessageType.js similarity index 100% rename from Canopy [BP]/scripts/lib/canopy/commands/FeedbackMessageType.js rename to Canopy[BP]/scripts/lib/canopy/commands/FeedbackMessageType.js diff --git a/Canopy [BP]/scripts/lib/canopy/commands/PlayerCommandOrigin.js b/Canopy[BP]/scripts/lib/canopy/commands/PlayerCommandOrigin.js similarity index 100% rename from Canopy [BP]/scripts/lib/canopy/commands/PlayerCommandOrigin.js rename to Canopy[BP]/scripts/lib/canopy/commands/PlayerCommandOrigin.js diff --git a/Canopy [BP]/scripts/lib/canopy/commands/ServerCommandOrigin.js b/Canopy[BP]/scripts/lib/canopy/commands/ServerCommandOrigin.js similarity index 100% rename from Canopy [BP]/scripts/lib/canopy/commands/ServerCommandOrigin.js rename to Canopy[BP]/scripts/lib/canopy/commands/ServerCommandOrigin.js diff --git a/Canopy [BP]/scripts/lib/canopy/commands/VanillaCommand.js b/Canopy[BP]/scripts/lib/canopy/commands/VanillaCommand.js similarity index 93% rename from Canopy [BP]/scripts/lib/canopy/commands/VanillaCommand.js rename to Canopy[BP]/scripts/lib/canopy/commands/VanillaCommand.js index 39d2fe0b..e6c73354 100644 --- a/Canopy [BP]/scripts/lib/canopy/commands/VanillaCommand.js +++ b/Canopy[BP]/scripts/lib/canopy/commands/VanillaCommand.js @@ -1,5 +1,6 @@ import { CustomCommandSource, CustomCommandStatus, Player, system } from "@minecraft/server"; import { Rules } from "../rules/Rules"; +import { VanillaCommands } from "./VanillaCommands"; import { BlockCommandOrigin } from "./BlockCommandOrigin"; import { EntityCommandOrigin } from "./EntityCommandOrigin"; import { ServerCommandOrigin } from "./ServerCommandOrigin"; @@ -11,6 +12,7 @@ export class VanillaCommand { constructor(customCommand) { this.customCommand = customCommand; this.setDefaultArgs(); + VanillaCommands.register(this); system.beforeEvents.startup.subscribe(this.setupForRegistry.bind(this)); } @@ -108,4 +110,12 @@ export class VanillaCommand { return false; return !this.customCommand.allowedSources.includes(source.constructor); } + + getName() { + return this.customCommand.name.replace(/^[^:]+:/, ''); + } + + getSubCommandWikiDescription() { + return this.customCommand.subCommandWikiDescription || {}; + } } \ No newline at end of file diff --git a/Canopy[BP]/scripts/lib/canopy/commands/VanillaCommands.js b/Canopy[BP]/scripts/lib/canopy/commands/VanillaCommands.js new file mode 100644 index 00000000..bac84f3f --- /dev/null +++ b/Canopy[BP]/scripts/lib/canopy/commands/VanillaCommands.js @@ -0,0 +1,17 @@ +class VanillaCommands { + static #commands = []; + + static register(command) { + this.#commands.push(command); + } + + static getAll() { + return [...this.#commands]; + } + + static clear() { + this.#commands = []; + } +} + +export { VanillaCommands }; diff --git a/Canopy [BP]/scripts/lib/canopy/extension.ipc.js b/Canopy[BP]/scripts/lib/canopy/extension.ipc.js similarity index 100% rename from Canopy [BP]/scripts/lib/canopy/extension.ipc.js rename to Canopy[BP]/scripts/lib/canopy/extension.ipc.js diff --git a/Canopy [BP]/scripts/lib/canopy/help/CommandHelpEntry.js b/Canopy[BP]/scripts/lib/canopy/help/CommandHelpEntry.js similarity index 100% rename from Canopy [BP]/scripts/lib/canopy/help/CommandHelpEntry.js rename to Canopy[BP]/scripts/lib/canopy/help/CommandHelpEntry.js diff --git a/Canopy [BP]/scripts/lib/canopy/help/CommandHelpPage.js b/Canopy[BP]/scripts/lib/canopy/help/CommandHelpPage.js similarity index 100% rename from Canopy [BP]/scripts/lib/canopy/help/CommandHelpPage.js rename to Canopy[BP]/scripts/lib/canopy/help/CommandHelpPage.js diff --git a/Canopy [BP]/scripts/lib/canopy/help/HelpBook.js b/Canopy[BP]/scripts/lib/canopy/help/HelpBook.js similarity index 100% rename from Canopy [BP]/scripts/lib/canopy/help/HelpBook.js rename to Canopy[BP]/scripts/lib/canopy/help/HelpBook.js diff --git a/Canopy [BP]/scripts/lib/canopy/help/HelpEntry.js b/Canopy[BP]/scripts/lib/canopy/help/HelpEntry.js similarity index 100% rename from Canopy [BP]/scripts/lib/canopy/help/HelpEntry.js rename to Canopy[BP]/scripts/lib/canopy/help/HelpEntry.js diff --git a/Canopy [BP]/scripts/lib/canopy/help/HelpPage.js b/Canopy[BP]/scripts/lib/canopy/help/HelpPage.js similarity index 100% rename from Canopy [BP]/scripts/lib/canopy/help/HelpPage.js rename to Canopy[BP]/scripts/lib/canopy/help/HelpPage.js diff --git a/Canopy [BP]/scripts/lib/canopy/help/InfoDisplayRuleHelpEntry.js b/Canopy[BP]/scripts/lib/canopy/help/InfoDisplayRuleHelpEntry.js similarity index 100% rename from Canopy [BP]/scripts/lib/canopy/help/InfoDisplayRuleHelpEntry.js rename to Canopy[BP]/scripts/lib/canopy/help/InfoDisplayRuleHelpEntry.js diff --git a/Canopy [BP]/scripts/lib/canopy/help/InfoDisplayRuleHelpPage.js b/Canopy[BP]/scripts/lib/canopy/help/InfoDisplayRuleHelpPage.js similarity index 100% rename from Canopy [BP]/scripts/lib/canopy/help/InfoDisplayRuleHelpPage.js rename to Canopy[BP]/scripts/lib/canopy/help/InfoDisplayRuleHelpPage.js diff --git a/Canopy [BP]/scripts/lib/canopy/help/RuleHelpEntry.js b/Canopy[BP]/scripts/lib/canopy/help/RuleHelpEntry.js similarity index 100% rename from Canopy [BP]/scripts/lib/canopy/help/RuleHelpEntry.js rename to Canopy[BP]/scripts/lib/canopy/help/RuleHelpEntry.js diff --git a/Canopy [BP]/scripts/lib/canopy/help/RuleHelpPage.js b/Canopy[BP]/scripts/lib/canopy/help/RuleHelpPage.js similarity index 100% rename from Canopy [BP]/scripts/lib/canopy/help/RuleHelpPage.js rename to Canopy[BP]/scripts/lib/canopy/help/RuleHelpPage.js diff --git a/Canopy [BP]/scripts/lib/canopy/rules/AbilityRule.js b/Canopy[BP]/scripts/lib/canopy/rules/AbilityRule.js similarity index 100% rename from Canopy [BP]/scripts/lib/canopy/rules/AbilityRule.js rename to Canopy[BP]/scripts/lib/canopy/rules/AbilityRule.js diff --git a/Canopy [BP]/scripts/lib/canopy/rules/BooleanRule.js b/Canopy[BP]/scripts/lib/canopy/rules/BooleanRule.js similarity index 85% rename from Canopy [BP]/scripts/lib/canopy/rules/BooleanRule.js rename to Canopy[BP]/scripts/lib/canopy/rules/BooleanRule.js index 5384ad06..291edf44 100644 --- a/Canopy [BP]/scripts/lib/canopy/rules/BooleanRule.js +++ b/Canopy[BP]/scripts/lib/canopy/rules/BooleanRule.js @@ -2,7 +2,8 @@ import { Rule } from './Rule'; export class BooleanRule extends Rule { constructor(options) { - options.defaultValue = options.defaultValue || false; + options.suggestedOptions = options.suggestedOptions ?? [false, true]; + options.defaultValue = options.defaultValue ?? false; options.onModifyCallback = (value) => this.onModifyBool(value); super({ ...options }); this.onEnable = options.onEnableCallback || (() => {}); diff --git a/Canopy [BP]/scripts/lib/canopy/rules/FloatRule.js b/Canopy[BP]/scripts/lib/canopy/rules/FloatRule.js similarity index 82% rename from Canopy [BP]/scripts/lib/canopy/rules/FloatRule.js rename to Canopy[BP]/scripts/lib/canopy/rules/FloatRule.js index 021cd6ff..527db59b 100644 --- a/Canopy [BP]/scripts/lib/canopy/rules/FloatRule.js +++ b/Canopy[BP]/scripts/lib/canopy/rules/FloatRule.js @@ -26,6 +26,6 @@ export class FloatRule extends Rule { } isInRange(value) { - return this.valueRange.other?.includes(value) || (value >= this.valueRange.range?.min && value <= this.valueRange.range?.max); + return this.valueRange?.other?.includes(value) || (value >= this.valueRange?.range?.min && value <= this.valueRange.range?.max); } } \ No newline at end of file diff --git a/Canopy [BP]/scripts/lib/canopy/rules/GlobalRule.js b/Canopy[BP]/scripts/lib/canopy/rules/GlobalRule.js similarity index 100% rename from Canopy [BP]/scripts/lib/canopy/rules/GlobalRule.js rename to Canopy[BP]/scripts/lib/canopy/rules/GlobalRule.js diff --git a/Canopy [BP]/scripts/lib/canopy/rules/InfoDisplayRule.js b/Canopy[BP]/scripts/lib/canopy/rules/InfoDisplayRule.js similarity index 100% rename from Canopy [BP]/scripts/lib/canopy/rules/InfoDisplayRule.js rename to Canopy[BP]/scripts/lib/canopy/rules/InfoDisplayRule.js diff --git a/Canopy [BP]/scripts/lib/canopy/rules/IntegerRule.js b/Canopy[BP]/scripts/lib/canopy/rules/IntegerRule.js similarity index 83% rename from Canopy [BP]/scripts/lib/canopy/rules/IntegerRule.js rename to Canopy[BP]/scripts/lib/canopy/rules/IntegerRule.js index d20b61e7..013c59fa 100644 --- a/Canopy [BP]/scripts/lib/canopy/rules/IntegerRule.js +++ b/Canopy[BP]/scripts/lib/canopy/rules/IntegerRule.js @@ -26,6 +26,6 @@ export class IntegerRule extends Rule { } isInRange(value) { - return this.valueRange.other?.includes(value) || (value >= this.valueRange?.range.min && value <= this.valueRange.range.max); + return this.valueRange?.other?.includes(value) || (value >= this.valueRange?.range.min && value <= this.valueRange.range.max); } } \ No newline at end of file diff --git a/Canopy [BP]/scripts/lib/canopy/rules/Rule.js b/Canopy[BP]/scripts/lib/canopy/rules/Rule.js similarity index 88% rename from Canopy [BP]/scripts/lib/canopy/rules/Rule.js rename to Canopy[BP]/scripts/lib/canopy/rules/Rule.js index b4a2d55a..75edfaf0 100644 --- a/Canopy [BP]/scripts/lib/canopy/rules/Rule.js +++ b/Canopy[BP]/scripts/lib/canopy/rules/Rule.js @@ -9,10 +9,13 @@ export class Rule { #contingentRules; #independentRules; #extension; + #wikiDescription; + #suggestedOptions; constructor({ category, identifier, description = '', defaultValue = void 0, - contingentRules = [], independentRules = [], onModifyCallback = () => {}, extension = false }) { - if (this.constructor === Rule) + contingentRules = [], independentRules = [], onModifyCallback = () => {}, extension = false, + wikiDescription = undefined, suggestedOptions = undefined }) { + if (this.constructor === Rule) throw new TypeError("Abstract class 'Rule' cannot be instantiated directly."); this.#category = category; this.#identifier = identifier; @@ -22,6 +25,8 @@ export class Rule { this.#independentRules = independentRules; this.onModify = onModifyCallback; this.#extension = extension; + this.#wikiDescription = wikiDescription; + this.#suggestedOptions = suggestedOptions; Rules.register(this); } @@ -61,6 +66,14 @@ export class Rule { return this.#defaultValue; } + getWikiDescription() { + return this.#wikiDescription; + } + + getSuggestedOptions() { + return this.#suggestedOptions; + } + resetToDefaultValue() { this.setValue(this.#defaultValue); } diff --git a/Canopy [BP]/scripts/lib/canopy/rules/Rules.js b/Canopy[BP]/scripts/lib/canopy/rules/Rules.js similarity index 98% rename from Canopy [BP]/scripts/lib/canopy/rules/Rules.js rename to Canopy[BP]/scripts/lib/canopy/rules/Rules.js index 16fc385a..b0b07bbe 100644 --- a/Canopy [BP]/scripts/lib/canopy/rules/Rules.js +++ b/Canopy[BP]/scripts/lib/canopy/rules/Rules.js @@ -11,6 +11,7 @@ class Rules { throw new Error(`[Canopy] Rule with identifier '${rule.getID()}' already exists.`); this.#rules[rule.getID()] = rule; if (rule.getCategory() === "Rules") { + await Promise.resolve(); const value = await rule.getValue(); if (value === void 0) rule.resetToDefaultValue(); diff --git a/Canopy [BP]/scripts/lib/chestui/constants.js b/Canopy[BP]/scripts/lib/chestui/constants.js similarity index 100% rename from Canopy [BP]/scripts/lib/chestui/constants.js rename to Canopy[BP]/scripts/lib/chestui/constants.js diff --git a/Canopy [BP]/scripts/lib/chestui/forms.d.ts b/Canopy[BP]/scripts/lib/chestui/forms.d.ts similarity index 100% rename from Canopy [BP]/scripts/lib/chestui/forms.d.ts rename to Canopy[BP]/scripts/lib/chestui/forms.d.ts diff --git a/Canopy [BP]/scripts/lib/chestui/forms.js b/Canopy[BP]/scripts/lib/chestui/forms.js similarity index 100% rename from Canopy [BP]/scripts/lib/chestui/forms.js rename to Canopy[BP]/scripts/lib/chestui/forms.js diff --git a/Canopy [BP]/scripts/lib/chestui/typeIds.js b/Canopy[BP]/scripts/lib/chestui/typeIds.js similarity index 100% rename from Canopy [BP]/scripts/lib/chestui/typeIds.js rename to Canopy[BP]/scripts/lib/chestui/typeIds.js diff --git a/Canopy [BP]/scripts/main.js b/Canopy[BP]/scripts/main.js similarity index 95% rename from Canopy [BP]/scripts/main.js rename to Canopy[BP]/scripts/main.js index 613930e1..e944168a 100644 --- a/Canopy [BP]/scripts/main.js +++ b/Canopy[BP]/scripts/main.js @@ -80,6 +80,7 @@ import './src/rules/potionBoostedBreeding' import './src/rules/serverSideCollisionBoxes' import './src/rules/entitySeparation' import './src/rules/enderPearlChunkLoading' +import './src/rules/renderEndGatewayExits' // Load Time Processes import './src/onStart' diff --git a/Canopy [BP]/scripts/src/classes/BiomeEdgeFinder.js b/Canopy[BP]/scripts/src/classes/BiomeEdgeFinder.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/BiomeEdgeFinder.js rename to Canopy[BP]/scripts/src/classes/BiomeEdgeFinder.js diff --git a/Canopy [BP]/scripts/src/classes/BiomeEdgeRenderer.js b/Canopy[BP]/scripts/src/classes/BiomeEdgeRenderer.js similarity index 96% rename from Canopy [BP]/scripts/src/classes/BiomeEdgeRenderer.js rename to Canopy[BP]/scripts/src/classes/BiomeEdgeRenderer.js index fc056b43..fa800e42 100644 --- a/Canopy [BP]/scripts/src/classes/BiomeEdgeRenderer.js +++ b/Canopy[BP]/scripts/src/classes/BiomeEdgeRenderer.js @@ -10,7 +10,7 @@ export class BiomeEdgeRenderer { shouldStop = false; drawRunner = null; analysisBoundingBoxShape; - analysisColor = { red: 0, green: 0, blue: 1 }; + analysisColor = { red: 0, green: 0, blue: 1, alpha: 1 }; constructor(blockVolume, dimension) { this.blockVolume = blockVolume; @@ -138,11 +138,11 @@ export class BiomeEdgeRenderer { changeInFinalAxis[finalAxis] = quadHeight; const bound = new Vector(...changeInMiddleAxis).add(new Vector(...changeInFinalAxis)); - const worldLocation = Vector.from(this.blockVolume.getMin()).add(bound.multiply(0.5)).add(new Vector(...localLocation)); + const worldLocation = Vector.from(this.blockVolume.getMin()).add(bound.scale(0.5)).add(new Vector(...localLocation)); worldLocation.dimension = this.dimension; const sidedBox = new DebugBox(worldLocation); sidedBox.bound = bound; - sidedBox.color = { red: 1, green: 1, blue: 1 }; + sidedBox.color = { red: 1, green: 1, blue: 1, alpha: 1 }; this.drawShape(sidedBox); } @@ -162,8 +162,9 @@ export class BiomeEdgeRenderer { const biomeId = biome?.replace('minecraft:', ''); const hexColor = biomeToHexColorMap[biomeId]; if (!hexColor) - return { red: 1, green: 1, blue: 1 }; + return { red: 1, green: 1, blue: 1, alpha: 1 }; const biomeRGB = hexToRGB(hexColor); + biomeRGB.alpha = 1; return biomeRGB; } @@ -171,7 +172,7 @@ export class BiomeEdgeRenderer { const dimensionLocation = Vector.from(location).add(new Vector(0.5, 0.5, 0.5)); dimensionLocation.dimension = this.dimension; const tempBox = new DebugBox(dimensionLocation); - tempBox.color = { red: 1, green: 1, blue: 1 }; + tempBox.color = { red: 1, green: 1, blue: 1, alpha: 1 }; debugDrawer.addShape(tempBox); system.runTimeout(() => { debugDrawer.removeShape(tempBox); @@ -181,7 +182,7 @@ export class BiomeEdgeRenderer { drawAnalysisBoundingBox() { if (this.analysisBoundingBoxShape) this.analysisBoundingBoxShape.remove(); - const dimensionLocation = Vector.from(this.blockVolume.getMin()).add(Vector.from(this.blockVolume.getSpan()).multiply(0.5)); + const dimensionLocation = Vector.from(this.blockVolume.getMin()).add(Vector.from(this.blockVolume.getSpan()).scale(0.5)); dimensionLocation.dimension = this.dimension; const boundingBox = new DebugBox(dimensionLocation); boundingBox.bound = this.blockVolume.getSpan(); diff --git a/Canopy [BP]/scripts/src/classes/BlockRotator.js b/Canopy[BP]/scripts/src/classes/BlockRotator.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/BlockRotator.js rename to Canopy[BP]/scripts/src/classes/BlockRotator.js diff --git a/Canopy [BP]/scripts/src/classes/ChunkBorderRender.js b/Canopy[BP]/scripts/src/classes/ChunkBorderRender.js similarity index 93% rename from Canopy [BP]/scripts/src/classes/ChunkBorderRender.js rename to Canopy[BP]/scripts/src/classes/ChunkBorderRender.js index 56eb8fea..fe544c58 100644 --- a/Canopy [BP]/scripts/src/classes/ChunkBorderRender.js +++ b/Canopy[BP]/scripts/src/classes/ChunkBorderRender.js @@ -50,7 +50,7 @@ export class ChunkBorderRender { return; let color = void 0; if (y % this.CHUNK_SIZE === 0) - color = { red: 0, green: 0, blue: 1 }; + color = { red: 0, green: 0, blue: 1, alpha: 1 }; this.renderLine(new Vector(subChunkCoords.x, y, subChunkCoords.z), new Vector(subChunkCoords.x + this.CHUNK_SIZE, y, subChunkCoords.z), color); this.renderLine(new Vector(subChunkCoords.x, y, subChunkCoords.z + this.CHUNK_SIZE), new Vector(subChunkCoords.x + this.CHUNK_SIZE, y, subChunkCoords.z + this.CHUNK_SIZE), color); this.renderLine(new Vector(subChunkCoords.x, y, subChunkCoords.z), new Vector(subChunkCoords.x, y, subChunkCoords.z + this.CHUNK_SIZE), color); @@ -70,7 +70,7 @@ export class ChunkBorderRender { let bottomY = subChunkCoords.y - this.CHUNK_SIZE; let topY = subChunkCoords.y + this.CHUNK_SIZE*2; if (coord % this.CHUNK_SIZE === 0) { - color = { red: 0, green: 0, blue: 1 }; + color = { red: 0, green: 0, blue: 1, alpha: 1 }; bottomY = this.getLowerBound(subChunkCoords); topY = this.getUpperBound(subChunkCoords); } @@ -83,7 +83,7 @@ export class ChunkBorderRender { renderAdjacentChunkLinesAlongFace(subChunkCoords, iterableCoord, isX) { for (let coord = iterableCoord; coord <= iterableCoord + this.CHUNK_SIZE * 2; coord += this.CHUNK_SIZE) { - const color = { red: 1, green: 0, blue: 0 }; + const color = { red: 1, green: 0, blue: 0, alpha: 1 }; if (isX) this.renderLine(new Vector(coord, this.getLowerBound(subChunkCoords), subChunkCoords.z), new Vector(coord, this.getUpperBound(subChunkCoords), subChunkCoords.z), color); else @@ -91,7 +91,7 @@ export class ChunkBorderRender { } } - renderLine(start, end, color = { red: 1, green: 1, blue: 0 }) { + renderLine(start, end, color = { red: 1, green: 1, blue: 0, alpha: 1 }) { start.dimension = this.dimension; const line = new DebugLine(start, end); line.color = color; @@ -105,7 +105,7 @@ export class ChunkBorderRender { } getSubChunkWorldCoords(location) { - return Vector.from(location).divide(this.CHUNK_SIZE).floor().multiply(this.CHUNK_SIZE); + return Vector.from(location).scale(1/this.CHUNK_SIZE).floor().scale(this.CHUNK_SIZE); } getLowerBound(subChunkCoords) { diff --git a/Canopy [BP]/scripts/src/classes/CollisionBoxRenderer.js b/Canopy[BP]/scripts/src/classes/CollisionBoxRenderer.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/CollisionBoxRenderer.js rename to Canopy[BP]/scripts/src/classes/CollisionBoxRenderer.js diff --git a/Canopy [BP]/scripts/src/classes/CounterChannel.js b/Canopy[BP]/scripts/src/classes/CounterChannel.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/CounterChannel.js rename to Canopy[BP]/scripts/src/classes/CounterChannel.js diff --git a/Canopy [BP]/scripts/src/classes/CounterChannels.js b/Canopy[BP]/scripts/src/classes/CounterChannels.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/CounterChannels.js rename to Canopy[BP]/scripts/src/classes/CounterChannels.js diff --git a/Canopy [BP]/scripts/src/classes/DirectionState.js b/Canopy[BP]/scripts/src/classes/DirectionState.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/DirectionState.js rename to Canopy[BP]/scripts/src/classes/DirectionState.js diff --git a/Canopy[BP]/scripts/src/classes/EndGatewayExitFinder.js b/Canopy[BP]/scripts/src/classes/EndGatewayExitFinder.js new file mode 100644 index 00000000..8829d690 --- /dev/null +++ b/Canopy[BP]/scripts/src/classes/EndGatewayExitFinder.js @@ -0,0 +1,119 @@ +import { BlockVolume, system, UnloadedChunksError, world } from "@minecraft/server"; +import { Vector } from "../../lib/Vector"; +import { EndGatewayExitRender } from "./EndGatewayExitRender"; +import { EndGatewayExits } from "./EndGatewayExits"; + +export class EndGatewayExitFinder { + gatewayExits = []; + VANILLA_EXIT_SEARCH_AREA_SIZE = 32; + #runner = void 0; + + constructor() { + this.populateKnownExits(); + } + + destroy() { + for (const exit of this.gatewayExits) + exit.render.destroy(); + this.gatewayExits.length = 0; + } + + start() { + if (this.#runner) + return; + this.#runner = system.runInterval(() => this.onTick()); + } + + stop() { + if (this.#runner) { + system.clearRun(this.#runner); + this.#runner = void 0; + } + } + + onTick() { + for (const player of world.getPlayers()) + this.onTickPlayer(player); + } + + onTickPlayer(player) { + const dimension = player.dimension; + const previousLocation = player.location; + if (this.isNearEndGateway(dimension, previousLocation)) + system.runTimeout(() => this.tryAddEndGatewayExit(dimension, previousLocation, player.location), 1); + } + + isNearEndGateway(dimension, location) { + const min = Vector.add(location, new Vector(-1, -1, -1)); + const max = Vector.add(location, new Vector(1, 2, 1)); + const blockVolume = new BlockVolume(min, max); + try { + return dimension?.getBlocks(blockVolume, { includeTypes: ['minecraft:end_gateway'] })?.getCapacity() > 0; + } catch (error) { + if (error instanceof UnloadedChunksError) + return false; + throw error; + } + } + + tryAddEndGatewayExit(dimension, previousLocation, location) { + const flooredLocation = Vector.from(location).floor(); + if (!this.hasTraveledFar(previousLocation, flooredLocation) && !this.exitIsKnown(flooredLocation)) + return; + this.removeNearbyInvalidExits(dimension, flooredLocation); + this.addGatewayExit(dimension, flooredLocation); + } + + hasTraveledFar(previousLocation, location) { + const minDistance = 100; + const distance = Vector.distance(previousLocation, location); + return distance > minDistance; + } + + exitIsKnown(location) { + return this.gatewayExits.some(exit => exit.location.equals(Vector.from(location))); + } + + removeNearbyInvalidExits(dimension, location) { + const nearbyExits = this.gatewayExits.filter(exit => + exit.dimension.id === dimension.id + && Math.abs(exit.location.x - location.x) <= this.VANILLA_EXIT_SEARCH_AREA_SIZE / 2 + && Math.abs(exit.location.y - location.y) <= exit.dimension.heightRange.max + && Math.abs(exit.location.z - location.z) <= this.VANILLA_EXIT_SEARCH_AREA_SIZE / 2 + ); + for (const exit of nearbyExits) { + let blockBelowExit; + try { + blockBelowExit = dimension.getBlock({ x: exit.location.x, y: exit.location.y - 1, z: exit.location.z }); + } catch (error) { + if (error instanceof UnloadedChunksError) + continue; + throw error; + } + if (blockBelowExit?.id !== 'minecraft:end_stone') { + exit.render.destroy(); + this.removeGatewayExit(exit); + } + } + } + + addGatewayExit(dimension, location) { + const render = new EndGatewayExitRender(dimension, location, this.VANILLA_EXIT_SEARCH_AREA_SIZE); + this.gatewayExits.push({ location: location, dimension, render }); + EndGatewayExits.addLocation(dimension, location); + } + + removeGatewayExit(exit) { + this.gatewayExits.splice(this.gatewayExits.indexOf(exit), 1); + EndGatewayExits.removeLocation(exit.dimension, exit.location); + } + + populateKnownExits() { + const knownExits = EndGatewayExits.getLocations(); + for (const { dimension, ...location } of knownExits) { + const dimensionObj = world.getDimension(dimension.id); + if (dimensionObj) + this.gatewayExits.push({ location: Vector.from(location), dimension: dimensionObj, render: new EndGatewayExitRender(dimensionObj, location, this.VANILLA_EXIT_SEARCH_AREA_SIZE) }); + } + } +} \ No newline at end of file diff --git a/Canopy[BP]/scripts/src/classes/EndGatewayExitRender.js b/Canopy[BP]/scripts/src/classes/EndGatewayExitRender.js new file mode 100644 index 00000000..feef3af8 --- /dev/null +++ b/Canopy[BP]/scripts/src/classes/EndGatewayExitRender.js @@ -0,0 +1,72 @@ +import { debugDrawer, DebugBox, DebugLine } from "@minecraft/debug-utilities"; +import { Vector } from "../../lib/Vector"; + +export class EndGatewayExitRender { + debugShapes = []; + + constructor(dimension, location, searchAreaSize) { + this.dimension = dimension; + this.location = Vector.from(location).floor(); + this.searchAreaSize = searchAreaSize ?? 1; + this.render(); + } + + destroy() { + for(const shape of this.debugShapes) + shape.remove(); + this.debugShapes.length = 0; + } + + render() { + this.drawExit(); + this.drawSearchArea(); + } + + drawExit() { + const center = { + ...this.location.add(new Vector(0.5, 0.5, 0.5)), + dimension: this.dimension + }; + const box = new DebugBox(center); + box.color = { red: 1, green: 1, blue: 0, alpha: 1 }; + this.drawShape(box); + } + + drawSearchArea() { + const halfSize = this.searchAreaSize / 2; + const corners = [ + { x: this.location.x - halfSize, y: this.location.y, z: this.location.z - halfSize }, + { x: this.location.x - halfSize, y: this.location.y, z: this.location.z + halfSize }, + { x: this.location.x + halfSize, y: this.location.y, z: this.location.z + halfSize }, + { x: this.location.x + halfSize, y: this.location.y, z: this.location.z - halfSize } + ]; + for (let i = 0; i < corners.length; i++) { + this.drawCornerToCornerLine(corners, i); + this.drawEnclosingBox(); + } + } + + drawCornerToCornerLine(corners, i) { + const start = corners[i]; + const end = corners[(i + 1) % corners.length]; + const line = new DebugLine({ ...start, dimension: this.dimension }, end); + line.color = { red: 0, green: 1, blue: 0, alpha: 1 }; + this.drawShape(line); + } + + drawEnclosingBox() { + const dimensionLocation = { + ...this.location, + dimension: this.dimension + }; + const box = new DebugBox(dimensionLocation); + box.color = { red: 0, green: 1, blue: 0, alpha: 0.5 }; + box.bound = { x: this.searchAreaSize, y: this.searchAreaSize * 2, z: this.searchAreaSize }; + this.drawShape(box); + } + + drawShape(shape) { + debugDrawer.addShape(shape); + this.debugShapes.push(shape); + } +} \ No newline at end of file diff --git a/Canopy[BP]/scripts/src/classes/EndGatewayExits.js b/Canopy[BP]/scripts/src/classes/EndGatewayExits.js new file mode 100644 index 00000000..0d776261 --- /dev/null +++ b/Canopy[BP]/scripts/src/classes/EndGatewayExits.js @@ -0,0 +1,29 @@ +import { world } from "@minecraft/server"; + +export class EndGatewayExits { + static getLocations() { + const locationsData = world.getDynamicProperty("end_gateway_exit_locations"); + if (!locationsData) + return []; + return JSON.parse(locationsData) ?? []; + } + + static setLocations(dimensionLocations) { + if (!dimensionLocations) + dimensionLocations = []; + const formattedLocations = dimensionLocations.map(loc => ({ dimension: { id: loc.dimension.id }, x: loc.x, y: loc.y, z: loc.z })); + world.setDynamicProperty("end_gateway_exit_locations", JSON.stringify(formattedLocations)); + } + + static addLocation(dimension, location) { + const locations = this.getLocations(); + locations.push({ dimension: { id: dimension.id }, x: location.x, y: location.y, z: location.z }); + this.setLocations(locations); + } + + static removeLocation(dimension, location) { + const locations = this.getLocations(); + const updatedLocations = locations.filter(loc => !(loc.dimension.id === dimension.id && loc.x === location.x && loc.y === location.y && loc.z === location.z)); + this.setLocations(updatedLocations); + } +} \ No newline at end of file diff --git a/Canopy [BP]/scripts/src/classes/EntityLifetimeRecord.js b/Canopy[BP]/scripts/src/classes/EntityLifetimeRecord.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/EntityLifetimeRecord.js rename to Canopy[BP]/scripts/src/classes/EntityLifetimeRecord.js diff --git a/Canopy [BP]/scripts/src/classes/EntityLifetimeRecords.js b/Canopy[BP]/scripts/src/classes/EntityLifetimeRecords.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/EntityLifetimeRecords.js rename to Canopy[BP]/scripts/src/classes/EntityLifetimeRecords.js diff --git a/Canopy [BP]/scripts/src/classes/EntityLog.js b/Canopy[BP]/scripts/src/classes/EntityLog.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/EntityLog.js rename to Canopy[BP]/scripts/src/classes/EntityLog.js diff --git a/Canopy [BP]/scripts/src/classes/EntityMovementLog.js b/Canopy[BP]/scripts/src/classes/EntityMovementLog.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/EntityMovementLog.js rename to Canopy[BP]/scripts/src/classes/EntityMovementLog.js diff --git a/Canopy [BP]/scripts/src/classes/EntityTntLog.js b/Canopy[BP]/scripts/src/classes/EntityTntLog.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/EntityTntLog.js rename to Canopy[BP]/scripts/src/classes/EntityTntLog.js diff --git a/Canopy [BP]/scripts/src/classes/EventTracker.js b/Canopy[BP]/scripts/src/classes/EventTracker.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/EventTracker.js rename to Canopy[BP]/scripts/src/classes/EventTracker.js diff --git a/Canopy [BP]/scripts/src/classes/GeneratorChannel.js b/Canopy[BP]/scripts/src/classes/GeneratorChannel.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/GeneratorChannel.js rename to Canopy[BP]/scripts/src/classes/GeneratorChannel.js diff --git a/Canopy [BP]/scripts/src/classes/GeneratorChannels.js b/Canopy[BP]/scripts/src/classes/GeneratorChannels.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/GeneratorChannels.js rename to Canopy[BP]/scripts/src/classes/GeneratorChannels.js diff --git a/Canopy [BP]/scripts/src/classes/HSSFinder.js b/Canopy[BP]/scripts/src/classes/HSSFinder.js similarity index 95% rename from Canopy [BP]/scripts/src/classes/HSSFinder.js rename to Canopy[BP]/scripts/src/classes/HSSFinder.js index 39a0746b..4ea168cd 100644 --- a/Canopy [BP]/scripts/src/classes/HSSFinder.js +++ b/Canopy[BP]/scripts/src/classes/HSSFinder.js @@ -46,8 +46,8 @@ export class HSSFinder { calculateHSS(structureBounds) { const CHUNK_SIZE = 16; const chunkOverlay = { - min: structureBounds.min.divide(CHUNK_SIZE).floor().multiply(CHUNK_SIZE), - max: structureBounds.max.divide(CHUNK_SIZE).floor().add(new Vector(1, 1, 1)).multiply(CHUNK_SIZE) + min: structureBounds.min.scale(1/CHUNK_SIZE).floor().scale(CHUNK_SIZE), + max: structureBounds.max.scale(1/CHUNK_SIZE).floor().add(new Vector(1, 1, 1)).scale(CHUNK_SIZE) }; const hssLocations = []; for (let chunkX = chunkOverlay.min.x; chunkX < chunkOverlay.max.x; chunkX += CHUNK_SIZE) { @@ -99,7 +99,7 @@ export class HSSFinder { dimensionLocation.dimension = dimension; const box = new DebugBox(dimensionLocation); box.bound = new Vector(1, height, 1); - box.color = { red: 0, green: 1, blue: 0 }; + box.color = { red: 0, green: 1, blue: 0, alpha: 1 }; this.fortressHSSShapes.push(box); debugDrawer.addShape(box); } diff --git a/Canopy [BP]/scripts/src/classes/HSSRenderer.js b/Canopy[BP]/scripts/src/classes/HSSRenderer.js similarity index 92% rename from Canopy [BP]/scripts/src/classes/HSSRenderer.js rename to Canopy[BP]/scripts/src/classes/HSSRenderer.js index 02270a20..ccf88607 100644 --- a/Canopy [BP]/scripts/src/classes/HSSRenderer.js +++ b/Canopy[BP]/scripts/src/classes/HSSRenderer.js @@ -27,7 +27,7 @@ export class HSSRenderer { dimensionLocation.dimension = this.dimension; const box = new DebugBox(dimensionLocation); box.bound = this.structureBounds.getSize(); - box.color = { red: 1, green: 1, blue: 1 }; + box.color = { red: 1, green: 1, blue: 1, alpha: 1 }; this.renderShape(box); } @@ -41,7 +41,7 @@ export class HSSRenderer { bottom.dimension = this.dimension; const box = new DebugBox(bottom); box.bound = new Vector(1, this.structureBounds.getSize().y, 1); - box.color = { red: 0, green: 1, blue: 0 }; + box.color = { red: 0, green: 1, blue: 0, alpha: 1 }; this.renderShape(box); } diff --git a/Canopy [BP]/scripts/src/classes/HotbarManager.js b/Canopy[BP]/scripts/src/classes/HotbarManager.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/HotbarManager.js rename to Canopy[BP]/scripts/src/classes/HotbarManager.js diff --git a/Canopy [BP]/scripts/src/classes/Instaminable.js b/Canopy[BP]/scripts/src/classes/Instaminable.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/Instaminable.js rename to Canopy[BP]/scripts/src/classes/Instaminable.js diff --git a/Canopy [BP]/scripts/src/classes/InventoryUI.js b/Canopy[BP]/scripts/src/classes/InventoryUI.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/InventoryUI.js rename to Canopy[BP]/scripts/src/classes/InventoryUI.js diff --git a/Canopy [BP]/scripts/src/classes/ItemCounterChannel.js b/Canopy[BP]/scripts/src/classes/ItemCounterChannel.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/ItemCounterChannel.js rename to Canopy[BP]/scripts/src/classes/ItemCounterChannel.js diff --git a/Canopy [BP]/scripts/src/classes/ItemCounterChannels.js b/Canopy[BP]/scripts/src/classes/ItemCounterChannels.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/ItemCounterChannels.js rename to Canopy[BP]/scripts/src/classes/ItemCounterChannels.js diff --git a/Canopy [BP]/scripts/src/classes/ItemLifetimeRecord.js b/Canopy[BP]/scripts/src/classes/ItemLifetimeRecord.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/ItemLifetimeRecord.js rename to Canopy[BP]/scripts/src/classes/ItemLifetimeRecord.js diff --git a/Canopy [BP]/scripts/src/classes/Profiler.js b/Canopy[BP]/scripts/src/classes/Profiler.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/Profiler.js rename to Canopy[BP]/scripts/src/classes/Profiler.js diff --git a/Canopy[BP]/scripts/src/classes/SignalStrengthRenderer.js b/Canopy[BP]/scripts/src/classes/SignalStrengthRenderer.js new file mode 100644 index 00000000..04458998 --- /dev/null +++ b/Canopy[BP]/scripts/src/classes/SignalStrengthRenderer.js @@ -0,0 +1,69 @@ +import { system, TextPrimitive, world } from "@minecraft/server"; +import { Vector } from "../../lib/Vector"; + +export class SignalStrengthRenderer { + block; + dimension; + visibleToPlayer; + textShape; + runner = void 0; + + constructor(block, dimension, visibleToPlayer) { + this.block = block; + this.dimension = dimension; + this.visibleToPlayer = visibleToPlayer; + this.startRender(); + } + + destroy() { + this.stopRender(); + this.block = void 0; + this.visibleToPlayer = void 0; + } + + startRender() { + this.createTextShape(); + this.runner = system.runInterval(this.onTick.bind(this)); + } + + stopRender() { + if (this.runner !== void 0) { + system.clearRun(this.runner); + this.runner = void 0; + } + this.textShape?.remove(); + this.textShape = void 0; + } + + onTick() { + if (!this.block?.isValid || this.block.typeId !== "minecraft:redstone_wire") { + this.stopRender(); + return; + } + this.updateRedstonePower(); + } + + updateRedstonePower() { + const power = this.block.getRedstonePower(); + if (this.textShape.text !== String(power)) + this.textShape.setText(String(power)); + } + + createTextShape() { + const dimensionlocation = Vector.from(this.block.center()).add(new Vector(-0.0125, -7.7/16, 0.0925)); + dimensionlocation.dimension = this.dimension; + this.textShape = new TextPrimitive(dimensionlocation, String(this.block.getRedstonePower())); + this.textShape.backgroundColorOverride = { red: 0, green: 0, blue: 0, alpha: 0 }; + this.textShape.rotation = { x: 90, y: 0, z: 0 }; + this.textShape.useRotation = true; + this.textShape.depthTest = true; + this.textShape.backfaceVisible = false; + this.drawShape(); + } + + drawShape() { + if (this.visibleToPlayer) + this.textShape.visibleTo = [this.visibleToPlayer]; + world.primitiveShapesManager.addText(this.textShape); + } +} \ No newline at end of file diff --git a/Canopy [BP]/scripts/src/classes/SpawnTracker.js b/Canopy[BP]/scripts/src/classes/SpawnTracker.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/SpawnTracker.js rename to Canopy[BP]/scripts/src/classes/SpawnTracker.js diff --git a/Canopy [BP]/scripts/src/classes/StructureBoundsFinder.js b/Canopy[BP]/scripts/src/classes/StructureBoundsFinder.js similarity index 98% rename from Canopy [BP]/scripts/src/classes/StructureBoundsFinder.js rename to Canopy[BP]/scripts/src/classes/StructureBoundsFinder.js index dbaf04e2..f28bb1a8 100644 --- a/Canopy [BP]/scripts/src/classes/StructureBoundsFinder.js +++ b/Canopy[BP]/scripts/src/classes/StructureBoundsFinder.js @@ -38,7 +38,7 @@ export class StructureBoundsFinder { } getCenterpoint() { - return this.min.add(this.getSize().multiply(0.5)); + return this.min.add(this.getSize().scale(0.5)); } tryAnalyzeStructureBounds() { diff --git a/Canopy [BP]/scripts/src/classes/TNTFuse.js b/Canopy[BP]/scripts/src/classes/TNTFuse.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/TNTFuse.js rename to Canopy[BP]/scripts/src/classes/TNTFuse.js diff --git a/Canopy [BP]/scripts/src/classes/Warps.js b/Canopy[BP]/scripts/src/classes/Warps.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/Warps.js rename to Canopy[BP]/scripts/src/classes/Warps.js diff --git a/Canopy [BP]/scripts/src/classes/WorldLifetimeTracker.js b/Canopy[BP]/scripts/src/classes/WorldLifetimeTracker.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/WorldLifetimeTracker.js rename to Canopy[BP]/scripts/src/classes/WorldLifetimeTracker.js diff --git a/Canopy [BP]/scripts/src/classes/WorldSpawns.js b/Canopy[BP]/scripts/src/classes/WorldSpawns.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/WorldSpawns.js rename to Canopy[BP]/scripts/src/classes/WorldSpawns.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/Age.js b/Canopy[BP]/scripts/src/classes/debugdisplay/Age.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/Age.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/Age.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/AttackBox.js b/Canopy[BP]/scripts/src/classes/debugdisplay/AttackBox.js similarity index 94% rename from Canopy [BP]/scripts/src/classes/debugdisplay/AttackBox.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/AttackBox.js index 1c8fb1af..950d6f5d 100644 --- a/Canopy [BP]/scripts/src/classes/debugdisplay/AttackBox.js +++ b/Canopy[BP]/scripts/src/classes/debugdisplay/AttackBox.js @@ -12,7 +12,7 @@ export class AttackBox extends DebugDisplayShapeElement { const dimensionLocation = { ...attackBoxData.location, dimension: this.entity.dimension }; const attackBox = new DebugBox(dimensionLocation); attackBox.bound = attackBoxData.size; - attackBox.color = { red: 1, green: 0, blue: 0 }; + attackBox.color = { red: 1, green: 0, blue: 0, alpha: 1 }; this.drawShape(attackBox); } @@ -30,13 +30,13 @@ export class AttackBox extends DebugDisplayShapeElement { const marginFromCenter = this.getProjectileMargin(); return { location: new Vector(0, AABB.extent.y, 0), - size: marginFromCenter.multiply(2) + size: marginFromCenter.scale(2) }; } const marginFromCollisionBox = new Vector(0.8, 0, 0.8); return { location: new Vector(0, AABB.extent.y, 0), - size: Vector.from(AABB.extent).add(marginFromCollisionBox).multiply(2) + size: Vector.from(AABB.extent).add(marginFromCollisionBox).scale(2) }; } diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/AttributeDebugDisplayElement.js b/Canopy[BP]/scripts/src/classes/debugdisplay/AttributeDebugDisplayElement.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/AttributeDebugDisplayElement.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/AttributeDebugDisplayElement.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/BooleanDebugDisplayElement.js b/Canopy[BP]/scripts/src/classes/debugdisplay/BooleanDebugDisplayElement.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/BooleanDebugDisplayElement.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/BooleanDebugDisplayElement.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/Breath.js b/Canopy[BP]/scripts/src/classes/debugdisplay/Breath.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/Breath.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/Breath.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/CollisionBox.js b/Canopy[BP]/scripts/src/classes/debugdisplay/CollisionBox.js similarity index 88% rename from Canopy [BP]/scripts/src/classes/debugdisplay/CollisionBox.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/CollisionBox.js index 18b923e6..fa1be4ff 100644 --- a/Canopy [BP]/scripts/src/classes/debugdisplay/CollisionBox.js +++ b/Canopy[BP]/scripts/src/classes/debugdisplay/CollisionBox.js @@ -10,7 +10,7 @@ export class CollisionBox extends DebugDisplayShapeElement { const dimensionLocation = { ...collisionBoxData.location, dimension: this.entity.dimension }; const collisionBox = new DebugBox(dimensionLocation); collisionBox.bound = collisionBoxData.size; - collisionBox.color = { red: 1, green: 1, blue: 1 }; + collisionBox.color = { red: 1, green: 1, blue: 1, alpha: 1 }; this.drawShape(collisionBox); } @@ -23,7 +23,7 @@ export class CollisionBox extends DebugDisplayShapeElement { const AABB = this.entity.getAABB(); return { location: new Vector(0, AABB.extent.y, 0), - size: Vector.from(AABB.extent).multiply(2) + size: Vector.from(AABB.extent).scale(2) }; } diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/ComponentDebugDisplayElement.js b/Canopy[BP]/scripts/src/classes/debugdisplay/ComponentDebugDisplayElement.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/ComponentDebugDisplayElement.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/ComponentDebugDisplayElement.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/Container.js b/Canopy[BP]/scripts/src/classes/debugdisplay/Container.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/Container.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/Container.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/DebugDisplay.js b/Canopy[BP]/scripts/src/classes/debugdisplay/DebugDisplay.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/DebugDisplay.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/DebugDisplay.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/DebugDisplayElement.js b/Canopy[BP]/scripts/src/classes/debugdisplay/DebugDisplayElement.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/DebugDisplayElement.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/DebugDisplayElement.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/DebugDisplayShapeElement.js b/Canopy[BP]/scripts/src/classes/debugdisplay/DebugDisplayShapeElement.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/DebugDisplayShapeElement.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/DebugDisplayShapeElement.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/DebugDisplayTextDrawer.js b/Canopy[BP]/scripts/src/classes/debugdisplay/DebugDisplayTextDrawer.js similarity index 73% rename from Canopy [BP]/scripts/src/classes/debugdisplay/DebugDisplayTextDrawer.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/DebugDisplayTextDrawer.js index 785b0090..1696e871 100644 --- a/Canopy [BP]/scripts/src/classes/debugdisplay/DebugDisplayTextDrawer.js +++ b/Canopy[BP]/scripts/src/classes/debugdisplay/DebugDisplayTextDrawer.js @@ -1,4 +1,4 @@ -import { debugDrawer, DebugText } from '@minecraft/debug-utilities'; +import { world, TextPrimitive } from '@minecraft/server'; import { Vector } from "../../../lib/Vector"; export class DebugDisplayTextDrawer { @@ -11,21 +11,21 @@ export class DebugDisplayTextDrawer { } destroy() { - debugDrawer.removeShape(this.textShape); + this.textShape.remove(); this.debugDisplay = void 0; } update() { - this.textShape.text = this.debugDisplay.debugMessage; + this.textShape.setText(this.debugDisplay.debugMessage); } beginDraw() { if (this.isDrawing()) return; const dimensionLocation = { ...this.getTextLocation(), dimension: this.dimension }; - this.textShape = new DebugText(dimensionLocation, this.debugDisplay.debugMessage); + this.textShape = new TextPrimitive(dimensionLocation, this.debugDisplay.debugMessage); this.textShape.attachedTo = this.debugDisplay.entity; - debugDrawer.addShape(this.textShape); + world.primitiveShapesManager.addText(this.textShape); } isDrawing() { diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/DebugDisplayTextElement.js b/Canopy[BP]/scripts/src/classes/debugdisplay/DebugDisplayTextElement.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/DebugDisplayTextElement.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/DebugDisplayTextElement.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/Effects.js b/Canopy[BP]/scripts/src/classes/debugdisplay/Effects.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/Effects.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/Effects.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/Equipment.js b/Canopy[BP]/scripts/src/classes/debugdisplay/Equipment.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/Equipment.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/Equipment.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/Exhaustion.js b/Canopy[BP]/scripts/src/classes/debugdisplay/Exhaustion.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/Exhaustion.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/Exhaustion.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/EyeLevel.js b/Canopy[BP]/scripts/src/classes/debugdisplay/EyeLevel.js similarity index 94% rename from Canopy [BP]/scripts/src/classes/debugdisplay/EyeLevel.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/EyeLevel.js index 28ab1681..7da58c2c 100644 --- a/Canopy [BP]/scripts/src/classes/debugdisplay/EyeLevel.js +++ b/Canopy[BP]/scripts/src/classes/debugdisplay/EyeLevel.js @@ -10,7 +10,7 @@ export class EyeLevel extends DebugDisplayShapeElement { const dimensionLocation = { ...eyeLevelData.location, dimension: this.entity.dimension }; const eyeLevel = new DebugBox(dimensionLocation); eyeLevel.bound = eyeLevelData.size; - eyeLevel.color = { red: 1, green: 0, blue: 0 }; + eyeLevel.color = { red: 1, green: 0, blue: 0, alpha: 1 }; this.drawShape(eyeLevel); } diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/Families.js b/Canopy[BP]/scripts/src/classes/debugdisplay/Families.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/Families.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/Families.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/FlySpeed.js b/Canopy[BP]/scripts/src/classes/debugdisplay/FlySpeed.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/FlySpeed.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/FlySpeed.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/Friction.js b/Canopy[BP]/scripts/src/classes/debugdisplay/Friction.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/Friction.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/Friction.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/GrowUp.js b/Canopy[BP]/scripts/src/classes/debugdisplay/GrowUp.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/GrowUp.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/GrowUp.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/HeadLocation.js b/Canopy[BP]/scripts/src/classes/debugdisplay/HeadLocation.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/HeadLocation.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/HeadLocation.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/Health.js b/Canopy[BP]/scripts/src/classes/debugdisplay/Health.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/Health.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/Health.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/HitBox.js b/Canopy[BP]/scripts/src/classes/debugdisplay/HitBox.js similarity index 98% rename from Canopy [BP]/scripts/src/classes/debugdisplay/HitBox.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/HitBox.js index 59dc9600..ef548acb 100644 --- a/Canopy [BP]/scripts/src/classes/debugdisplay/HitBox.js +++ b/Canopy[BP]/scripts/src/classes/debugdisplay/HitBox.js @@ -25,7 +25,7 @@ export class HitBox extends DebugDisplayShapeElement { const marginFromCollisionBox = this.getMargin(); return { location: new Vector(0, AABB.extent.y, 0), - size: Vector.from(AABB.extent).add(marginFromCollisionBox).multiply(2) + size: Vector.from(AABB.extent).add(marginFromCollisionBox).scale(2) }; } diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/Horse.js b/Canopy[BP]/scripts/src/classes/debugdisplay/Horse.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/Horse.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/Horse.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/Hunger.js b/Canopy[BP]/scripts/src/classes/debugdisplay/Hunger.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/Hunger.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/Hunger.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/ID.js b/Canopy[BP]/scripts/src/classes/debugdisplay/ID.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/ID.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/ID.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/IsClimbing.js b/Canopy[BP]/scripts/src/classes/debugdisplay/IsClimbing.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/IsClimbing.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/IsClimbing.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/IsFalling.js b/Canopy[BP]/scripts/src/classes/debugdisplay/IsFalling.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/IsFalling.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/IsFalling.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/IsIllagerCaptain.js b/Canopy[BP]/scripts/src/classes/debugdisplay/IsIllagerCaptain.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/IsIllagerCaptain.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/IsIllagerCaptain.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/IsInWater.js b/Canopy[BP]/scripts/src/classes/debugdisplay/IsInWater.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/IsInWater.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/IsInWater.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/IsOnGround.js b/Canopy[BP]/scripts/src/classes/debugdisplay/IsOnGround.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/IsOnGround.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/IsOnGround.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/IsSleeping.js b/Canopy[BP]/scripts/src/classes/debugdisplay/IsSleeping.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/IsSleeping.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/IsSleeping.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/IsSneaking.js b/Canopy[BP]/scripts/src/classes/debugdisplay/IsSneaking.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/IsSneaking.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/IsSneaking.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/IsSprinting.js b/Canopy[BP]/scripts/src/classes/debugdisplay/IsSprinting.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/IsSprinting.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/IsSprinting.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/IsSwimming.js b/Canopy[BP]/scripts/src/classes/debugdisplay/IsSwimming.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/IsSwimming.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/IsSwimming.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/IsValid.js b/Canopy[BP]/scripts/src/classes/debugdisplay/IsValid.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/IsValid.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/IsValid.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/Item.js b/Canopy[BP]/scripts/src/classes/debugdisplay/Item.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/Item.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/Item.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/LavaMovement.js b/Canopy[BP]/scripts/src/classes/debugdisplay/LavaMovement.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/LavaMovement.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/LavaMovement.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/Leash.js b/Canopy[BP]/scripts/src/classes/debugdisplay/Leash.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/Leash.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/Leash.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/Location.js b/Canopy[BP]/scripts/src/classes/debugdisplay/Location.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/Location.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/Location.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/Movement.js b/Canopy[BP]/scripts/src/classes/debugdisplay/Movement.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/Movement.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/Movement.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/NameTag.js b/Canopy[BP]/scripts/src/classes/debugdisplay/NameTag.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/NameTag.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/NameTag.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/OnFire.js b/Canopy[BP]/scripts/src/classes/debugdisplay/OnFire.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/OnFire.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/OnFire.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/Projectile.js b/Canopy[BP]/scripts/src/classes/debugdisplay/Projectile.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/Projectile.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/Projectile.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/PushThrough.js b/Canopy[BP]/scripts/src/classes/debugdisplay/PushThrough.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/PushThrough.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/PushThrough.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/Ride.js b/Canopy[BP]/scripts/src/classes/debugdisplay/Ride.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/Ride.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/Ride.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/Riding.js b/Canopy[BP]/scripts/src/classes/debugdisplay/Riding.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/Riding.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/Riding.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/Rotation.js b/Canopy[BP]/scripts/src/classes/debugdisplay/Rotation.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/Rotation.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/Rotation.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/Saturation.js b/Canopy[BP]/scripts/src/classes/debugdisplay/Saturation.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/Saturation.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/Saturation.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/Scale.js b/Canopy[BP]/scripts/src/classes/debugdisplay/Scale.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/Scale.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/Scale.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/SkinId.js b/Canopy[BP]/scripts/src/classes/debugdisplay/SkinId.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/SkinId.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/SkinId.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/Speed.js b/Canopy[BP]/scripts/src/classes/debugdisplay/Speed.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/Speed.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/Speed.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/Strength.js b/Canopy[BP]/scripts/src/classes/debugdisplay/Strength.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/Strength.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/Strength.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/TNT.js b/Canopy[BP]/scripts/src/classes/debugdisplay/TNT.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/TNT.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/TNT.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/Tame.js b/Canopy[BP]/scripts/src/classes/debugdisplay/Tame.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/Tame.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/Tame.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/Target.js b/Canopy[BP]/scripts/src/classes/debugdisplay/Target.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/Target.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/Target.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/TypeID.js b/Canopy[BP]/scripts/src/classes/debugdisplay/TypeID.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/TypeID.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/TypeID.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/UnderwaterMovement.js b/Canopy[BP]/scripts/src/classes/debugdisplay/UnderwaterMovement.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/UnderwaterMovement.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/UnderwaterMovement.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/Variant.js b/Canopy[BP]/scripts/src/classes/debugdisplay/Variant.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/Variant.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/Variant.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/VectorDebugDisplayElement.js b/Canopy[BP]/scripts/src/classes/debugdisplay/VectorDebugDisplayElement.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/VectorDebugDisplayElement.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/VectorDebugDisplayElement.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/Velocity.js b/Canopy[BP]/scripts/src/classes/debugdisplay/Velocity.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/Velocity.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/Velocity.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/ViewDirection.js b/Canopy[BP]/scripts/src/classes/debugdisplay/ViewDirection.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/ViewDirection.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/ViewDirection.js diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/ViewDirectionVector.js b/Canopy[BP]/scripts/src/classes/debugdisplay/ViewDirectionVector.js similarity index 84% rename from Canopy [BP]/scripts/src/classes/debugdisplay/ViewDirectionVector.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/ViewDirectionVector.js index 31ea668f..39b0a713 100644 --- a/Canopy [BP]/scripts/src/classes/debugdisplay/ViewDirectionVector.js +++ b/Canopy[BP]/scripts/src/classes/debugdisplay/ViewDirectionVector.js @@ -1,7 +1,7 @@ import { DebugDisplayShapeElement } from "./DebugDisplayShapeElement"; import { Vector } from "../../../lib/Vector"; import { DebugArrow } from "@minecraft/debug-utilities"; -import { serverSideCollisionBoxes } from "../../rules/serverSideCollisionBoxes"; +import { Rules } from "../../../lib/canopy/rules/Rules"; export class ViewDirectionVector extends DebugDisplayShapeElement { createShapes() { @@ -10,14 +10,14 @@ export class ViewDirectionVector extends DebugDisplayShapeElement { const viewDirectionVector = new DebugArrow(dimensionLocation, viewDirectionData.endLocation); viewDirectionVector.headLength = 0.20; viewDirectionVector.headRadius = 0.10; - viewDirectionVector.color = { red: 0, green: 0, blue: 1 }; + viewDirectionVector.color = { red: 0, green: 0, blue: 1, alpha: 1 }; this.drawShape(viewDirectionVector); } update() { const viewDirectionData = this.getViewDirectionBounds(); let endLocation = viewDirectionData.endLocation; - if (serverSideCollisionBoxes.getNativeValue()) + if (Rules.getNativeValue('serverSideCollisionBoxes')) endLocation = endLocation.add(this.entity.location); this.shapes[0].endLocation = endLocation; } @@ -27,7 +27,7 @@ export class ViewDirectionVector extends DebugDisplayShapeElement { const location = new Vector(0, this.entity.getHeadLocation().y - this.entity.location.y, 0); return { location, - endLocation: location.add(this.entity.getViewDirection()).multiply(1 + AABB.extent.x) + endLocation: location.add(this.entity.getViewDirection()).scale(1 + AABB.extent.x) }; } diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/WantsJockey.js b/Canopy[BP]/scripts/src/classes/debugdisplay/WantsJockey.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/WantsJockey.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/WantsJockey.js diff --git a/Canopy [BP]/scripts/src/classes/errors/GeneratedStructureError.js b/Canopy[BP]/scripts/src/classes/errors/GeneratedStructureError.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/errors/GeneratedStructureError.js rename to Canopy[BP]/scripts/src/classes/errors/GeneratedStructureError.js diff --git a/Canopy [BP]/scripts/src/commands/CommandEnums.js b/Canopy[BP]/scripts/src/commands/CommandEnums.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/CommandEnums.js rename to Canopy[BP]/scripts/src/commands/CommandEnums.js diff --git a/Canopy [BP]/scripts/src/commands/biomeedges.js b/Canopy[BP]/scripts/src/commands/biomeedges.js similarity index 86% rename from Canopy [BP]/scripts/src/commands/biomeedges.js rename to Canopy[BP]/scripts/src/commands/biomeedges.js index 47c6fb20..3529dd09 100644 --- a/Canopy [BP]/scripts/src/commands/biomeedges.js +++ b/Canopy[BP]/scripts/src/commands/biomeedges.js @@ -25,6 +25,20 @@ export class BiomeEdges extends VanillaCommand { permissionLevel: CommandPermissionLevel.GameDirectors, allowedSources: [PlayerCommandOrigin, BlockCommandOrigin, EntityCommandOrigin], callback: (origin, ...args) => this.biomeEdgesCommand(origin, ...args), + subCommandWikiDescription: { + 'add': { + description: 'Creates a visual representation of biome edges in the specified region.', + params: ['from', 'to'] + }, + 'removelast': { + description: 'Removes the most recently created biome edge visualization.', + params: [] + }, + 'clear': { + description: 'Removes all biome edge visualizations.', + params: [] + }, + }, }); this.biomeEdgeFinders = []; } diff --git a/Canopy [BP]/scripts/src/commands/butcher.js b/Canopy[BP]/scripts/src/commands/butcher.js similarity index 92% rename from Canopy [BP]/scripts/src/commands/butcher.js rename to Canopy[BP]/scripts/src/commands/butcher.js index 625c9b45..11aa1ec7 100644 --- a/Canopy [BP]/scripts/src/commands/butcher.js +++ b/Canopy[BP]/scripts/src/commands/butcher.js @@ -8,7 +8,8 @@ new VanillaCommand({ optionalParameters: [{ name: 'entity', type: CustomCommandParamType.EntitySelector }], permissionLevel: CommandPermissionLevel.GameDirectors, cheatsRequired: true, - callback: butcherCommand + callback: butcherCommand, + wikiDescription: 'Instantly removes entities without dropping their items. If the entity argument is unspecified, the entity you are looking at is assumed.' }); function butcherCommand(origin, entity) { diff --git a/Canopy [BP]/scripts/src/commands/camera.js b/Canopy[BP]/scripts/src/commands/camera.js similarity index 89% rename from Canopy [BP]/scripts/src/commands/camera.js rename to Canopy[BP]/scripts/src/commands/camera.js index 4ab58716..893adf05 100644 --- a/Canopy [BP]/scripts/src/commands/camera.js +++ b/Canopy[BP]/scripts/src/commands/camera.js @@ -9,6 +9,7 @@ new BooleanRule({ category: 'Rules', identifier: 'commandCamera', description: { translate: 'rules.commandCamera' }, + wikiDescription: 'Determines whether the `/cam` command can be used.', onEnableCallback: () => { world.afterEvents.playerGameModeChange.subscribe(onPlayerGameModeChange); world.beforeEvents.playerLeave.subscribe(onPlayerLeave); @@ -35,7 +36,21 @@ new VanillaCommand({ permissionLevel: CommandPermissionLevel.Any, contingentRules: ['commandCamera'], allowedSources: [PlayerCommandOrigin], - callback: cameraCommand + callback: cameraCommand, + subCommandWikiDescription: { + 'place': { + description: 'Places down a camera for you to view later on.', + params: [] + }, + 'view': { + description: 'Toggles viewing your placed camera. Your player will still be able to move around and interact as normal while viewing your placed camera.', + params: [] + }, + 'spectate': { + description: 'Toggles a survival-friendly freecam. It switches you to spectator mode with night vision and conduit power. When you are finished, just run the command again to return to your original position. Alias: **`/cs`**', + params: [] + }, + }, }); new VanillaCommand({ @@ -46,6 +61,7 @@ new VanillaCommand({ contingentRules: ['commandCamera'], allowedSources: [PlayerCommandOrigin], callback: (origin) => cameraCommand(origin, CAM_ACTIONS.Spectate), + wikiDescription: 'Alias for `/cam spectate`.' }); class BeforeSpectatorPlayer { diff --git a/Canopy [BP]/scripts/src/commands/canopy.js b/Canopy[BP]/scripts/src/commands/canopy.js similarity index 94% rename from Canopy [BP]/scripts/src/commands/canopy.js rename to Canopy[BP]/scripts/src/commands/canopy.js index c553c540..afe9dec6 100644 --- a/Canopy [BP]/scripts/src/commands/canopy.js +++ b/Canopy[BP]/scripts/src/commands/canopy.js @@ -13,7 +13,7 @@ const cmd = new Command({ ], callback: canopyCommand, helpEntries: [ - { usage: 'canopy menu', description: { translate: 'commands.canopy.menu' } }, + { usage: 'canopy menu', description: { translate: 'commands.canopy.menu' }, wikiDescription: 'Displays a menu with toggles for every rule. Flip the switches for the ones you want a hit the submit button at the bottom to save your changes.' }, { usage: 'canopy [true/false/integer/float]', description: { translate: 'commands.canopy.single' } }, { usage: 'canopy <[rule1,rule2,...]> [true/false/integer/float]', description: { translate: 'commands.canopy.multiple' } }, { usage: 'canopy version', description: { translate: 'commands.canopy.version' } } @@ -131,7 +131,7 @@ async function openMenu(sender) { if (rule.getType() === 'boolean') form.toggle(rule.getID(), { defaultValue: ruleValue, tooltip: rule.getDescription() }); else - form.textField(rule.getID(), rule.getType(), { defaultValue: String(rule.getDefaultValue()), tooltip: rule.getDescription() }); + form.textField(rule.getID(), rule.getType(), { defaultValue: String(ruleValue), tooltip: rule.getDescription() }); } catch (error) { sender.sendMessage(`§cError: ${error.message} for rule ${rule.getID()}`); } diff --git a/Canopy [BP]/scripts/src/commands/changedimension.js b/Canopy[BP]/scripts/src/commands/changedimension.js similarity index 85% rename from Canopy [BP]/scripts/src/commands/changedimension.js rename to Canopy[BP]/scripts/src/commands/changedimension.js index 9e0d66e1..a1f5fd31 100644 --- a/Canopy [BP]/scripts/src/commands/changedimension.js +++ b/Canopy[BP]/scripts/src/commands/changedimension.js @@ -23,7 +23,8 @@ new VanillaCommand({ ], permissionLevel: CommandPermissionLevel.GameDirectors, cheatsRequired: true, - callback: changeDimensionCommand + callback: changeDimensionCommand, + wikiDescription: 'Teleports entities to the specified dimension. If you include coordinates, the victim will be teleported to those coordinates in the specified dimension, otherwise coordinates will keep your current coordinates in the new dimension. Coordinates are converted like a nether portal when changing between the nether and the overworld. When the victim argument is empty, the command sender is assumed.' }); function changeDimensionCommand(origin, dimension, destination, victim) { diff --git a/Canopy [BP]/scripts/src/commands/claimprojectiles.js b/Canopy[BP]/scripts/src/commands/claimprojectiles.js similarity index 84% rename from Canopy [BP]/scripts/src/commands/claimprojectiles.js rename to Canopy[BP]/scripts/src/commands/claimprojectiles.js index d31a1e58..a982cc6f 100644 --- a/Canopy [BP]/scripts/src/commands/claimprojectiles.js +++ b/Canopy[BP]/scripts/src/commands/claimprojectiles.js @@ -6,7 +6,8 @@ const CLAIM_RADIUS = 25; new BooleanRule({ category: 'Rules', identifier: 'commandClaimProjectiles', - description: { translate: 'rules.commandClaimProjectiles' } + description: { translate: 'rules.commandClaimProjectiles' }, + wikiDescription: 'Determines whether the `/claimprojectiles` command can be used.' }); new VanillaCommand( { @@ -18,7 +19,8 @@ new VanillaCommand( { ], permissionLevel: CommandPermissionLevel.Any, contingentRules: ['commandClaimProjectiles'], - callback: claimProjectilesCommand + callback: claimProjectilesCommand, + wikiDescription: 'Makes you (or another specified player) the owner of all projectile within a certain block radius. If no radius is specified, the default is 25 blocks.' }); function claimProjectilesCommand(origin, radius = CLAIM_RADIUS, player) { diff --git a/Canopy [BP]/scripts/src/commands/cleanup.js b/Canopy[BP]/scripts/src/commands/cleanup.js similarity index 91% rename from Canopy [BP]/scripts/src/commands/cleanup.js rename to Canopy[BP]/scripts/src/commands/cleanup.js index 3de49187..3531cac0 100644 --- a/Canopy [BP]/scripts/src/commands/cleanup.js +++ b/Canopy[BP]/scripts/src/commands/cleanup.js @@ -9,7 +9,8 @@ new VanillaCommand({ allowedSources: [PlayerCommandOrigin, BlockCommandOrigin, EntityCommandOrigin], cheatsRequired: true, callback: cleanupCommand, - aliases: ['canopy:k'] + aliases: ['canopy:k'], + wikiDescription: 'Removes all items and xp orbs within 50 blocks, or at a specified distance. Alias: **`/k`**' }); const TRASH_ENTITY_TYPES = ['minecraft:item', 'minecraft:xp_orb']; diff --git a/Canopy [BP]/scripts/src/commands/counter.js b/Canopy[BP]/scripts/src/commands/counter.js similarity index 69% rename from Canopy [BP]/scripts/src/commands/counter.js rename to Canopy[BP]/scripts/src/commands/counter.js index 1b1d5254..794e1fe5 100644 --- a/Canopy [BP]/scripts/src/commands/counter.js +++ b/Canopy[BP]/scripts/src/commands/counter.js @@ -6,6 +6,7 @@ new BooleanRule({ category: 'Rules', identifier: 'hopperCounters', description: { translate: 'rules.hopperCounters' }, + wikiDescription: 'Enables/disables the counter command and hopper counter functionality. Disabling this rule also resets all counters.', onEnableCallback: () => counterChannels.enable(), onDisableCallback: () => counterChannels.disable() }); @@ -21,11 +22,12 @@ const cmd = new Command({ callback: counterCommand, contingentRules: ['hopperCounters'], helpEntries: [ - { usage: 'counter [color/all]', description: { translate: 'commands.counter.query' } }, - { usage: 'counter [color/all] realtime', description: { translate: 'commands.counter.realtime' } }, - { usage: 'counter [color/all] ', description: { translate: 'commands.counter.mode' } }, - { usage: 'counter [color/all] reset', description: { translate: 'commands.counter.reset' } }, - { usage: 'counter [color/all] remove', description: { translate: 'commands.counter.remove' } } + { usage: 'counter', description: { translate: 'commands.counter.query.all' }, wikiDescription: 'Displays information about the item counts in each channel. This includes metrics like the total items and items per hour, and the same divided up into individual item types. Alias: **`./ct`**' }, + { usage: 'counter [color/all]', description: { translate: 'commands.counter.query' }, wikiDescription: 'Does the same as `./counter`, but displays info for only one channel. Using the `all` keyword has exactly the same behavior as `./counter`. Alias: **`./ct `** This command can also be triggered with the vanilla command `/scriptevent canopy:counter [color]` (ie. in a command block).' }, + { usage: 'counter [color/all] realtime', description: { translate: 'commands.counter.realtime' }, wikiDescription: 'Displays information about the item counts using real-world time instead of Minecraft tick-based time to do rate calculations. Alias: **`./ct realtime`**, **`./ct realtime`**' }, + { usage: 'counter [color/all] ', description: { translate: 'commands.counter.mode' }, wikiDescription: 'Changes the mode of a channel while tracking hopper counters in the InfoDisplay. `count` displays a count of every item that passes through. `hr`, `min`, `sec` displays the number of items per hour, minute, and second respectively. Alias: **`./ct `**' }, + { usage: 'counter [color/all] reset', description: { translate: 'commands.counter.reset' }, wikiDescription: 'Resets the count of all channels to zero and restarts the timer. Alias: **`./ct [color|all] reset`**. This command can also be triggered with the vanilla command `/scriptevent canopy:counter [color|all] reset` (ie. in a command block).' }, + { usage: 'counter [color/all] remove', description: { translate: 'commands.counter.remove' }, wikiDescription: 'Removes all known hoppers in the specified channel or all channels. This will also reset the timer for that channel or all channels. Alias: **`./ct remove`**, **`./ct remove`** This command can also be triggered with the vanilla command `/scriptevent canopy:counter [color|all] remove` (ie. in a command block).' } ] }); diff --git a/Canopy [BP]/scripts/src/commands/data.js b/Canopy[BP]/scripts/src/commands/data.js similarity index 92% rename from Canopy [BP]/scripts/src/commands/data.js rename to Canopy[BP]/scripts/src/commands/data.js index 5174041f..4b5f9eb2 100644 --- a/Canopy [BP]/scripts/src/commands/data.js +++ b/Canopy[BP]/scripts/src/commands/data.js @@ -10,7 +10,8 @@ new VanillaCommand({ optionalParameters: [{ name: 'entity', type: CustomCommandParamType.EntitySelector }], permissionLevel: CommandPermissionLevel.Any, allowedSources: [PlayerCommandOrigin], - callback: dataCommand + callback: dataCommand, + wikiDescription: 'Displays information about blocks or entities. If the entity argument is specified, it will display information about that entity. Otherwise, information about the block or entity you are looking at will be displayed. This includes the its name, location, dimension, properties, states, components, component data, tags, and other info.' }); function dataCommand(origin, entity) { diff --git a/Canopy [BP]/scripts/src/commands/debugentity.js b/Canopy[BP]/scripts/src/commands/debugentity.js similarity index 95% rename from Canopy [BP]/scripts/src/commands/debugentity.js rename to Canopy[BP]/scripts/src/commands/debugentity.js index 4b20f834..4cc12889 100644 --- a/Canopy [BP]/scripts/src/commands/debugentity.js +++ b/Canopy[BP]/scripts/src/commands/debugentity.js @@ -21,7 +21,8 @@ new VanillaCommand({ {name: 'canopy:debugableProperty', type: CustomCommandParamType.Enum} ], permissionLevel: CommandPermissionLevel.GameDirectors, - callback: debugEntityCommand + callback: debugEntityCommand, + wikiDescription: "Overlays debug information on selected entities. Not available in realms version." }); function debugEntityCommand(origin, entities, addOrRemove, property) { diff --git a/Canopy [BP]/scripts/src/commands/distance.js b/Canopy[BP]/scripts/src/commands/distance.js similarity index 86% rename from Canopy [BP]/scripts/src/commands/distance.js rename to Canopy[BP]/scripts/src/commands/distance.js index df34adfe..17ce56da 100644 --- a/Canopy [BP]/scripts/src/commands/distance.js +++ b/Canopy[BP]/scripts/src/commands/distance.js @@ -20,10 +20,10 @@ const cmd = new Command({ ], callback: distanceCommand, helpEntries: [ - { usage: `distance target`, description: { translate: 'commands.distance.target' } }, - { usage: `distance from to [x y z]`, description: { translate: 'commands.distance.fromto' } }, - { usage: `distance from [x y z]`, description: { translate: 'commands.distance.from' } }, - { usage: `distance to [x y z]`, description: { translate: 'commands.distance.to' } } + { usage: `distance target`, description: { translate: 'commands.distance.target' }, wikiDescription: 'Calculates the distance in blocks between your head and the block or entity you are looking at down to three decimal places. Note that entity positions are at their foot. Alias: **`./d target`**' }, + { usage: `distance from to [x y z]`, description: { translate: 'commands.distance.fromto' }, wikiDescription: 'Calculates the distance in blocks between the two points. The `to` coordinates can be omitted to use your player\'s position. Alias: **`./d from to [x y z]`**' }, + { usage: `distance from [x y z]`, description: { translate: 'commands.distance.from' }, wikiDescription: 'Saves a location to calculate distance to later. The coordinates can be omitted to use your player\'s position. Alias: **`./d from [x y z]`**' }, + { usage: `distance to [x y z]`, description: { translate: 'commands.distance.to' }, wikiDescription: 'Calculates the distance in blocks between the saved location and the specified coordinates. The coordinates can be omitted to use your player\'s position. Alias: **`./d to [x y z]`**' } ] }); diff --git a/Canopy [BP]/scripts/src/commands/entitydensity.js b/Canopy[BP]/scripts/src/commands/entitydensity.js similarity index 89% rename from Canopy [BP]/scripts/src/commands/entitydensity.js rename to Canopy[BP]/scripts/src/commands/entitydensity.js index 047d5710..96e47cb9 100644 --- a/Canopy [BP]/scripts/src/commands/entitydensity.js +++ b/Canopy[BP]/scripts/src/commands/entitydensity.js @@ -21,7 +21,8 @@ new VanillaCommand({ optionalParameters: [{name: 'canopy:dimension', type: CustomCommandParamType.Enum}], permissionLevel: CommandPermissionLevel.Any, allowedSources: [PlayerCommandOrigin], - callback: entityDensityCommand + callback: entityDensityCommand, + wikiDescription: 'Displays the entity count for each dimension and identifies dense areas of entities in the specified dimension. The dimension argument can be omitted to use your current dimension. Valid dimension names include `overworld`, `nether`, `end`, `the_end`, `o`, `n`, and, `e`. Recommended grid sizes: 100-512 or more.' }); function entityDensityCommand(origin, gridSize, dimension) { diff --git a/Canopy [BP]/scripts/src/commands/gamemode.js b/Canopy[BP]/scripts/src/commands/gamemode.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/gamemode.js rename to Canopy[BP]/scripts/src/commands/gamemode.js diff --git a/Canopy [BP]/scripts/src/commands/generator.js b/Canopy[BP]/scripts/src/commands/generator.js similarity index 70% rename from Canopy [BP]/scripts/src/commands/generator.js rename to Canopy[BP]/scripts/src/commands/generator.js index d3541de1..9a25e2a6 100644 --- a/Canopy [BP]/scripts/src/commands/generator.js +++ b/Canopy[BP]/scripts/src/commands/generator.js @@ -6,6 +6,7 @@ new BooleanRule({ category: 'Rules', identifier: 'hopperGenerators', description: { translate: 'rules.hopperGenerators' }, + wikiDescription: 'Enables/disables the generator command and hopper generator functionality. Disabling this rule also resets all generators.', onEnableCallback: () => generatorChannels.enable(), onDisableCallback: () => generatorChannels.disable() }); @@ -21,10 +22,11 @@ const cmd = new Command({ callback: generatorCommand, contingentRules: ['hopperGenerators'], helpEntries: [ - { usage: 'generator [color/all]', description: { translate: 'commands.generator.query' } }, - { usage: 'generator [color/all] realtime', description: { translate: 'commands.generator.realtime' } }, - { usage: 'generator [color/all] reset', description: { translate: 'commands.generator.reset' } }, - { usage: 'generator [color/all remove', description: { translate: 'commands.counter.remove' } } + { usage: 'generator', description: { translate: 'commands.generator.query.all' }, wikiDescription: 'Displays information about the item counts in each channel. This includes metrics like the total items and items per hour, and the same divided up into individual item types. Alias: **`./gt`**' }, + { usage: 'generator [color/all]', description: { translate: 'commands.generator.query' }, wikiDescription: 'Does the same as `./generator`, but displays info for only one channel. Using the `all` keyword has exactly the same behavior as `./generator`. Alias: **`./gt `** This command can also be triggered with the vanilla command `/scriptevent canopy:generator [color]` (ie. in a command block).' }, + { usage: 'generator [color/all] realtime', description: { translate: 'commands.generator.realtime' }, wikiDescription: 'Displays information about the item counts using real-world time instead of Minecraft tick-based time to do rate calculations. Alias: **`./gt realtime`**, **`./gt realtime`**' }, + { usage: 'generator [color/all] reset', description: { translate: 'commands.generator.reset' }, wikiDescription: 'Resets the count of all channels to zero and restarts the timer. Alias: **`./gt [color|all] reset`**. This command can also be triggered with the vanilla command `/scriptevent canopy:generator [color|all] reset` (ie. in a command block).' }, + { usage: 'generator [color/all remove', description: { translate: 'commands.counter.remove' }, wikiDescription: 'Removes all known hoppers in the specified channel or all channels. This will also reset the timer for that channel or all channels. Alias: **`./gt remove`**, **`./gt remove`** This command can also be triggered with the vanilla command `/scriptevent canopy:generator [color|all] remove` (ie. in a command block).' } ] }); diff --git a/Canopy [BP]/scripts/src/commands/health.js b/Canopy[BP]/scripts/src/commands/health.js similarity index 78% rename from Canopy [BP]/scripts/src/commands/health.js rename to Canopy[BP]/scripts/src/commands/health.js index 4641cd41..1d2c10e2 100644 --- a/Canopy [BP]/scripts/src/commands/health.js +++ b/Canopy[BP]/scripts/src/commands/health.js @@ -7,7 +7,8 @@ new VanillaCommand({ name: 'canopy:health', description: 'commands.health', permissionLevel: CommandPermissionLevel.Any, - callback: healthCommand + callback: healthCommand, + wikiDescription: 'Profiles the server\'s tick speed for five seconds, displaying the average ticks per second (TPS) and the milliseconds per tick (MSPT). Also shows the lowest and highest values of each over that time. Additionally, this command also displays the entity count for each dimension.' }); export function healthCommand(origin) { diff --git a/Canopy [BP]/scripts/src/commands/help.js b/Canopy[BP]/scripts/src/commands/help.js similarity index 87% rename from Canopy [BP]/scripts/src/commands/help.js rename to Canopy[BP]/scripts/src/commands/help.js index 099ff3ab..e3f885fb 100644 --- a/Canopy [BP]/scripts/src/commands/help.js +++ b/Canopy[BP]/scripts/src/commands/help.js @@ -9,7 +9,9 @@ new Command({ args: [ { type: 'string|integer', name: 'pageName' } ], - callback: helpCommand + callback: helpCommand, + wikiDescription: `Displays every command's and every rule's usage and a short description. Default page names include "InfoDisplay", "Rules", and several numbered pages of commands. There is also a page for any loaded extensions. Specifying a search term will display any command or rule that includes that term.` + + "\n\nThis only includes `./` commands. To see a list of `/` commands, use the vanilla `/help` command." }); function helpCommand(sender, args) { diff --git a/Canopy [BP]/scripts/src/commands/hss.js b/Canopy[BP]/scripts/src/commands/hss.js similarity index 82% rename from Canopy [BP]/scripts/src/commands/hss.js rename to Canopy[BP]/scripts/src/commands/hss.js index c9c9b6a6..c9e28d90 100644 --- a/Canopy [BP]/scripts/src/commands/hss.js +++ b/Canopy[BP]/scripts/src/commands/hss.js @@ -22,6 +22,20 @@ export class HSS extends VanillaCommand { permissionLevel: CommandPermissionLevel.GameDirectors, allowedSources: [PlayerCommandOrigin, EntityCommandOrigin, BlockCommandOrigin], callback: (origin, ...args) => this.hssCommand(origin, ...args), + subCommandWikiDescription: { + 'calculate': { + description: 'Finds hardcoded spawn spots (HSSes) near your current position. Stand inside a structure and run this command. Does not work on fortresses.', + params: [] + }, + 'fortress': { + description: 'Finds HSSes using the mob spawning algorithm. May take a few minutes. Spawns are mocked during the search so no mobs will spawn.', + params: [] + }, + 'stop': { + description: 'Clears all HSS visualizations.', + params: [] + } + } }); } diff --git a/Canopy [BP]/scripts/src/commands/info.js b/Canopy[BP]/scripts/src/commands/info.js similarity index 94% rename from Canopy [BP]/scripts/src/commands/info.js rename to Canopy[BP]/scripts/src/commands/info.js index d36a2425..a41ac802 100644 --- a/Canopy [BP]/scripts/src/commands/info.js +++ b/Canopy[BP]/scripts/src/commands/info.js @@ -12,7 +12,7 @@ const cmd = new Command({ ], callback: infoCommand, helpEntries: [ - { usage: 'info menu', description: { translate: 'commands.info.menu' } }, + { usage: 'info menu', description: { translate: 'commands.info.menu' }, wikiDescription: 'Displays a menu with toggles for every InfoDisplay rule. Flip the switches for the ones you want a hit the submit button at the bottom to save your changes.' }, { usage: 'info [true/false]', description: { translate: 'commands.info.single' } }, { usage: 'info <[rule1,rule2,...]> [true/false]', description: { translate: 'commands.info.multiple' } }, { usage: 'info all [true/false]', description: { translate: 'commands.info.all' } } diff --git a/Canopy [BP]/scripts/src/commands/jump.js b/Canopy[BP]/scripts/src/commands/jump.js similarity index 84% rename from Canopy [BP]/scripts/src/commands/jump.js rename to Canopy[BP]/scripts/src/commands/jump.js index 286d3d0c..85a133b3 100644 --- a/Canopy [BP]/scripts/src/commands/jump.js +++ b/Canopy[BP]/scripts/src/commands/jump.js @@ -4,7 +4,8 @@ import { CommandPermissionLevel, GameMode, system } from "@minecraft/server"; new BooleanRule({ category: 'Rules', identifier: 'commandJumpSurvival', - description: { translate: 'rules.commandJumpSurvival' } + description: { translate: 'rules.commandJumpSurvival' }, + wikiDescription: 'Determines whether the `/jump` command can be used while in Survival mode.' }); new VanillaCommand({ @@ -14,7 +15,8 @@ new VanillaCommand({ allowedSources: [PlayerCommandOrigin, EntityCommandOrigin], cheatsRequired: true, callback: jumpCommand, - aliases: ['canopy:j'] + aliases: ['canopy:j'], + wikiDescription: 'Teleports you to the block you are currently looking at with a maximum range of 64 chunks. Alias: **`/j`**' }); function jumpCommand(origin) { diff --git a/Canopy [BP]/scripts/src/commands/lifetimequery.js b/Canopy[BP]/scripts/src/commands/lifetimequery.js similarity index 90% rename from Canopy [BP]/scripts/src/commands/lifetimequery.js rename to Canopy[BP]/scripts/src/commands/lifetimequery.js index 309290ff..9db92166 100644 --- a/Canopy [BP]/scripts/src/commands/lifetimequery.js +++ b/Canopy[BP]/scripts/src/commands/lifetimequery.js @@ -25,6 +25,7 @@ export class LifetimeQuery extends VanillaCommand { permissionLevel: CommandPermissionLevel.GameDirectors, allowedSources: [PlayerCommandOrigin, ServerCommandOrigin], callback: (origin, ...args) => this.lifetimeQueryCommand(origin, ...args), + wikiDescription: 'Queries lifetime tracking data for the specified entity type. You can choose to see lifetime, spawning, removal statistics, or a summary of all three. Adding the `useRealtime` argument will calculate rates based on real-world time instead of tick-based time.' }); } diff --git a/Canopy [BP]/scripts/src/commands/lifetimequeryitem.js b/Canopy[BP]/scripts/src/commands/lifetimequeryitem.js similarity index 82% rename from Canopy [BP]/scripts/src/commands/lifetimequeryitem.js rename to Canopy[BP]/scripts/src/commands/lifetimequeryitem.js index 6ecd6edc..520c554e 100644 --- a/Canopy [BP]/scripts/src/commands/lifetimequeryitem.js +++ b/Canopy[BP]/scripts/src/commands/lifetimequeryitem.js @@ -17,6 +17,7 @@ export class LifetimeQueryItem extends VanillaCommand { permissionLevel: CommandPermissionLevel.GameDirectors, allowedSources: [PlayerCommandOrigin, ServerCommandOrigin], callback: (origin, ...args) => this.lifetimeQueryItemCommand(origin, ...args), + wikiDescription: "Queries lifetime tracking data for the specified item entity type. You can choose to see lifetime, spawning, removal statistics, or a summary of all three. Adding the `useRealtime` argument will calculate rates based on real-world time instead of tick-based time." }); } diff --git a/Canopy [BP]/scripts/src/commands/lifetimetracking.js b/Canopy[BP]/scripts/src/commands/lifetimetracking.js similarity index 99% rename from Canopy [BP]/scripts/src/commands/lifetimetracking.js rename to Canopy[BP]/scripts/src/commands/lifetimetracking.js index 1f5f1897..f24927a4 100644 --- a/Canopy [BP]/scripts/src/commands/lifetimetracking.js +++ b/Canopy[BP]/scripts/src/commands/lifetimetracking.js @@ -19,7 +19,7 @@ export class LifetimeTracking extends VanillaCommand { optionalParameters: [{ name: 'canopy:lifetimeTrackingActions', type: CustomCommandParamType.Enum }], permissionLevel: CommandPermissionLevel.GameDirectors, allowedSources: [PlayerCommandOrigin, BlockCommandOrigin, EntityCommandOrigin, ServerCommandOrigin], - callback: (origin, ...args) => this.lifetimeTrackingCommand(origin, ...args), + callback: (origin, ...args) => this.lifetimeTrackingCommand(origin, ...args) }); } diff --git a/Canopy [BP]/scripts/src/commands/log.js b/Canopy[BP]/scripts/src/commands/log.js similarity index 89% rename from Canopy [BP]/scripts/src/commands/log.js rename to Canopy[BP]/scripts/src/commands/log.js index 5c295734..41928374 100644 --- a/Canopy [BP]/scripts/src/commands/log.js +++ b/Canopy[BP]/scripts/src/commands/log.js @@ -22,6 +22,7 @@ new VanillaCommand({ permissionLevel: CommandPermissionLevel.Any, allowedSources: [PlayerCommandOrigin], callback: logCommand, + wikiDescription: 'Logs the location of the specified entity type in chat. The precision argument is optional and sets the number of decimal places to truncate the entity location at. There is a maximum of 15 and the default is 3.' }); export function logCommand(origin, type, precision) { diff --git a/Canopy [BP]/scripts/src/commands/loop.js b/Canopy[BP]/scripts/src/commands/loop.js similarity index 88% rename from Canopy [BP]/scripts/src/commands/loop.js rename to Canopy[BP]/scripts/src/commands/loop.js index 2209f9ef..fc56a102 100644 --- a/Canopy [BP]/scripts/src/commands/loop.js +++ b/Canopy[BP]/scripts/src/commands/loop.js @@ -9,7 +9,8 @@ new VanillaCommand({ { name: 'command', type: CustomCommandParamType.String } ], permissionLevel: CommandPermissionLevel.GameDirectors, - callback: loopCommand + callback: loopCommand, + wikiDescription: 'Runs the specified command a certain number of times in a single tick. Note that the looped command must be encased in quotes.' }); function loopCommand(origin, times, command) { diff --git a/Canopy [BP]/scripts/src/commands/peek.js b/Canopy[BP]/scripts/src/commands/peek.js similarity index 89% rename from Canopy [BP]/scripts/src/commands/peek.js rename to Canopy[BP]/scripts/src/commands/peek.js index 7cb76e02..a1065a6a 100644 --- a/Canopy [BP]/scripts/src/commands/peek.js +++ b/Canopy[BP]/scripts/src/commands/peek.js @@ -13,7 +13,8 @@ new VanillaCommand({ permissionLevel: CommandPermissionLevel.Any, allowedSources: [PlayerCommandOrigin], contingentRules: ['allowPeekInventory'], - callback: peekCommand + callback: peekCommand, + wikiDescription: 'Show the inventory of the block or entity you\'re looking at up to 64 chunks away. Using the "search term" argument will highlight any items that include your search term in your InfoDisplay.' }); function peekCommand(origin, itemQuery) { diff --git a/Canopy [BP]/scripts/src/commands/pos.js b/Canopy[BP]/scripts/src/commands/pos.js similarity index 89% rename from Canopy [BP]/scripts/src/commands/pos.js rename to Canopy[BP]/scripts/src/commands/pos.js index 9317c13d..8f438f5b 100644 --- a/Canopy [BP]/scripts/src/commands/pos.js +++ b/Canopy[BP]/scripts/src/commands/pos.js @@ -7,7 +7,8 @@ const NETHER_SCALE_FACTOR = 8; new BooleanRule({ category: 'Rules', identifier: 'commandPosOthers', - description: { translate: 'rules.commandPosOthers' } + description: { translate: 'rules.commandPosOthers' }, + wikiDescription: 'Determines whether non-operators can use `/pos` on players other than themselves.' }); new VanillaCommand({ @@ -15,7 +16,8 @@ new VanillaCommand({ description: 'commands.pos', optionalParameters: [{ name: 'player', type: CustomCommandParamType.PlayerSelector }], permissionLevel: CommandPermissionLevel.Any, - callback: posCommand + callback: posCommand, + wikiDescription: 'Displays your current position, or the position of another player. Shows dimension and relative nether/overworld position as well.' }); function posCommand(origin, player) { diff --git a/Canopy [BP]/scripts/src/commands/retest.js b/Canopy[BP]/scripts/src/commands/retest.js similarity index 79% rename from Canopy [BP]/scripts/src/commands/retest.js rename to Canopy[BP]/scripts/src/commands/retest.js index 5c216475..c4fe3f9e 100644 --- a/Canopy [BP]/scripts/src/commands/retest.js +++ b/Canopy[BP]/scripts/src/commands/retest.js @@ -8,7 +8,8 @@ new VanillaCommand({ name: 'canopy:retest', description: 'commands.retest', permissionLevel: CommandPermissionLevel.Any, - callback: retestCommand + callback: retestCommand, + wikiDescription: 'Resets spawn tracking, hopper counters, and hopper generators. This is great if you want to restart a test for a machine or farm!' }); function retestCommand() { diff --git a/Canopy [BP]/scripts/src/commands/scriptevents/counter.js b/Canopy[BP]/scripts/src/commands/scriptevents/counter.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/scriptevents/counter.js rename to Canopy[BP]/scripts/src/commands/scriptevents/counter.js diff --git a/Canopy [BP]/scripts/src/commands/scriptevents/generator.js b/Canopy[BP]/scripts/src/commands/scriptevents/generator.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/scriptevents/generator.js rename to Canopy[BP]/scripts/src/commands/scriptevents/generator.js diff --git a/Canopy [BP]/scripts/src/commands/scriptevents/spawn.js b/Canopy[BP]/scripts/src/commands/scriptevents/spawn.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/scriptevents/spawn.js rename to Canopy[BP]/scripts/src/commands/scriptevents/spawn.js diff --git a/Canopy [BP]/scripts/src/commands/scriptevents/tick.js b/Canopy[BP]/scripts/src/commands/scriptevents/tick.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/scriptevents/tick.js rename to Canopy[BP]/scripts/src/commands/scriptevents/tick.js diff --git a/Canopy [BP]/scripts/src/commands/simmap.js b/Canopy[BP]/scripts/src/commands/simmap.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/simmap.js rename to Canopy[BP]/scripts/src/commands/simmap.js diff --git a/Canopy [BP]/scripts/src/commands/sit.js b/Canopy[BP]/scripts/src/commands/sit.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/sit.js rename to Canopy[BP]/scripts/src/commands/sit.js diff --git a/Canopy [BP]/scripts/src/commands/spawn.js b/Canopy[BP]/scripts/src/commands/spawn.js similarity index 89% rename from Canopy [BP]/scripts/src/commands/spawn.js rename to Canopy[BP]/scripts/src/commands/spawn.js index 4c737a0d..8fcafc31 100644 --- a/Canopy [BP]/scripts/src/commands/spawn.js +++ b/Canopy[BP]/scripts/src/commands/spawn.js @@ -23,10 +23,10 @@ const cmd = new Command({ { usage: 'spawn entities', description: { translate: 'commands.spawn.entities' } }, { usage: 'spawn recent [mobName]', description: { translate: 'commands.spawn.recent' } }, { usage: 'spawn tracking start [x1 y1 z1] [x2 y2 z2]', description: { translate: 'commands.spawn.tracking.start' } }, - { usage: 'spawn tracking [x1 y1 z1] [x2 y2 z2]', description: { translate: 'commands.spawn.tracking.mob' } }, - { usage: 'spawn tracking', description: { translate: 'commands.spawn.tracking.query' } }, - { usage: 'spawn tracking stop', description: { translate: 'commands.spawn.tracking.stop' } }, - { usage: 'spawn mocking ', description: { translate: 'commands.spawn.mocking' } } + { usage: 'spawn tracking [x1 y1 z1] [x2 y2 z2]', description: { translate: 'commands.spawn.tracking.mob' }, wikiDescription: 'Starts spawn tracking for a specific mob. Specify coordinates to track a specific area. Run this command again with a new mob name to track multiple mobs types at once.' }, + { usage: 'spawn tracking', description: { translate: 'commands.spawn.tracking.query' }, wikiDescription: 'Displays statistics about mob spawning since spawn tracking started. Mob categories are based on population control.' }, + { usage: 'spawn tracking stop', description: { translate: 'commands.spawn.tracking.stop' }, wikiDescription: 'Displays statistics about mob spawning since spawn tracking started and then stops spawn tracking.' }, + { usage: 'spawn mocking ', description: { translate: 'commands.spawn.mocking' }, wikiDescription: 'Allows the spawning algorithm to continue running while no mobs spawn. Useful for getting an upper bound on your farm\'s rates while tracking spawns. Requires OP.' } ] }); diff --git a/Canopy [BP]/scripts/src/commands/tick.js b/Canopy[BP]/scripts/src/commands/tick.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/tick.js rename to Canopy[BP]/scripts/src/commands/tick.js diff --git a/Canopy [BP]/scripts/src/commands/trackevent.js b/Canopy[BP]/scripts/src/commands/trackevent.js similarity index 84% rename from Canopy [BP]/scripts/src/commands/trackevent.js rename to Canopy[BP]/scripts/src/commands/trackevent.js index 7d58a2e4..69b94088 100644 --- a/Canopy [BP]/scripts/src/commands/trackevent.js +++ b/Canopy[BP]/scripts/src/commands/trackevent.js @@ -27,7 +27,8 @@ new VanillaCommand({ mandatoryParameters: [{ name: 'canopy:eventName', type: CustomCommandParamType.Enum }], optionalParameters: [{ name: 'canopy:eventType', type: CustomCommandParamType.Enum }], permissionLevel: CommandPermissionLevel.Any, - callback: trackCommand + callback: trackCommand, + wikiDescription: "Toggles tracking for the specified event. Each time the event occurs, the tracker will increment. You can view the trackers' count in realtime in the InfoDisplay by enabling the `eventTrackers` InfoDisplay rule. If you need to find the names of some events, use these links: [beforeEvents](https://learn.microsoft.com/en-us/minecraft/creator/scriptapi/minecraft/server/worldbeforeevents?view=minecraft-bedrock-experimental), [afterEvents](https://learn.microsoft.com/en-us/minecraft/creator/scriptapi/minecraft/server/worldafterevents?view=minecraft-bedrock-experimental). If the last argument is not specified, afterEvent is assumed." }); const trackers = { diff --git a/Canopy [BP]/scripts/src/commands/velocity.js b/Canopy[BP]/scripts/src/commands/velocity.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/velocity.js rename to Canopy[BP]/scripts/src/commands/velocity.js diff --git a/Canopy [BP]/scripts/src/commands/warp.js b/Canopy[BP]/scripts/src/commands/warp.js similarity index 86% rename from Canopy [BP]/scripts/src/commands/warp.js rename to Canopy[BP]/scripts/src/commands/warp.js index 4ff2cffa..529e28b4 100644 --- a/Canopy [BP]/scripts/src/commands/warp.js +++ b/Canopy[BP]/scripts/src/commands/warp.js @@ -5,14 +5,16 @@ import Warps from '../classes/Warps'; new BooleanRule({ category: 'Rules', identifier: 'commandWarp', - description: { translate: 'rules.commandWarp' } + description: { translate: 'rules.commandWarp' }, + wikiDescription: 'Determines whether the `./warp` and `./warps` commands can be used.' }); new BooleanRule({ category: 'Rules', identifier: 'commandWarpSurvival', description: { translate: 'rules.commandWarpSurvival' }, - contingentRules: ['commandWarp'] + contingentRules: ['commandWarp'], + wikiDescription: 'Determines whether the `./warp` command can be used while in Survival mode.' }); const cmd = new Command({ @@ -26,8 +28,8 @@ const cmd = new Command({ callback: warpActionCommand, contingentRules: ['commandWarp'], helpEntries: [ - { usage: 'warp ', description: { translate: 'commands.warp.edit' } }, - { usage: 'warp ', description: { translate: 'commands.warp.tp' } } + { usage: 'warp ', description: { translate: 'commands.warp.edit' }, wikiDescription: 'Adds or removes a warp. Alias: **`./w `**' }, + { usage: 'warp ', description: { translate: 'commands.warp.tp' }, wikiDescription: 'Teleports you to a warp. Alias: **`./w `**' } ] }); diff --git a/Canopy [BP]/scripts/src/events/EffectRemoveEvent.js b/Canopy[BP]/scripts/src/events/EffectRemoveEvent.js similarity index 100% rename from Canopy [BP]/scripts/src/events/EffectRemoveEvent.js rename to Canopy[BP]/scripts/src/events/EffectRemoveEvent.js diff --git a/Canopy [BP]/scripts/src/events/Event.js b/Canopy[BP]/scripts/src/events/Event.js similarity index 100% rename from Canopy [BP]/scripts/src/events/Event.js rename to Canopy[BP]/scripts/src/events/Event.js diff --git a/Canopy [BP]/scripts/src/events/PlayerChangeSubChunkEvent.js b/Canopy[BP]/scripts/src/events/PlayerChangeSubChunkEvent.js similarity index 92% rename from Canopy [BP]/scripts/src/events/PlayerChangeSubChunkEvent.js rename to Canopy[BP]/scripts/src/events/PlayerChangeSubChunkEvent.js index 79e2d2c6..0cdd6052 100644 --- a/Canopy [BP]/scripts/src/events/PlayerChangeSubChunkEvent.js +++ b/Canopy[BP]/scripts/src/events/PlayerChangeSubChunkEvent.js @@ -41,8 +41,9 @@ class PlayerChangeSubChunkEvent extends Event { } isInSameSubChunk(currentLocation, lastLocation) { - const currentChunkVec = new Vector(currentLocation.x, currentLocation.y, currentLocation.z).divide(16).floor(); - const lastChunkVec = new Vector(lastLocation.x, lastLocation.y, lastLocation.z).divide(16).floor(); + const CHUNK_SIZE = 16; + const currentChunkVec = new Vector(currentLocation.x, currentLocation.y, currentLocation.z).scale(1/CHUNK_SIZE).floor(); + const lastChunkVec = new Vector(lastLocation.x, lastLocation.y, lastLocation.z).scale(1/CHUNK_SIZE).floor(); return currentChunkVec.x === lastChunkVec.x && currentChunkVec.y === lastChunkVec.y && currentChunkVec.z === lastChunkVec.z; diff --git a/Canopy [BP]/scripts/src/events/PlayerStartSneakEvent.js b/Canopy[BP]/scripts/src/events/PlayerStartSneakEvent.js similarity index 100% rename from Canopy [BP]/scripts/src/events/PlayerStartSneakEvent.js rename to Canopy[BP]/scripts/src/events/PlayerStartSneakEvent.js diff --git a/Canopy [BP]/scripts/src/events/PlayerTameEntityEvent.js b/Canopy[BP]/scripts/src/events/PlayerTameEntityEvent.js similarity index 100% rename from Canopy [BP]/scripts/src/events/PlayerTameEntityEvent.js rename to Canopy[BP]/scripts/src/events/PlayerTameEntityEvent.js diff --git a/Canopy [BP]/scripts/src/events/SpawnEggSpawnEntityEvent.js b/Canopy[BP]/scripts/src/events/SpawnEggSpawnEntityEvent.js similarity index 100% rename from Canopy [BP]/scripts/src/events/SpawnEggSpawnEntityEvent.js rename to Canopy[BP]/scripts/src/events/SpawnEggSpawnEntityEvent.js diff --git a/Canopy [BP]/scripts/src/onReload.js b/Canopy[BP]/scripts/src/onReload.js similarity index 100% rename from Canopy [BP]/scripts/src/onReload.js rename to Canopy[BP]/scripts/src/onReload.js diff --git a/Canopy [BP]/scripts/src/onStart.js b/Canopy[BP]/scripts/src/onStart.js similarity index 100% rename from Canopy [BP]/scripts/src/onStart.js rename to Canopy[BP]/scripts/src/onStart.js diff --git a/Canopy [BP]/scripts/src/rules/allowBubbleColumnPlacement.js b/Canopy[BP]/scripts/src/rules/allowBubbleColumnPlacement.js similarity index 89% rename from Canopy [BP]/scripts/src/rules/allowBubbleColumnPlacement.js rename to Canopy[BP]/scripts/src/rules/allowBubbleColumnPlacement.js index ad78f430..6cce8233 100644 --- a/Canopy [BP]/scripts/src/rules/allowBubbleColumnPlacement.js +++ b/Canopy[BP]/scripts/src/rules/allowBubbleColumnPlacement.js @@ -5,6 +5,7 @@ class AllowBubbleColumnPlacement extends BooleanRule { constructor() { super(GlobalRule.morphOptions({ identifier: 'allowBubbleColumnPlacement', + wikiDescription: 'Removes the vanilla restriction on placing bubble column items. A soul sand or magma block underneath is still required.', onEnableCallback: () => this.subscribeToEvent(), onDisableCallback: () => this.unsubscribeFromEvent() })); diff --git a/Canopy [BP]/scripts/src/rules/allowPeekInventory.js b/Canopy[BP]/scripts/src/rules/allowPeekInventory.js similarity index 86% rename from Canopy [BP]/scripts/src/rules/allowPeekInventory.js rename to Canopy[BP]/scripts/src/rules/allowPeekInventory.js index aa70e4c3..7b69564d 100644 --- a/Canopy [BP]/scripts/src/rules/allowPeekInventory.js +++ b/Canopy[BP]/scripts/src/rules/allowPeekInventory.js @@ -8,6 +8,7 @@ class AllowPeekInventory extends BooleanRule { constructor() { super(GlobalRule.morphOptions({ identifier: 'allowPeekInventory', + wikiDescription: 'Enables all peek inventory functionality. This rule must be enabled to use `peekInventory` in your InfoDisplay. It also enables the `/peek` command and the ability to peek inside containers by holding a spyglass.', onEnableCallback: () => this.subscribeToEvents(), onDisableCallback: () => this.unsubscribeFromEvents() })); diff --git a/Canopy [BP]/scripts/src/rules/armorStandRespawning.js b/Canopy[BP]/scripts/src/rules/armorStandRespawning.js similarity index 83% rename from Canopy [BP]/scripts/src/rules/armorStandRespawning.js rename to Canopy[BP]/scripts/src/rules/armorStandRespawning.js index fd8b8991..1b2e8384 100644 --- a/Canopy [BP]/scripts/src/rules/armorStandRespawning.js +++ b/Canopy[BP]/scripts/src/rules/armorStandRespawning.js @@ -4,7 +4,8 @@ import { world } from "@minecraft/server"; new BooleanRule({ category: 'Rules', identifier: 'armorStandRespawning', - description: { translate: 'rules.armorStandRespawning' } + description: { translate: 'rules.armorStandRespawning' }, + wikiDescription: 'Enables/disables armor stands dropping their items and respawning when broken by a projectile. This rule is useful for sorting items with armor stands.' }); world.afterEvents.projectileHitEntity.subscribe((event) => { diff --git a/Canopy [BP]/scripts/src/rules/autoItemPickup.js b/Canopy[BP]/scripts/src/rules/autoItemPickup.js similarity index 95% rename from Canopy [BP]/scripts/src/rules/autoItemPickup.js rename to Canopy[BP]/scripts/src/rules/autoItemPickup.js index 8714e238..484bfb9e 100644 --- a/Canopy [BP]/scripts/src/rules/autoItemPickup.js +++ b/Canopy[BP]/scripts/src/rules/autoItemPickup.js @@ -5,7 +5,8 @@ import { calcDistance } from "../../include/utils"; new BooleanRule({ category: 'Rules', identifier: 'autoItemPickup', - description: { translate: 'rules.autoItemPickup' } + description: { translate: 'rules.autoItemPickup' }, + wikiDescription: 'Enables/disables the automatic pickup of items that drop when you break a block.' }); new BooleanRule({ diff --git a/Canopy [BP]/scripts/src/rules/cauldronConcreteConversion.js b/Canopy[BP]/scripts/src/rules/cauldronConcreteConversion.js similarity index 96% rename from Canopy [BP]/scripts/src/rules/cauldronConcreteConversion.js rename to Canopy[BP]/scripts/src/rules/cauldronConcreteConversion.js index e27e1c1f..0af41b44 100644 --- a/Canopy [BP]/scripts/src/rules/cauldronConcreteConversion.js +++ b/Canopy[BP]/scripts/src/rules/cauldronConcreteConversion.js @@ -11,6 +11,7 @@ new BooleanRule({ category: 'Rules', identifier: 'cauldronConcreteConversion', description: { translate: 'rules.cauldronConcreteConversion' }, + wikiDescription: 'Concrete powder items inside water cauldrons will convert to concrete after 7 seconds.', onEnableCallback: () => { runner = system.runInterval(onTick.bind(this)); world.afterEvents.entitySpawn.subscribe(onEntitySpawnBound); diff --git a/Canopy [BP]/scripts/src/rules/chunkBorders.js b/Canopy[BP]/scripts/src/rules/chunkBorders.js similarity index 91% rename from Canopy [BP]/scripts/src/rules/chunkBorders.js rename to Canopy[BP]/scripts/src/rules/chunkBorders.js index 01ae6f20..ce809754 100644 --- a/Canopy [BP]/scripts/src/rules/chunkBorders.js +++ b/Canopy[BP]/scripts/src/rules/chunkBorders.js @@ -8,6 +8,7 @@ export class ChunkBorders extends AbilityRule { constructor() { super({ identifier: 'chunkBorders', + wikiDescription: 'Enables chunk border visualization when an arrow is in the inventory slot below your offhand slot. Not available in the realms version.', onEnableCallback: () => { playerChangeSubChunkEvent.subscribe(this.onPlayerChangeSubChunkBound); }, diff --git a/Canopy [BP]/scripts/src/rules/collisionBoxes.js b/Canopy[BP]/scripts/src/rules/collisionBoxes.js similarity index 94% rename from Canopy [BP]/scripts/src/rules/collisionBoxes.js rename to Canopy[BP]/scripts/src/rules/collisionBoxes.js index e9e8183e..9c358c02 100644 --- a/Canopy [BP]/scripts/src/rules/collisionBoxes.js +++ b/Canopy[BP]/scripts/src/rules/collisionBoxes.js @@ -10,6 +10,7 @@ export class CollisionBoxes extends AbilityRule { constructor() { super({ identifier: 'collisionBoxes', + wikiDescription: 'Enables entity collision box visualization when an arrow is in inventory slot 14 (the slot to the right of the top middle slot in your inventory).', onEnableCallback: () => {}, onDisableCallback: () => { this.stopAllRendering(); diff --git a/Canopy [BP]/scripts/src/rules/creativeHotbarSwitching.js b/Canopy[BP]/scripts/src/rules/creativeHotbarSwitching.js similarity index 91% rename from Canopy [BP]/scripts/src/rules/creativeHotbarSwitching.js rename to Canopy[BP]/scripts/src/rules/creativeHotbarSwitching.js index 4d613080..30a20257 100644 --- a/Canopy [BP]/scripts/src/rules/creativeHotbarSwitching.js +++ b/Canopy[BP]/scripts/src/rules/creativeHotbarSwitching.js @@ -10,6 +10,7 @@ class CreativeHotbarSwitching extends AbilityRule { constructor() { super({ identifier: 'creativeHotbarSwitching', + wikiDescription: 'Allows you to quickly switch between multiple hotbars. With an arrow in the top right of your inventory, sneak and change hotbar slots to cycle through your saved hotbars. For Creative mode use only.', onEnableCallback: () => { this.runner = system.runInterval(this.onTick.bind(this)); }, onDisableCallback: () => { if (this.runner !== void 0) diff --git a/Canopy [BP]/scripts/src/rules/creativeInstantTame.js b/Canopy[BP]/scripts/src/rules/creativeInstantTame.js similarity index 83% rename from Canopy [BP]/scripts/src/rules/creativeInstantTame.js rename to Canopy[BP]/scripts/src/rules/creativeInstantTame.js index 43b77357..df13b577 100644 --- a/Canopy [BP]/scripts/src/rules/creativeInstantTame.js +++ b/Canopy[BP]/scripts/src/rules/creativeInstantTame.js @@ -4,7 +4,8 @@ import { world, GameMode, EntityComponentTypes } from "@minecraft/server"; new BooleanRule({ category: 'Rules', identifier: 'creativeInstantTame', - description: { translate: 'rules.creativeInstantTame' } + description: { translate: 'rules.creativeInstantTame' }, + wikiDescription: 'Enables/disables the ability to tame animals instantly by feeding or mounting them in creative mode.' }); diff --git a/Canopy [BP]/scripts/src/rules/creativeNetherWaterPlacement.js b/Canopy[BP]/scripts/src/rules/creativeNetherWaterPlacement.js similarity index 95% rename from Canopy [BP]/scripts/src/rules/creativeNetherWaterPlacement.js rename to Canopy[BP]/scripts/src/rules/creativeNetherWaterPlacement.js index ca8f6b26..09360ba1 100644 --- a/Canopy [BP]/scripts/src/rules/creativeNetherWaterPlacement.js +++ b/Canopy[BP]/scripts/src/rules/creativeNetherWaterPlacement.js @@ -5,6 +5,7 @@ class CreativeNetherWaterPlacement extends BooleanRule { constructor() { super(GlobalRule.morphOptions({ identifier: 'creativeNetherWaterPlacement', + wikiDescription: 'Allows players in creative mode to place water in the Nether.', onEnableCallback: () => this.subscribeToEvent(), onDisableCallback: () => this.unsubscribeFromEvent() })); diff --git a/Canopy [BP]/scripts/src/rules/creativeNoTileDrops.js b/Canopy[BP]/scripts/src/rules/creativeNoTileDrops.js similarity index 81% rename from Canopy [BP]/scripts/src/rules/creativeNoTileDrops.js rename to Canopy[BP]/scripts/src/rules/creativeNoTileDrops.js index 27a312a2..1d93dfeb 100644 --- a/Canopy [BP]/scripts/src/rules/creativeNoTileDrops.js +++ b/Canopy[BP]/scripts/src/rules/creativeNoTileDrops.js @@ -7,7 +7,8 @@ const REMOVAL_DISTANCE = 2.5; new BooleanRule({ category: 'Rules', identifier: 'creativeNoTileDrops', - description: { translate: 'rules.creativeNoTileDrops' } + description: { translate: 'rules.creativeNoTileDrops' }, + wikiDescription: 'Enables/disables items dropping from blocks when breaking them in creative mode. Unlike the vanilla gamerule, this also suppresses drops from containers and only applies when you break them — not when they break in the world.' }); let brokenBlockEventsThisTick = []; diff --git a/Canopy [BP]/scripts/src/rules/creativeOneHitKill.js b/Canopy[BP]/scripts/src/rules/creativeOneHitKill.js similarity index 80% rename from Canopy [BP]/scripts/src/rules/creativeOneHitKill.js rename to Canopy[BP]/scripts/src/rules/creativeOneHitKill.js index 27799839..a9e81227 100644 --- a/Canopy [BP]/scripts/src/rules/creativeOneHitKill.js +++ b/Canopy[BP]/scripts/src/rules/creativeOneHitKill.js @@ -4,7 +4,8 @@ import { world, InputButton, ButtonState, GameMode } from "@minecraft/server"; new BooleanRule({ category: 'Rules', identifier: 'creativeOneHitKill', - description: { translate: 'rules.creativeOneHitKill' } + description: { translate: 'rules.creativeOneHitKill' }, + wikiDescription: 'Allows players in creative to kill entities in one hit. If the player is sneaking, all entities in a small radius will be killed. Does not affect items, xp orbs, or players.' }); world.afterEvents.entityHitEntity.subscribe((event) => { diff --git a/Canopy [BP]/scripts/src/rules/dupeTnt.js b/Canopy[BP]/scripts/src/rules/dupeTnt.js similarity index 88% rename from Canopy [BP]/scripts/src/rules/dupeTnt.js rename to Canopy[BP]/scripts/src/rules/dupeTnt.js index 654db077..27dae59e 100644 --- a/Canopy [BP]/scripts/src/rules/dupeTnt.js +++ b/Canopy[BP]/scripts/src/rules/dupeTnt.js @@ -4,7 +4,8 @@ import { system, world } from '@minecraft/server'; new BooleanRule({ category: 'Rules', identifier: 'dupeTnt', - description: { translate: 'rules.dupeTnt' } + description: { translate: 'rules.dupeTnt' }, + wikiDescription: 'Enables/disables TNT duping. To dupe a block of TNT, it must be moved by a piston while adjacent to a note block, then ignited.\n\nThe TNT will drop with normal priming momentum in the block below where it was ignited. Note that using this rule alongside `tntPrimeMomentum` will cause a 1-gametick slowdown before the TNT drops.\n\n![Dupe TNT Example](./exampleAssets/dupeTnt.png)' }); export let spawnedEntitiesThisTick = []; diff --git a/Canopy [BP]/scripts/src/rules/durabilityNotifier.js b/Canopy[BP]/scripts/src/rules/durabilityNotifier.js similarity index 91% rename from Canopy [BP]/scripts/src/rules/durabilityNotifier.js rename to Canopy[BP]/scripts/src/rules/durabilityNotifier.js index a30c6203..c5ca3fae 100644 --- a/Canopy [BP]/scripts/src/rules/durabilityNotifier.js +++ b/Canopy[BP]/scripts/src/rules/durabilityNotifier.js @@ -7,7 +7,8 @@ const ADDITIONAL_DURABILITIES = [20]; const rule = new BooleanRule({ category: 'Rules', identifier: 'durabilityNotifier', - description: { translate: 'rules.durabilityNotifier', with: [ACTIVE_DURABILITY.toString()] } + description: { translate: 'rules.durabilityNotifier', with: [ACTIVE_DURABILITY.toString()] }, + wikiDescription: 'Enables a sound and tooltip when your tool has three hits left before breaking. An additional tooltip appears when your tool has 20 durability remaining.' }); world.afterEvents.playerBreakBlock.subscribe((event) => durabilityNotifier(event.player, event.itemStackBeforeBreak, event.itemStackAfterBreak)); diff --git a/Canopy [BP]/scripts/src/rules/durabilitySwap.js b/Canopy[BP]/scripts/src/rules/durabilitySwap.js similarity index 90% rename from Canopy [BP]/scripts/src/rules/durabilitySwap.js rename to Canopy[BP]/scripts/src/rules/durabilitySwap.js index 14b08cdb..8d88fdc0 100644 --- a/Canopy [BP]/scripts/src/rules/durabilitySwap.js +++ b/Canopy[BP]/scripts/src/rules/durabilitySwap.js @@ -5,7 +5,8 @@ import { usedDurability, getRemainingDurability } from 'src/rules/durabilityNoti const rule = new BooleanRule({ category: 'Rules', identifier: 'durabilitySwap', - description: { translate: 'rules.durabilitySwap' } + description: { translate: 'rules.durabilitySwap' }, + wikiDescription: 'When your tool hits 0 durability, it is automatically swapped out of your hand. Swap priority: empty slots first, then items that don\'t take durability damage, then items with durability remaining.' }); world.afterEvents.playerBreakBlock.subscribe((event) => durabilitySwap(event.player, event.itemStackBeforeBreak, event.itemStackAfterBreak)); diff --git a/Canopy [BP]/scripts/src/rules/echoShardsEnableShriekers.js b/Canopy[BP]/scripts/src/rules/echoShardsEnableShriekers.js similarity index 95% rename from Canopy [BP]/scripts/src/rules/echoShardsEnableShriekers.js rename to Canopy[BP]/scripts/src/rules/echoShardsEnableShriekers.js index 4a92eeae..8cdfe1c9 100644 --- a/Canopy [BP]/scripts/src/rules/echoShardsEnableShriekers.js +++ b/Canopy[BP]/scripts/src/rules/echoShardsEnableShriekers.js @@ -8,6 +8,7 @@ export class EchoShardsEnableShriekers extends BooleanRule { constructor() { super(GlobalRule.morphOptions({ identifier: 'echoShardsEnableShriekers', + wikiDescription: 'Using an echo shard on a sculk shrieker allows it to summon wardens.', onEnableCallback: () => this.subscribeToEvent(), onDisableCallback: () => this.unsubscribeFromEvent() })); diff --git a/Canopy [BP]/scripts/src/rules/enderPearlChunkLoading.js b/Canopy[BP]/scripts/src/rules/enderPearlChunkLoading.js similarity index 93% rename from Canopy [BP]/scripts/src/rules/enderPearlChunkLoading.js rename to Canopy[BP]/scripts/src/rules/enderPearlChunkLoading.js index e785d9b2..b1c85b4d 100644 --- a/Canopy [BP]/scripts/src/rules/enderPearlChunkLoading.js +++ b/Canopy[BP]/scripts/src/rules/enderPearlChunkLoading.js @@ -6,6 +6,8 @@ class EnderPearlChunkLoading extends IntegerRule { constructor() { super(GlobalRule.morphOptions({ identifier: 'enderPearlChunkLoading', + wikiDescription: 'Allows ender pearls to tick the chunks around them in a square radius. Set to `-1` to disable.', + suggestedOptions: [-1, 2, 3, 4], onModifyCallback: (newValue) => this.tryStartTicking(newValue), defaultValue: -1, valueRange: { range: { min: 2, max: 6 }, other: [-1] } diff --git a/Canopy [BP]/scripts/src/rules/entityInstantDeath.js b/Canopy[BP]/scripts/src/rules/entityInstantDeath.js similarity index 85% rename from Canopy [BP]/scripts/src/rules/entityInstantDeath.js rename to Canopy[BP]/scripts/src/rules/entityInstantDeath.js index 1687e5e5..8a3506fa 100644 --- a/Canopy [BP]/scripts/src/rules/entityInstantDeath.js +++ b/Canopy[BP]/scripts/src/rules/entityInstantDeath.js @@ -5,6 +5,7 @@ export class EntityInstantDeath extends BooleanRule { constructor() { super(GlobalRule.morphOptions({ identifier: 'entityInstantDeath', + wikiDescription: 'Removes the 20-gametick entity death animation, removing the entity immediately. Also causes entities not to drop XP.', onEnableCallback: () => this.subscribeToEvent(), onDisableCallback: () => this.unsubscribeFromEvent() })); diff --git a/Canopy [BP]/scripts/src/rules/entitySeparation.js b/Canopy[BP]/scripts/src/rules/entitySeparation.js similarity index 94% rename from Canopy [BP]/scripts/src/rules/entitySeparation.js rename to Canopy[BP]/scripts/src/rules/entitySeparation.js index 14dfcfc6..6b75ccbb 100644 --- a/Canopy [BP]/scripts/src/rules/entitySeparation.js +++ b/Canopy[BP]/scripts/src/rules/entitySeparation.js @@ -8,6 +8,7 @@ export class EntitySeparation extends BooleanRule { constructor() { super(GlobalRule.morphOptions({ identifier: 'entitySeparation', + wikiDescription: 'When stacked entities trigger a pressure plate, one entity will be pushed in the direction a neighboring dropper is facing.', onEnableCallback: () => this.subscribeToEvent(), onDisableCallback: () => this.unsubscribeFromEvent() })); diff --git a/Canopy [BP]/scripts/src/rules/explosionChainReactionOnly.js b/Canopy[BP]/scripts/src/rules/explosionChainReactionOnly.js similarity index 74% rename from Canopy [BP]/scripts/src/rules/explosionChainReactionOnly.js rename to Canopy[BP]/scripts/src/rules/explosionChainReactionOnly.js index 444c1398..35a5e571 100644 --- a/Canopy [BP]/scripts/src/rules/explosionChainReactionOnly.js +++ b/Canopy[BP]/scripts/src/rules/explosionChainReactionOnly.js @@ -5,7 +5,8 @@ new BooleanRule({ category: 'Rules', identifier: 'explosionChainReactionOnly', description: { translate: 'rules.explosionChainReactionOnly' }, - independentRules: ['explosionNoBlockDamage', 'explosionOff'] + independentRules: ['explosionNoBlockDamage', 'explosionOff'], + wikiDescription: 'When enabled, explosions only affect TNT blocks, resulting in chain reactions but no other block damage. Cannot be enabled at the same time as `explosionNoBlockDamage` or `explosionOff`.' }); world.beforeEvents.explosion.subscribe((event) => { diff --git a/Canopy [BP]/scripts/src/rules/explosionNoBlockDamage.js b/Canopy[BP]/scripts/src/rules/explosionNoBlockDamage.js similarity index 61% rename from Canopy [BP]/scripts/src/rules/explosionNoBlockDamage.js rename to Canopy[BP]/scripts/src/rules/explosionNoBlockDamage.js index fb33bddf..238d7399 100644 --- a/Canopy [BP]/scripts/src/rules/explosionNoBlockDamage.js +++ b/Canopy[BP]/scripts/src/rules/explosionNoBlockDamage.js @@ -5,7 +5,8 @@ new BooleanRule({ category: 'Rules', identifier: 'explosionNoBlockDamage', description: { translate: 'rules.explosionNoBlockDamage' }, - independentRules: ['explosionChainReactionOnly', 'explosionOff'] + independentRules: ['explosionChainReactionOnly', 'explosionOff'], + wikiDescription: 'Enables/disables explosion block damage. Explosions will still damage entities but will not break blocks. Useful for testing TNT-based contraptions in a controlled environment. Cannot be enabled at the same time as `explosionChainReactionOnly` or `explosionOff`.' }); world.beforeEvents.explosion.subscribe((event) => { diff --git a/Canopy [BP]/scripts/src/rules/explosionOff.js b/Canopy[BP]/scripts/src/rules/explosionOff.js similarity index 85% rename from Canopy [BP]/scripts/src/rules/explosionOff.js rename to Canopy[BP]/scripts/src/rules/explosionOff.js index 720d4989..2ba32a45 100644 --- a/Canopy [BP]/scripts/src/rules/explosionOff.js +++ b/Canopy[BP]/scripts/src/rules/explosionOff.js @@ -5,7 +5,8 @@ new BooleanRule({ category: 'Rules', identifier: 'explosionOff', description: { translate: 'rules.explosionOff' }, - independentRules: ['explosionChainReactionOnly', 'explosionNoBlockDamage'] + independentRules: ['explosionChainReactionOnly', 'explosionNoBlockDamage'], + wikiDescription: 'Enables/disables all explosions.' }); world.beforeEvents.explosion.subscribe((event) => { diff --git a/Canopy [BP]/scripts/src/rules/flippinArrows.js b/Canopy[BP]/scripts/src/rules/flippinArrows.js similarity index 96% rename from Canopy [BP]/scripts/src/rules/flippinArrows.js rename to Canopy[BP]/scripts/src/rules/flippinArrows.js index 5b559da5..8a77acb5 100644 --- a/Canopy [BP]/scripts/src/rules/flippinArrows.js +++ b/Canopy[BP]/scripts/src/rules/flippinArrows.js @@ -6,7 +6,8 @@ import DirectionStateFinder from "../classes/DirectionState"; new BooleanRule({ category: 'Rules', identifier: 'flippinArrows', - description: { translate: 'rules.flippinArrows' } + description: { translate: 'rules.flippinArrows' }, + wikiDescription: 'Using an arrow on a block will flip, rotate, or open it. Keeping the arrow in your offhand while placing blocks will place them in the opposite direction they would normally face.' }); const WAIT_TICKS_BETWEEN_USE = 5; diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Biome.js b/Canopy[BP]/scripts/src/rules/infodisplay/Biome.js similarity index 73% rename from Canopy [BP]/scripts/src/rules/infodisplay/Biome.js rename to Canopy[BP]/scripts/src/rules/infodisplay/Biome.js index 62a2fb19..0b07f8eb 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/Biome.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/Biome.js @@ -1,12 +1,13 @@ -import { InfoDisplayElement } from './InfoDisplayElement.js'; +import { InfoDisplayTextElement } from './InfoDisplayTextElement.js'; -class Biome extends InfoDisplayElement { +class Biome extends InfoDisplayTextElement { player; constructor(player, displayLine) { - const ruleData = { - identifier: 'biome', - description: { translate: 'rules.infoDisplay.biome' } + const ruleData = { + identifier: 'biome', + description: { translate: 'rules.infoDisplay.biome' }, + wikiDescription: 'Shows the biome at your current location.' }; super(ruleData, displayLine); this.player = player; diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/BlockStates.js b/Canopy[BP]/scripts/src/rules/infodisplay/BlockStates.js similarity index 78% rename from Canopy [BP]/scripts/src/rules/infodisplay/BlockStates.js rename to Canopy[BP]/scripts/src/rules/infodisplay/BlockStates.js index 33ec7b69..8b290946 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/BlockStates.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/BlockStates.js @@ -1,14 +1,15 @@ -import { InfoDisplayElement } from './InfoDisplayElement.js'; +import { InfoDisplayTextElement } from './InfoDisplayTextElement.js'; import { getRaycastResults } from '../../../include/utils.js'; import { LiquidType } from '@minecraft/server'; -export class BlockStates extends InfoDisplayElement { +export class BlockStates extends InfoDisplayTextElement { player; constructor(player, displayLine) { const ruleData = { identifier: 'blockStates', - description: { translate: 'rules.infoDisplay.blockStates' } + description: { translate: 'rules.infoDisplay.blockStates' }, + wikiDescription: 'Shows the block states of the block you are targeting. Especially useful with [Construct](https://github.com/ForestOfLight/Construct), which shows desired block states as you build. Includes waterlogged status.' } super(ruleData, displayLine); this.player = player; diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/CardinalFacing.js b/Canopy[BP]/scripts/src/rules/infodisplay/CardinalFacing.js similarity index 72% rename from Canopy [BP]/scripts/src/rules/infodisplay/CardinalFacing.js rename to Canopy[BP]/scripts/src/rules/infodisplay/CardinalFacing.js index 3332e068..441578aa 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/CardinalFacing.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/CardinalFacing.js @@ -1,10 +1,10 @@ -import { InfoDisplayElement } from './InfoDisplayElement.js'; +import { InfoDisplayTextElement } from './InfoDisplayTextElement.js'; -class CardinalFacing extends InfoDisplayElement { +class CardinalFacing extends InfoDisplayTextElement { player; constructor(player, displayLine) { - const ruleData = { identifier: 'cardinalFacing', description: { translate: 'rules.infoDisplay.cardinalFacing' } }; + const ruleData = { identifier: 'cardinalFacing', description: { translate: 'rules.infoDisplay.cardinalFacing' }, wikiDescription: 'Shows which direction you are facing using cardinal directions (N, S, E, W) and the corresponding coordinate axis (e.g., N (-z)).' }; super(ruleData, displayLine); this.player = player; } diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/ChunkCoords.js b/Canopy[BP]/scripts/src/rules/infodisplay/ChunkCoords.js similarity index 83% rename from Canopy [BP]/scripts/src/rules/infodisplay/ChunkCoords.js rename to Canopy[BP]/scripts/src/rules/infodisplay/ChunkCoords.js index fd56c96d..2c90567a 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/ChunkCoords.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/ChunkCoords.js @@ -1,8 +1,8 @@ -import { InfoDisplayElement } from './InfoDisplayElement.js'; +import { InfoDisplayTextElement } from './InfoDisplayTextElement.js'; -class ChunkCoords extends InfoDisplayElement { +class ChunkCoords extends InfoDisplayTextElement { constructor(player, displayLine) { - const ruleData = { identifier: 'chunkCoords', description: { translate: 'rules.infoDisplay.chunkCoords' } }; + const ruleData = { identifier: 'chunkCoords', description: { translate: 'rules.infoDisplay.chunkCoords' }, wikiDescription: 'Shows the coordinates of the chunk you are in and your relative position within that chunk.' }; super(ruleData, displayLine); this.player = player; } diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Coords.js b/Canopy[BP]/scripts/src/rules/infodisplay/Coords.js similarity index 70% rename from Canopy [BP]/scripts/src/rules/infodisplay/Coords.js rename to Canopy[BP]/scripts/src/rules/infodisplay/Coords.js index 0265263e..60f13447 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/Coords.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/Coords.js @@ -1,10 +1,10 @@ -import { InfoDisplayElement } from './InfoDisplayElement.js'; +import { InfoDisplayTextElement } from './InfoDisplayTextElement.js'; -class Coords extends InfoDisplayElement { +class Coords extends InfoDisplayTextElement { player; constructor(player, displayLine) { - const ruleData = { identifier: 'coords', description: { translate: 'rules.infoDisplay.coords' } }; + const ruleData = { identifier: 'coords', description: { translate: 'rules.infoDisplay.coords' }, wikiDescription: 'Shows your coordinates truncated at 2 decimal places.' }; super(ruleData, displayLine); this.player = player; } diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Dimension.js b/Canopy[BP]/scripts/src/rules/infodisplay/Dimension.js similarity index 70% rename from Canopy [BP]/scripts/src/rules/infodisplay/Dimension.js rename to Canopy[BP]/scripts/src/rules/infodisplay/Dimension.js index bc3a883c..8cd6b0e8 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/Dimension.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/Dimension.js @@ -1,9 +1,9 @@ import { getColorByDimension } from '../../../include/utils.js'; -import { InfoDisplayElement } from './InfoDisplayElement.js'; +import { InfoDisplayTextElement } from './InfoDisplayTextElement.js'; -class Dimension extends InfoDisplayElement { +class Dimension extends InfoDisplayTextElement { constructor(player, displayLine) { - const ruleData = { identifier: 'dimension', description: { translate: 'rules.infoDisplay.dimension' } }; + const ruleData = { identifier: 'dimension', description: { translate: 'rules.infoDisplay.dimension' }, wikiDescription: 'Shows your current dimension\'s identifier.' }; super(ruleData, displayLine); this.player = player; } diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Entities.js b/Canopy[BP]/scripts/src/rules/infodisplay/Entities.js similarity index 80% rename from Canopy [BP]/scripts/src/rules/infodisplay/Entities.js rename to Canopy[BP]/scripts/src/rules/infodisplay/Entities.js index f9e29298..585ef9f3 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/Entities.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/Entities.js @@ -1,11 +1,11 @@ -import { InfoDisplayElement } from "./InfoDisplayElement.js"; +import { InfoDisplayTextElement } from "./InfoDisplayTextElement.js"; import { Vector } from "../../../lib/Vector.js"; -class Entities extends InfoDisplayElement { +class Entities extends InfoDisplayTextElement { player; constructor(player, displayLine) { - const ruleData = { identifier: 'entities', description: { translate: 'rules.infoDisplay.entities' } }; + const ruleData = { identifier: 'entities', description: { translate: 'rules.infoDisplay.entities' }, wikiDescription: 'Shows the number of entities in front of your player. If there are many entities in the world, having this enabled may cause lag.' }; super(ruleData, displayLine); this.player = player; } diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/EventTrackers.js b/Canopy[BP]/scripts/src/rules/infodisplay/EventTrackers.js similarity index 60% rename from Canopy [BP]/scripts/src/rules/infodisplay/EventTrackers.js rename to Canopy[BP]/scripts/src/rules/infodisplay/EventTrackers.js index 6c930f7c..28d4d8db 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/EventTrackers.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/EventTrackers.js @@ -1,9 +1,9 @@ -import { InfoDisplayElement } from './InfoDisplayElement.js'; +import { InfoDisplayTextElement } from './InfoDisplayTextElement.js'; import { getAllTrackerInfoString } from 'src/commands/trackevent'; -class EventTrackers extends InfoDisplayElement { +class EventTrackers extends InfoDisplayTextElement { constructor(displayLine) { - const ruleData = { identifier: 'eventTrackers', description: { translate: 'rules.infoDisplay.eventTrackers' } }; + const ruleData = { identifier: 'eventTrackers', description: { translate: 'rules.infoDisplay.eventTrackers' }, wikiDescription: 'Shows the counts of currently tracked events. Tracking is controlled with `/trackevent`.' }; super(ruleData, displayLine, true); } diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Facing.js b/Canopy[BP]/scripts/src/rules/infodisplay/Facing.js similarity index 77% rename from Canopy [BP]/scripts/src/rules/infodisplay/Facing.js rename to Canopy[BP]/scripts/src/rules/infodisplay/Facing.js index 2057540e..c5eb8a09 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/Facing.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/Facing.js @@ -1,10 +1,10 @@ -import { InfoDisplayElement } from './InfoDisplayElement.js'; +import { InfoDisplayTextElement } from './InfoDisplayTextElement.js'; -class Facing extends InfoDisplayElement { +class Facing extends InfoDisplayTextElement { player; constructor(player, displayLine) { - const ruleData = { identifier: 'facing', description: { translate: 'rules.infoDisplay.facing' } }; + const ruleData = { identifier: 'facing', description: { translate: 'rules.infoDisplay.facing' }, wikiDescription: 'Shows your exact facing direction using yaw and pitch values.' }; super(ruleData, displayLine); this.player = player; } diff --git a/Canopy[BP]/scripts/src/rules/infodisplay/HeldItemDurability.js b/Canopy[BP]/scripts/src/rules/infodisplay/HeldItemDurability.js new file mode 100644 index 00000000..a7bbcf94 --- /dev/null +++ b/Canopy[BP]/scripts/src/rules/infodisplay/HeldItemDurability.js @@ -0,0 +1,49 @@ +import { EntityComponentTypes, EquipmentSlot, ItemComponentTypes } from '@minecraft/server'; +import { InfoDisplayTextElement } from './InfoDisplayTextElement.js'; + +export class HeldItemDurability extends InfoDisplayTextElement { + player; + + constructor(player, displayLine) { + const ruleData = { + identifier: 'heldItemDurability', + description: { translate: 'rules.infoDisplay.heldItemDurability' } + }; + super(ruleData, displayLine); + this.player = player; + } + + getFormattedDataOwnLine() { + const durability = this.#tryGetDurabilityValues(); + if (!durability) + return { text: '' }; + return { translate: 'rules.infoDisplay.heldItemDurability.display', with: [this.#getFormattedRatio(durability)] }; + } + + getFormattedDataSharedLine() { + return this.getFormattedDataOwnLine(); + } + + #tryGetDurabilityValues() { + const equippable = this.player.getComponent(EntityComponentTypes.Equippable); + const itemStack = equippable?.getEquipment(EquipmentSlot.Mainhand); + if (!itemStack) + return void 0; + const durabilityComponent = itemStack.getComponent(ItemComponentTypes.Durability); + if (!durabilityComponent) + return void 0; + return { max: durabilityComponent.maxDurability, damage: durabilityComponent.damage }; + } + + #getFormattedRatio({ max, damage }) { + const remaining = max - damage; + const color = this.#getDurabilityColor(remaining / max); + return `${color}${remaining}§7/§a${max}§r`; + } + + #getDurabilityColor(ratio) { + if (ratio >= 0.5) return '§a'; + if (ratio >= 0.1) return '§e'; + return '§c'; + } +} diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/HopperCounterCounts.js b/Canopy[BP]/scripts/src/rules/infodisplay/HopperCounterCounts.js similarity index 77% rename from Canopy [BP]/scripts/src/rules/infodisplay/HopperCounterCounts.js rename to Canopy[BP]/scripts/src/rules/infodisplay/HopperCounterCounts.js index d36ecbfb..7063091e 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/HopperCounterCounts.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/HopperCounterCounts.js @@ -1,10 +1,10 @@ -import { InfoDisplayElement } from "./InfoDisplayElement"; +import { InfoDisplayTextElement } from "./InfoDisplayTextElement"; import { counterChannels } from "../../classes/CounterChannels"; import { getColorCode } from "../../../include/utils"; -class HopperCounterCounts extends InfoDisplayElement { +class HopperCounterCounts extends InfoDisplayTextElement { constructor(displayLine) { - const ruleData = { identifier: 'hopperCounterCounts', description: { translate: 'rules.infoDisplay.hopperCounterCounts' } }; + const ruleData = { identifier: 'hopperCounterCounts', description: { translate: 'rules.infoDisplay.hopperCounterCounts' }, wikiDescription: 'Shows all active hopper counter channels in real-time, displayed in their respective wool colors. Channel display mode (count, hr, min, sec) is controlled with `./counter `.' }; super(ruleData, displayLine, true); } diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplay.js b/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplay.js similarity index 64% rename from Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplay.js rename to Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplay.js index 7f0779eb..5253f3f4 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplay.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplay.js @@ -1,4 +1,6 @@ import { system, world } from '@minecraft/server'; +import { InfoDisplayTextElement } from './InfoDisplayTextElement'; +import { InfoDisplayShapeElement } from './InfoDisplayShapeElement'; import Coords from './Coords'; import CardinalFacing from './CardinalFacing'; @@ -27,15 +29,17 @@ import { Dimension } from './Dimension'; import { Weather } from './Weather'; import { LiquidTarget } from './LiquidTarget'; import { LiquidStates } from './LiquidStates'; +import { HeldItemDurability } from './HeldItemDurability'; -const playerToInfoDisplayMap = {}; -let currentTickWorldwideElementData = {}; +import { RenderSignalStrength } from './RenderSignalStrength'; class InfoDisplay { player; elements = []; infoMessage = { rawtext: [] }; clearedPreviousMessage = false; + static playerToInfoDisplayMap = {}; + static currentTickWorldwideElementData = {}; constructor(player) { this.player = player; @@ -61,33 +65,44 @@ class InfoDisplay { new EventTrackers(17), new HopperCounterCounts(18), new SimulationMap(player, 19), - new Target(player, 20), - new SignalStrength(player, 20), - new BlockStates(player, 21), - new PeekInventory(player, 22), - new LiquidTarget(player, 23), - new LiquidStates(player, 24) + new HeldItemDurability(player, 20), + new Target(player, 21), + new SignalStrength(player, 21), + new BlockStates(player, 22), + new PeekInventory(player, 23), + new LiquidTarget(player, 24), + new LiquidStates(player, 25), + + new RenderSignalStrength(player) ]; - playerToInfoDisplayMap[player.id] = this; + InfoDisplay.playerToInfoDisplayMap[player.id] = this; + this.enableEnabledRules(); } update() { this.infoMessage = { rawtext: [] }; const enabledElements = this.getEnabledElements(); for (let i = 0; i < enabledElements.length; i++) - this.updateElementData(enabledElements, i); + this.updateElements(enabledElements, i); this.sendInfoMessage(); } - updateElementData(elements, currIndex) { + updateElements(elements, currIndex) { const element = elements[currIndex]; - if (element.isWorldwide && !currentTickWorldwideElementData[element.identifier]) - currentTickWorldwideElementData[element.identifier] = { own: element.getFormattedDataOwnLine(), shared: element.getFormattedDataSharedLine() }; + if (element instanceof InfoDisplayTextElement) + this.updateTextElement(element, elements, currIndex); + else if (element instanceof InfoDisplayShapeElement) + this.updateShapeElement(element, elements, currIndex); + } + + updateTextElement(element, elements, currIndex) { + if (element.isWorldwide && !InfoDisplay.currentTickWorldwideElementData[element.identifier]) + InfoDisplay.currentTickWorldwideElementData[element.identifier] = { own: element.getFormattedDataOwnLine(), shared: element.getFormattedDataSharedLine() }; let data; if (this.getElementsOnLine(elements, element.lineNumber).length === 1) - data = currentTickWorldwideElementData[element.identifier]?.own || element.getFormattedDataOwnLine(); + data = InfoDisplay.currentTickWorldwideElementData[element.identifier]?.own || element.getFormattedDataOwnLine(); else - data = currentTickWorldwideElementData[element.identifier]?.shared || element.getFormattedDataSharedLine(); + data = InfoDisplay.currentTickWorldwideElementData[element.identifier]?.shared || element.getFormattedDataSharedLine(); if (this.infoMessage.rawtext.length !== 0 && this.isOnNewLine(elements, currIndex) && !this.dataIsWhitespace(data)) this.infoMessage.rawtext.push({ text: '\n§r' }); if (!this.isOnNewLine(elements, currIndex) && !this.dataIsWhitespace(data)) @@ -97,6 +112,17 @@ class InfoDisplay { this.infoMessage.rawtext.push(data); } + updateShapeElement(element) { + if (!element.shouldRender()) + return; + if (element.isWorldwide && !InfoDisplay.currentTickWorldwideElementData[element.identifier]) { + InfoDisplay.currentTickWorldwideElementData[element.identifier] = true; + element.onTick(); + } else if (!element.isWorldwide) { + element.onTick(); + } + } + getEnabledElements() { return this.elements.filter(element => element.rule.getValue(this.player)); } @@ -151,19 +177,31 @@ class InfoDisplay { this.infoMessage = ''; this.clearedPreviousMessage = true; } + + enableEnabledRules() { + for (const element of this.elements) { + const rule = element.rule; + if (rule.getValue(this.player)) + rule.onEnable(); + } + } } system.runInterval(() => { - currentTickWorldwideElementData = {}; + InfoDisplay.currentTickWorldwideElementData = {}; const players = world.getAllPlayers(); for (const player of players) { - if (!player) continue; - const infoDisplay = playerToInfoDisplayMap[player.id] || new InfoDisplay(player); + if (!player) + continue; + const infoDisplay = InfoDisplay.playerToInfoDisplayMap[player.id] || new InfoDisplay(player); infoDisplay.update(); } }); world.beforeEvents.playerLeave.subscribe((event) => { - if (!event.player) return; - delete playerToInfoDisplayMap[event.player.id]; + if (!event.player) + return; + delete InfoDisplay.playerToInfoDisplayMap[event.player.id]; }); + +export { InfoDisplay }; diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplayElement.js b/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayElement.js similarity index 65% rename from Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplayElement.js rename to Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayElement.js index a2edb82d..e08aa8de 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplayElement.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayElement.js @@ -3,10 +3,9 @@ import { InfoDisplayRule, Rules } from '../../../lib/canopy/Canopy'; class InfoDisplayElement { identifier; rule; - lineNumber; isWorldwide; - constructor(ruleData, lineNumber, isWorldwide = false) { + constructor(ruleData, isWorldwide = false) { if (this.constructor === InfoDisplayElement) throw new TypeError("Abstract class 'InfoDisplayElement' cannot be instantiated directly."); if (!ruleData.identifier || !ruleData.description) @@ -14,15 +13,6 @@ class InfoDisplayElement { this.identifier = ruleData.identifier; this.rule = Rules.get(this.identifier) || new InfoDisplayRule({ identifier: this.identifier, ...ruleData }); this.isWorldwide = isWorldwide; - this.lineNumber = lineNumber; - } - - getFormattedDataOwnLine() { - throw new Error("Method 'getFormattedDataOwnLine()' must be implemented."); - } - - getFormattedDataSharedLine() { - throw new Error("Method 'getFormattedDataSharedLine()' must be implemented."); } } diff --git a/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayShapeElement.js b/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayShapeElement.js new file mode 100644 index 00000000..fe22749e --- /dev/null +++ b/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayShapeElement.js @@ -0,0 +1,41 @@ +import { InfoDisplayElement } from './InfoDisplayElement'; + +class InfoDisplayShapeElement extends InfoDisplayElement { + isRendering = false; + + constructor(ruleData, isWorldwide = false) { + const originalOnEnableCallback = ruleData.onEnableCallback; + ruleData.onEnableCallback = () => { + this.startRender(); + if (originalOnEnableCallback) + originalOnEnableCallback(); + }; + const originalOnDisableCallback = ruleData.onDisableCallback; + ruleData.onDisableCallback = () => { + this.stopRender(); + if (originalOnDisableCallback) + originalOnDisableCallback(); + }; + super(ruleData, isWorldwide); + if (this.constructor === InfoDisplayShapeElement) + throw new TypeError("Abstract class 'InfoDisplayShapeElement' cannot be instantiated directly."); + } + + startRender() { + this.isRendering = true; + } + + stopRender() { + this.isRendering = false; + } + + onTick() { + throw new Error("Method 'onTick()' must be implemented."); + } + + shouldRender() { + return this.isRendering; + } +} + +export { InfoDisplayShapeElement }; diff --git a/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayTextElement.js b/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayTextElement.js new file mode 100644 index 00000000..051d16fa --- /dev/null +++ b/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayTextElement.js @@ -0,0 +1,23 @@ +import { InfoDisplayElement } from './InfoDisplayElement'; + +class InfoDisplayTextElement extends InfoDisplayElement { + lineNumber; + isWorldwide; + + constructor(ruleData, lineNumber, isWorldwide = false) { + super(ruleData, isWorldwide); + if (this.constructor === InfoDisplayTextElement) + throw new TypeError("Abstract class 'InfoDisplayTextElement' cannot be instantiated directly."); + this.lineNumber = lineNumber; + } + + getFormattedDataOwnLine() { + throw new Error("Method 'getFormattedDataOwnLine()' must be implemented."); + } + + getFormattedDataSharedLine() { + throw new Error("Method 'getFormattedDataSharedLine()' must be implemented."); + } +} + +export { InfoDisplayTextElement }; diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Light.js b/Canopy[BP]/scripts/src/rules/infodisplay/Light.js similarity index 77% rename from Canopy [BP]/scripts/src/rules/infodisplay/Light.js rename to Canopy[BP]/scripts/src/rules/infodisplay/Light.js index 3628d377..ab4d2ac2 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/Light.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/Light.js @@ -1,12 +1,13 @@ -import { InfoDisplayElement } from './InfoDisplayElement.js'; +import { InfoDisplayTextElement } from './InfoDisplayTextElement.js'; -class Light extends InfoDisplayElement { +class Light extends InfoDisplayTextElement { player; constructor(player, displayLine) { - const ruleData = { + const ruleData = { identifier: 'light', - description: { translate: 'rules.infoDisplay.light' } + description: { translate: 'rules.infoDisplay.light' }, + wikiDescription: 'Shows the light level at your feet, including the sky light contribution.' }; super(ruleData, displayLine); this.player = player; diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/LiquidStates.js b/Canopy[BP]/scripts/src/rules/infodisplay/LiquidStates.js similarity index 85% rename from Canopy [BP]/scripts/src/rules/infodisplay/LiquidStates.js rename to Canopy[BP]/scripts/src/rules/infodisplay/LiquidStates.js index 9f7f5a19..1438737d 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/LiquidStates.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/LiquidStates.js @@ -1,13 +1,14 @@ -import { InfoDisplayElement } from './InfoDisplayElement.js'; +import { InfoDisplayTextElement } from './InfoDisplayTextElement.js'; import { LiquidType } from '@minecraft/server'; -export class LiquidStates extends InfoDisplayElement { +export class LiquidStates extends InfoDisplayTextElement { player; constructor(player, displayLine) { const ruleData = { identifier: 'liquidStates', - description: { translate: 'rules.infoDisplay.liquidStates' } + description: { translate: 'rules.infoDisplay.liquidStates' }, + wikiDescription: 'Shows the states of the liquid you are targeting.' } super(ruleData, displayLine); this.player = player; diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/LiquidTarget.js b/Canopy[BP]/scripts/src/rules/infodisplay/LiquidTarget.js similarity index 85% rename from Canopy [BP]/scripts/src/rules/infodisplay/LiquidTarget.js rename to Canopy[BP]/scripts/src/rules/infodisplay/LiquidTarget.js index 949f20f4..49cf841f 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/LiquidTarget.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/LiquidTarget.js @@ -1,9 +1,9 @@ -import { InfoDisplayElement } from "./InfoDisplayElement"; +import { InfoDisplayTextElement } from "./InfoDisplayTextElement"; import { parseName, stringifyLocation } from "../../../include/utils"; -export class LiquidTarget extends InfoDisplayElement { +export class LiquidTarget extends InfoDisplayTextElement { constructor(player, displayLine) { - const ruleData = { identifier: 'liquidTarget', description: { translate: 'rules.infoDisplay.liquidTarget' } }; + const ruleData = { identifier: 'liquidTarget', description: { translate: 'rules.infoDisplay.liquidTarget' }, wikiDescription: 'Shows the identifier of the liquid you are targeting.' }; super(ruleData, displayLine); this.player = player; } diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/MoonPhase.js b/Canopy[BP]/scripts/src/rules/infodisplay/MoonPhase.js similarity index 87% rename from Canopy [BP]/scripts/src/rules/infodisplay/MoonPhase.js rename to Canopy[BP]/scripts/src/rules/infodisplay/MoonPhase.js index f55aeb4f..fea59f97 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/MoonPhase.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/MoonPhase.js @@ -1,9 +1,9 @@ -import { InfoDisplayElement } from './InfoDisplayElement.js'; +import { InfoDisplayTextElement } from './InfoDisplayTextElement.js'; import { world } from '@minecraft/server'; -class MoonPhase extends InfoDisplayElement { +class MoonPhase extends InfoDisplayTextElement { constructor(displayLine) { - const ruleData = { identifier: 'moonPhase', description: { translate: 'rules.infoDisplay.moonPhase' } }; + const ruleData = { identifier: 'moonPhase', description: { translate: 'rules.infoDisplay.moonPhase' }, wikiDescription: 'Shows the current phase of the moon.' }; super(ruleData, displayLine, true); } diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/PeekInventory.js b/Canopy[BP]/scripts/src/rules/infodisplay/PeekInventory.js similarity index 84% rename from Canopy [BP]/scripts/src/rules/infodisplay/PeekInventory.js rename to Canopy[BP]/scripts/src/rules/infodisplay/PeekInventory.js index 3cbd4510..bf9c8b8e 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/PeekInventory.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/PeekInventory.js @@ -1,16 +1,17 @@ -import { InfoDisplayElement } from "./InfoDisplayElement"; +import { InfoDisplayTextElement } from "./InfoDisplayTextElement"; import { getRaycastResults, getClosestTarget } from "../../../include/utils"; import { currentQuery } from "../../commands/peek"; import { ItemStack } from "@minecraft/server"; -class PeekInventory extends InfoDisplayElement { +class PeekInventory extends InfoDisplayTextElement { player; constructor(player, displayLine) { - const ruleData = { identifier: 'peekInventory', - description: { translate: 'rules.infoDisplay.peekInventory' }, - contingentRules: ['target'], - globalContingentRules: ['allowPeekInventory'] + const ruleData = { identifier: 'peekInventory', + description: { translate: 'rules.infoDisplay.peekInventory' }, + contingentRules: ['target'], + globalContingentRules: ['allowPeekInventory'], + wikiDescription: 'Shows the inventory of the block or entity you are targeting in your InfoDisplay. Requires the `allowPeekInventory` global rule to be enabled.' }; super(ruleData, displayLine, false); this.player = player; diff --git a/Canopy[BP]/scripts/src/rules/infodisplay/RenderSignalStrength.js b/Canopy[BP]/scripts/src/rules/infodisplay/RenderSignalStrength.js new file mode 100644 index 00000000..877cd20e --- /dev/null +++ b/Canopy[BP]/scripts/src/rules/infodisplay/RenderSignalStrength.js @@ -0,0 +1,77 @@ +import { InfoDisplayShapeElement } from './InfoDisplayShapeElement'; +import { BlockVolume } from '@minecraft/server'; +import { SignalStrengthRenderer } from '../../classes/SignalStrengthRenderer'; +import { Vector } from '../../../lib/Vector'; + +class RenderSignalStrength extends InfoDisplayShapeElement { + player; + playerId; + static RENDER_DISTANCE = 10; + signalStrengthRenderers = {}; + + constructor(player) { + const ruleData = { + identifier: 'renderSignalStrength', + description: { translate: 'rules.infoDisplay.renderSignalStrength' }, + wikiDescription: `Renders the signal strength of nearby redstone dust in the world. Only renders for redstone dust within ${RenderSignalStrength.RENDER_DISTANCE} blocks from the player to avoid excessive rendering.`, + onEnableCallback: () => this.start(), + onDisableCallback: () => this.stop() + }; + super(ruleData, 0); + this.player = player; + this.playerId = player.id; + } + + start() { + this.signalStrengthRenderers = {}; + } + + stop() { + for (const [key, renderer] of Object.entries(this.signalStrengthRenderers)) { + renderer.destroy(); + delete this.signalStrengthRenderers[key]; + } + this.signalStrengthRenderers = {}; + } + + onTick() { + this.renderForNearbyBlocks(); + } + + renderForNearbyBlocks() { + const dimension = this.player.dimension; + const signalStrengthRendererKeys = Object.keys(this.signalStrengthRenderers); + const blockKeys = []; + const blockLocationIterator = this.getNearbyBlockLocationIterator(dimension, this.player.location); + let locationResult = blockLocationIterator.next(); + while (!locationResult.done) { + const block = dimension.getBlock(locationResult.value); + const key = this.getKey(block); + blockKeys.push(key); + if (!signalStrengthRendererKeys.includes(key)) + this.signalStrengthRenderers[key] = new SignalStrengthRenderer(block, dimension, this.player); + locationResult = blockLocationIterator.next(); + } + for (const [key, renderer] of Object.entries(this.signalStrengthRenderers)) { + if (!blockKeys.includes(key)) { + renderer.destroy(); + delete this.signalStrengthRenderers[key]; + } + } + } + + getNearbyBlockLocationIterator(dimension, location) { + const locationVector = Vector.from(location); + const min = locationVector.subtract(new Vector(RenderSignalStrength.RENDER_DISTANCE, RenderSignalStrength.RENDER_DISTANCE, RenderSignalStrength.RENDER_DISTANCE)); + const max = locationVector.add(new Vector(RenderSignalStrength.RENDER_DISTANCE, 2, RenderSignalStrength.RENDER_DISTANCE)); + const volume = new BlockVolume(min, max); + const blockVolume = dimension.getBlocks(volume, { includeTypes: ["minecraft:redstone_wire"] }, true); + return blockVolume.getBlockLocationIterator(); + } + + getKey(block) { + return `<${block.x}, ${block.y}, ${block.z}>`; + } +} + +export { RenderSignalStrength }; diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/SessionTime.js b/Canopy[BP]/scripts/src/rules/infodisplay/SessionTime.js similarity index 79% rename from Canopy [BP]/scripts/src/rules/infodisplay/SessionTime.js rename to Canopy[BP]/scripts/src/rules/infodisplay/SessionTime.js index c3e46fcd..e37e7bbb 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/SessionTime.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/SessionTime.js @@ -1,10 +1,10 @@ -import { InfoDisplayElement } from './InfoDisplayElement.js'; +import { InfoDisplayTextElement } from './InfoDisplayTextElement.js'; -class SessionTime extends InfoDisplayElement { +class SessionTime extends InfoDisplayTextElement { player; constructor(player, displayLine) { - const ruleData = { identifier: 'sessionTime', description: { translate: 'rules.infoDisplay.sessionTime' } }; + const ruleData = { identifier: 'sessionTime', description: { translate: 'rules.infoDisplay.sessionTime' }, wikiDescription: 'Shows the elapsed time since you joined the world in this session.' }; super(ruleData, displayLine); this.player = player; player.setDynamicProperty('joinDate', Date.now()); diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/SignalStrength.js b/Canopy[BP]/scripts/src/rules/infodisplay/SignalStrength.js similarity index 81% rename from Canopy [BP]/scripts/src/rules/infodisplay/SignalStrength.js rename to Canopy[BP]/scripts/src/rules/infodisplay/SignalStrength.js index 8ebc1123..951936ee 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/SignalStrength.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/SignalStrength.js @@ -1,11 +1,11 @@ -import { InfoDisplayElement } from "./InfoDisplayElement"; +import { InfoDisplayTextElement } from "./InfoDisplayTextElement"; import { getRaycastResults } from "../../../include/utils"; -class SignalStrength extends InfoDisplayElement { +class SignalStrength extends InfoDisplayTextElement { player; constructor(player, displayLine) { - const ruleData = { identifier: 'signalStrength', description: { translate: 'rules.infoDisplay.signalStrength' }, contingentRules: ['target'] }; + const ruleData = { identifier: 'signalStrength', description: { translate: 'rules.infoDisplay.signalStrength' }, contingentRules: ['target'], wikiDescription: 'Shows the redstone signal strength of the block you are targeting.' }; super(ruleData, displayLine, false); this.player = player; } diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/SimulationMap.js b/Canopy[BP]/scripts/src/rules/infodisplay/SimulationMap.js similarity index 68% rename from Canopy [BP]/scripts/src/rules/infodisplay/SimulationMap.js rename to Canopy[BP]/scripts/src/rules/infodisplay/SimulationMap.js index 9fbfb82e..ab44a76a 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/SimulationMap.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/SimulationMap.js @@ -1,12 +1,12 @@ import { world } from '@minecraft/server'; -import { InfoDisplayElement } from './InfoDisplayElement.js'; +import { InfoDisplayTextElement } from './InfoDisplayTextElement.js'; import { getConfig, getLoadedChunksMessage } from '../../commands/simmap.js'; -class SimulationMap extends InfoDisplayElement { +class SimulationMap extends InfoDisplayTextElement { player; constructor(player, displayLine) { - const ruleData = { identifier: 'simulationMap', description: { translate: 'rules.infoDisplay.simulationMap' } }; + const ruleData = { identifier: 'simulationMap', description: { translate: 'rules.infoDisplay.simulationMap' }, wikiDescription: 'Shows a map of loaded chunks around you or a configured location. Ticking chunks are green; non-ticking chunks are red. Configure with the `./simmap` command. **Warning:** This rule is performance-intensive.' }; super(ruleData, displayLine); this.player = player; } diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/SlimeChunk.js b/Canopy[BP]/scripts/src/rules/infodisplay/SlimeChunk.js similarity index 86% rename from Canopy [BP]/scripts/src/rules/infodisplay/SlimeChunk.js rename to Canopy[BP]/scripts/src/rules/infodisplay/SlimeChunk.js index 083490b6..6e58340a 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/SlimeChunk.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/SlimeChunk.js @@ -1,12 +1,12 @@ -import { InfoDisplayElement } from './InfoDisplayElement.js' +import { InfoDisplayTextElement } from './InfoDisplayTextElement.js' import { playerChangeSubChunkEvent } from '../../events/PlayerChangeSubChunkEvent.js' -export class SlimeChunk extends InfoDisplayElement { +export class SlimeChunk extends InfoDisplayTextElement { player; infoMessage = { text: '' }; constructor(player, displayLine) { - const ruleData = { identifier: 'slimeChunk', description: { translate: 'rules.infoDisplay.slimeChunk' } }; + const ruleData = { identifier: 'slimeChunk', description: { translate: 'rules.infoDisplay.slimeChunk' }, wikiDescription: 'Shows whether the chunk you are currently standing in is a slime chunk.' }; super(ruleData, displayLine); this.player = player; playerChangeSubChunkEvent.subscribe(this.onPlayerChangeSubChunk.bind(this)); diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Speed.js b/Canopy[BP]/scripts/src/rules/infodisplay/Speed.js similarity index 78% rename from Canopy [BP]/scripts/src/rules/infodisplay/Speed.js rename to Canopy[BP]/scripts/src/rules/infodisplay/Speed.js index a37833ec..bfc784af 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/Speed.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/Speed.js @@ -1,14 +1,15 @@ -import { InfoDisplayElement } from './InfoDisplayElement.js'; +import { InfoDisplayTextElement } from './InfoDisplayTextElement.js'; import { Vector } from '../../../lib/Vector.js'; import { TicksPerSecond } from '@minecraft/server'; -export class Speed extends InfoDisplayElement { +export class Speed extends InfoDisplayTextElement { player; constructor(player, displayLine) { const ruleData = { identifier: 'speed', - description: { translate: 'rules.infoDisplay.speed' } + description: { translate: 'rules.infoDisplay.speed' }, + wikiDescription: 'Shows your current movement speed in meters per second.' } super(ruleData, displayLine); this.player = player; diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Structures.js b/Canopy[BP]/scripts/src/rules/infodisplay/Structures.js similarity index 82% rename from Canopy [BP]/scripts/src/rules/infodisplay/Structures.js rename to Canopy[BP]/scripts/src/rules/infodisplay/Structures.js index e2f6e945..c43899a1 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/Structures.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/Structures.js @@ -1,12 +1,13 @@ -import { InfoDisplayElement } from "./InfoDisplayElement"; +import { InfoDisplayTextElement } from "./InfoDisplayTextElement"; -export class Structures extends InfoDisplayElement { +export class Structures extends InfoDisplayTextElement { player; constructor(player, displayLine) { const ruleData = { identifier: 'structures', - description: { translate: 'rules.infoDisplay.structures' } + description: { translate: 'rules.infoDisplay.structures' }, + wikiDescription: 'Shows naturally generated structures present at your current location.' } super(ruleData, displayLine); this.player = player; diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/TPS.js b/Canopy[BP]/scripts/src/rules/infodisplay/TPS.js similarity index 78% rename from Canopy [BP]/scripts/src/rules/infodisplay/TPS.js rename to Canopy[BP]/scripts/src/rules/infodisplay/TPS.js index 6d67b61b..0e43ce29 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/TPS.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/TPS.js @@ -1,10 +1,10 @@ -import { InfoDisplayElement } from './InfoDisplayElement.js'; +import { InfoDisplayTextElement } from './InfoDisplayTextElement.js'; import { Profiler } from '../../classes/Profiler.js'; import { TicksPerSecond } from '@minecraft/server'; -class TPS extends InfoDisplayElement { +class TPS extends InfoDisplayTextElement { constructor(displayLine) { - const ruleData = { identifier: 'tps', description: { translate: 'rules.infoDisplay.tps' } }; + const ruleData = { identifier: 'tps', description: { translate: 'rules.infoDisplay.tps' }, wikiDescription: 'Shows the server\'s current ticks per second (TPS).' }; super(ruleData, displayLine, true); } diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Target.js b/Canopy[BP]/scripts/src/rules/infodisplay/Target.js similarity index 89% rename from Canopy [BP]/scripts/src/rules/infodisplay/Target.js rename to Canopy[BP]/scripts/src/rules/infodisplay/Target.js index 866b09bc..bb58e398 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/Target.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/Target.js @@ -1,9 +1,9 @@ -import { InfoDisplayElement } from "./InfoDisplayElement"; +import { InfoDisplayTextElement } from "./InfoDisplayTextElement"; import { getRaycastResults, parseName, stringifyLocation } from "../../../include/utils"; -export class Target extends InfoDisplayElement { +export class Target extends InfoDisplayTextElement { constructor(player, displayLine) { - const ruleData = { identifier: 'target', description: { translate: 'rules.infoDisplay.target' } }; + const ruleData = { identifier: 'target', description: { translate: 'rules.infoDisplay.target' }, wikiDescription: 'Shows the identifier of the block or entity you are targeting.' }; super(ruleData, displayLine); this.player = player; } diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/TimeOfDay.js b/Canopy[BP]/scripts/src/rules/infodisplay/TimeOfDay.js similarity index 83% rename from Canopy [BP]/scripts/src/rules/infodisplay/TimeOfDay.js rename to Canopy[BP]/scripts/src/rules/infodisplay/TimeOfDay.js index d63954f7..db266019 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/TimeOfDay.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/TimeOfDay.js @@ -1,9 +1,9 @@ -import { InfoDisplayElement } from './InfoDisplayElement.js'; +import { InfoDisplayTextElement } from './InfoDisplayTextElement.js'; import { world } from '@minecraft/server'; -class TimeOfDay extends InfoDisplayElement { +class TimeOfDay extends InfoDisplayTextElement { constructor(displayLine) { - const ruleData = { identifier: 'timeOfDay', description: { translate: 'rules.infoDisplay.timeOfDay' } }; + const ruleData = { identifier: 'timeOfDay', description: { translate: 'rules.infoDisplay.timeOfDay' }, wikiDescription: 'Shows the Minecraft day-cycle time displayed as a 12-hour digital clock.' }; super(ruleData, displayLine, true); } diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Velocity.js b/Canopy[BP]/scripts/src/rules/infodisplay/Velocity.js similarity index 79% rename from Canopy [BP]/scripts/src/rules/infodisplay/Velocity.js rename to Canopy[BP]/scripts/src/rules/infodisplay/Velocity.js index bab0b83b..19e3a3e8 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/Velocity.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/Velocity.js @@ -1,13 +1,14 @@ -import { InfoDisplayElement } from './InfoDisplayElement.js'; +import { InfoDisplayTextElement } from './InfoDisplayTextElement.js'; import { Vector } from '../../../lib/Vector.js'; -export class Velocity extends InfoDisplayElement { +export class Velocity extends InfoDisplayTextElement { player; constructor(player, displayLine) { const ruleData = { identifier: 'velocity', - description: { translate: 'rules.infoDisplay.velocity' } + description: { translate: 'rules.infoDisplay.velocity' }, + wikiDescription: 'Shows your current x, y, and z velocity values in meters per tick.' } super(ruleData, displayLine); this.player = player; diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Weather.js b/Canopy[BP]/scripts/src/rules/infodisplay/Weather.js similarity index 65% rename from Canopy [BP]/scripts/src/rules/infodisplay/Weather.js rename to Canopy[BP]/scripts/src/rules/infodisplay/Weather.js index dbb18737..bf5d3997 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/Weather.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/Weather.js @@ -1,8 +1,8 @@ -import { InfoDisplayElement } from './InfoDisplayElement.js'; +import { InfoDisplayTextElement } from './InfoDisplayTextElement.js'; -class Weather extends InfoDisplayElement { +class Weather extends InfoDisplayTextElement { constructor(player, displayLine) { - const ruleData = { identifier: 'weather', description: { translate: 'rules.infoDisplay.weather' } }; + const ruleData = { identifier: 'weather', description: { translate: 'rules.infoDisplay.weather' }, wikiDescription: 'Shows the current weather in your dimension.' }; super(ruleData, displayLine); this.player = player; } diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/WorldDay.js b/Canopy[BP]/scripts/src/rules/infodisplay/WorldDay.js similarity index 63% rename from Canopy [BP]/scripts/src/rules/infodisplay/WorldDay.js rename to Canopy[BP]/scripts/src/rules/infodisplay/WorldDay.js index 67b6c137..f637f72f 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/WorldDay.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/WorldDay.js @@ -1,9 +1,9 @@ -import { InfoDisplayElement } from './InfoDisplayElement.js'; +import { InfoDisplayTextElement } from './InfoDisplayTextElement.js'; import { world } from '@minecraft/server'; -class WorldDay extends InfoDisplayElement { +class WorldDay extends InfoDisplayTextElement { constructor(displayLine) { - const ruleData = { identifier: 'worldDay', description: { translate: 'rules.infoDisplay.worldDay' } }; + const ruleData = { identifier: 'worldDay', description: { translate: 'rules.infoDisplay.worldDay' }, wikiDescription: 'Shows the count of Minecraft days elapsed since the world was created.' }; super(ruleData, displayLine, true); } diff --git a/Canopy [BP]/scripts/src/rules/instaminableDeepslate.js b/Canopy[BP]/scripts/src/rules/instaminableDeepslate.js similarity index 65% rename from Canopy [BP]/scripts/src/rules/instaminableDeepslate.js rename to Canopy[BP]/scripts/src/rules/instaminableDeepslate.js index 45b22001..90dc6949 100644 --- a/Canopy [BP]/scripts/src/rules/instaminableDeepslate.js +++ b/Canopy[BP]/scripts/src/rules/instaminableDeepslate.js @@ -4,7 +4,8 @@ import Instaminable from 'src/classes/Instaminable'; const instamineableDeepslateRule = new BooleanRule({ category: 'Rules', identifier: 'instaminableDeepslate', - description: { translate: 'rules.instaminableDeepslate' } + description: { translate: 'rules.instaminableDeepslate' }, + wikiDescription: 'Makes deepslate and its variants instaminable when using an efficiency 5 netherite pickaxe with haste 2.' }); function isDeepslate(value) { diff --git a/Canopy [BP]/scripts/src/rules/instaminableEndstone.js b/Canopy[BP]/scripts/src/rules/instaminableEndstone.js similarity index 65% rename from Canopy [BP]/scripts/src/rules/instaminableEndstone.js rename to Canopy[BP]/scripts/src/rules/instaminableEndstone.js index 52784a98..c51a0521 100644 --- a/Canopy [BP]/scripts/src/rules/instaminableEndstone.js +++ b/Canopy[BP]/scripts/src/rules/instaminableEndstone.js @@ -4,7 +4,8 @@ import Instaminable from 'src/classes/Instaminable'; const instamineableEndstoneRule = new BooleanRule({ category: 'Rules', identifier: 'instaminableEndstone', - description: { translate: 'rules.instaminableEndstone' } + description: { translate: 'rules.instaminableEndstone' }, + wikiDescription: 'Makes endstone and its variants instaminable when using an efficiency 5 netherite pickaxe with haste 2.' }); function isEndStone(value) { diff --git a/Canopy [BP]/scripts/src/rules/minecartChunkLoading.js b/Canopy[BP]/scripts/src/rules/minecartChunkLoading.js similarity index 88% rename from Canopy [BP]/scripts/src/rules/minecartChunkLoading.js rename to Canopy[BP]/scripts/src/rules/minecartChunkLoading.js index 0cf6c3f8..e36e5be8 100644 --- a/Canopy [BP]/scripts/src/rules/minecartChunkLoading.js +++ b/Canopy[BP]/scripts/src/rules/minecartChunkLoading.js @@ -6,6 +6,8 @@ class MinecartChunkLoading extends IntegerRule { constructor() { super(GlobalRule.morphOptions({ identifier: 'minecartChunkLoading', + wikiDescription: 'Allows minecarts to tick the chunks around them in a square radius for 10 seconds after they are spawned. The minecart must remain present for the full duration. Set to `-1` to disable.', + suggestedOptions: [-1, 2, 3, 4], onModifyCallback: (newValue) => this.tryStartTicking(newValue), defaultValue: -1, valueRange: { range: { min: 2, max: 6 }, other: [-1] } diff --git a/Canopy [BP]/scripts/src/rules/noWelcomeMessage.js b/Canopy[BP]/scripts/src/rules/noWelcomeMessage.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/noWelcomeMessage.js rename to Canopy[BP]/scripts/src/rules/noWelcomeMessage.js diff --git a/Canopy [BP]/scripts/src/rules/pistonBedrockBreaking.js b/Canopy[BP]/scripts/src/rules/pistonBedrockBreaking.js similarity index 86% rename from Canopy [BP]/scripts/src/rules/pistonBedrockBreaking.js rename to Canopy[BP]/scripts/src/rules/pistonBedrockBreaking.js index 4e3585d9..ce863d98 100644 --- a/Canopy [BP]/scripts/src/rules/pistonBedrockBreaking.js +++ b/Canopy[BP]/scripts/src/rules/pistonBedrockBreaking.js @@ -5,7 +5,8 @@ import DirectionStateFinder from "../classes/DirectionState"; new BooleanRule({ category: 'Rules', identifier: 'pistonBedrockBreaking', - description: { translate: 'rules.pistonBedrockBreaking' } + description: { translate: 'rules.pistonBedrockBreaking' }, + wikiDescription: 'Enables/disables the ability to break bedrock with pistons. To break bedrock, place a piston facing away from the bedrock block and power it. The piston will extend into the bedrock and break it when retracted. The piston also drops as an item.' }); const insideBedrockPistonList = []; diff --git a/Canopy [BP]/scripts/src/rules/playerSit.js b/Canopy[BP]/scripts/src/rules/playerSit.js similarity index 94% rename from Canopy [BP]/scripts/src/rules/playerSit.js rename to Canopy[BP]/scripts/src/rules/playerSit.js index b2c8802b..0760616e 100644 --- a/Canopy [BP]/scripts/src/rules/playerSit.js +++ b/Canopy[BP]/scripts/src/rules/playerSit.js @@ -14,6 +14,7 @@ class PlayerSit extends BooleanRule { constructor() { super(GlobalRule.morphOptions({ identifier: ruleID, + wikiDescription: 'Allows players to sit down anywhere after 3 quick sneaks. Also determines whether the `/sit` command can be used.', description: { translate: `rules.${ruleID}`, with: [SNEAK_COUNT.toString()] }, onEnableCallback: () => playerStartSneakEvent.subscribe(this.onPlayerStartSneakBound), onDisableCallback: () => playerStartSneakEvent.unsubscribe(this.onPlayerStartSneakBound) diff --git a/Canopy [BP]/scripts/src/rules/potionBoostedBreeding.js b/Canopy[BP]/scripts/src/rules/potionBoostedBreeding.js similarity index 97% rename from Canopy [BP]/scripts/src/rules/potionBoostedBreeding.js rename to Canopy[BP]/scripts/src/rules/potionBoostedBreeding.js index 36de10e3..dddf66b4 100644 --- a/Canopy [BP]/scripts/src/rules/potionBoostedBreeding.js +++ b/Canopy[BP]/scripts/src/rules/potionBoostedBreeding.js @@ -10,6 +10,7 @@ export class PotionBoostedBreeding extends BooleanRule { constructor() { super(GlobalRule.morphOptions({ identifier: 'potionBoostedBreeding', + wikiDescription: 'Reintroduces the behavior that allows speed potions to affect breeding attributes in animals.', onEnableCallback: () => this.subscribeToEvents(), onDisableCallback: () => this.unsubscribeFromEvents() })); diff --git a/Canopy [BP]/scripts/src/rules/quickFillContainer.js b/Canopy[BP]/scripts/src/rules/quickFillContainer.js similarity index 95% rename from Canopy [BP]/scripts/src/rules/quickFillContainer.js rename to Canopy[BP]/scripts/src/rules/quickFillContainer.js index b5f9c4c4..285c3197 100644 --- a/Canopy [BP]/scripts/src/rules/quickFillContainer.js +++ b/Canopy[BP]/scripts/src/rules/quickFillContainer.js @@ -7,6 +7,7 @@ class QuickFillContainer extends AbilityRule { constructor() { super({ identifier: 'quickFillContainer', + wikiDescription: 'With an arrow in the top left of your inventory (slot 9), using an item on a container moves all matching items from your inventory into the container. Hold sneak to reverse the flow.', onEnableCallback: () => { world.beforeEvents.playerInteractWithBlock.subscribe(this.onPlayerInteractWithBlockBound); }, onDisableCallback: () => { world.beforeEvents.playerInteractWithBlock.unsubscribe(this.onPlayerInteractWithBlockBound); } }, { slotNumber: 9 }); diff --git a/Canopy [BP]/scripts/src/rules/refillHand.js b/Canopy[BP]/scripts/src/rules/refillHand.js similarity index 92% rename from Canopy [BP]/scripts/src/rules/refillHand.js rename to Canopy[BP]/scripts/src/rules/refillHand.js index d23c645a..b0c79c48 100644 --- a/Canopy [BP]/scripts/src/rules/refillHand.js +++ b/Canopy[BP]/scripts/src/rules/refillHand.js @@ -5,6 +5,7 @@ class RefillHand extends AbilityRule { constructor() { super({ identifier: 'refillHand', + wikiDescription: 'Replenishes your hand from your inventory when you run out of the item you are holding. Place an arrow in slot 10 (the slot to the right of the top left) to activate.', onEnableCallback: () => this.subscribeToEvents(), onDisableCallback: () => this.unsubscribeFromEvents() }, { slotNumber: 10 }); diff --git a/Canopy[BP]/scripts/src/rules/renderEndGatewayExits.js b/Canopy[BP]/scripts/src/rules/renderEndGatewayExits.js new file mode 100644 index 00000000..d4f5bed9 --- /dev/null +++ b/Canopy[BP]/scripts/src/rules/renderEndGatewayExits.js @@ -0,0 +1,30 @@ +import { EndGatewayExitFinder } from '../classes/EndGatewayExitFinder'; +import { BooleanRule, GlobalRule } from '../../lib/canopy/Canopy'; + +export class RenderEndGatewayExits extends BooleanRule { + endGatewayExitFinder; + + constructor() { + super(GlobalRule.morphOptions({ + identifier: 'renderEndGatewayExits', + wikiDescription: 'Renders the exit locations of end gateways after passing through them. The green outline represents the area around the exit that is checked for a valid End Stone block to move to if the exit becomes invalid. If there is no valid End Stone in the area, the exit will generate a small platform of End Stone below it to ensure it remains valid. tl;dr - if you remove all the End Stone in the box and place just one inside the box, the exit will move to that location.', + onEnableCallback: () => this.start(), + onDisableCallback: () => this.stop() + })); + } + + start() { + this.stop(); + this.endGatewayExitFinder = new EndGatewayExitFinder(); + this.endGatewayExitFinder.start(); + } + + stop() { + if (this.endGatewayExitFinder) { + this.endGatewayExitFinder.destroy(); + this.endGatewayExitFinder = void 0; + } + } +} + +export const renderEndGatewayExits = new RenderEndGatewayExits(); \ No newline at end of file diff --git a/Canopy [BP]/scripts/src/rules/renewableElytraDropChance.js b/Canopy[BP]/scripts/src/rules/renewableElytraDropChance.js similarity index 84% rename from Canopy [BP]/scripts/src/rules/renewableElytraDropChance.js rename to Canopy[BP]/scripts/src/rules/renewableElytraDropChance.js index 14052e0e..6ec127ea 100644 --- a/Canopy [BP]/scripts/src/rules/renewableElytraDropChance.js +++ b/Canopy[BP]/scripts/src/rules/renewableElytraDropChance.js @@ -5,6 +5,8 @@ class RenewableElytraDropChance extends FloatRule { constructor() { super(GlobalRule.morphOptions({ identifier: 'renewableElytraDropChance', + wikiDescription: 'Gives phantoms a chance to drop elytra when killed by a shulker bullet. Set to `0` to disable.', + suggestedOptions: [0, 0.01, 0.2, 1], defaultValue: 0, valueRange: { range: { min: 0, max: 1 } } })); diff --git a/Canopy [BP]/scripts/src/rules/renewableSponge.js b/Canopy[BP]/scripts/src/rules/renewableSponge.js similarity index 77% rename from Canopy [BP]/scripts/src/rules/renewableSponge.js rename to Canopy[BP]/scripts/src/rules/renewableSponge.js index c924f638..cfec2706 100644 --- a/Canopy [BP]/scripts/src/rules/renewableSponge.js +++ b/Canopy[BP]/scripts/src/rules/renewableSponge.js @@ -4,7 +4,8 @@ import { world } from "@minecraft/server"; new BooleanRule({ category: 'Rules', identifier: 'renewableSponge', - description: { translate: 'rules.renewableSponge' } + description: { translate: 'rules.renewableSponge' }, + wikiDescription: 'When enabled, guardians struck by lightning will transform into elder guardians.' }); world.afterEvents.entityHurt.subscribe((event) => { diff --git a/Canopy [BP]/scripts/src/rules/serverSideCollisionBoxes.js b/Canopy[BP]/scripts/src/rules/serverSideCollisionBoxes.js similarity index 81% rename from Canopy [BP]/scripts/src/rules/serverSideCollisionBoxes.js rename to Canopy[BP]/scripts/src/rules/serverSideCollisionBoxes.js index c0251f2a..626939a1 100644 --- a/Canopy [BP]/scripts/src/rules/serverSideCollisionBoxes.js +++ b/Canopy[BP]/scripts/src/rules/serverSideCollisionBoxes.js @@ -8,6 +8,7 @@ export class ServerSideCollisionBoxes extends BooleanRule { constructor() { super(GlobalRule.morphOptions({ identifier: 'serverSideCollisionBoxes', + wikiDescription: 'Renders entity collision boxes based on their server-side position rather than their client-side position. Useful for debugging desyncs.', onEnableCallback: () => { collisionBoxes.refresh(); DebugDisplay.refreshAllElements(); diff --git a/Canopy [BP]/scripts/src/rules/spawnEggSpawnWithMinecart.js b/Canopy[BP]/scripts/src/rules/spawnEggSpawnWithMinecart.js similarity index 91% rename from Canopy [BP]/scripts/src/rules/spawnEggSpawnWithMinecart.js rename to Canopy[BP]/scripts/src/rules/spawnEggSpawnWithMinecart.js index 61c5d778..8ae10cfb 100644 --- a/Canopy [BP]/scripts/src/rules/spawnEggSpawnWithMinecart.js +++ b/Canopy[BP]/scripts/src/rules/spawnEggSpawnWithMinecart.js @@ -8,6 +8,7 @@ export class SpawnEggSpawnWithMinecart extends BooleanRule { constructor() { super(GlobalRule.morphOptions({ identifier: 'spawnEggSpawnWithMinecart', + wikiDescription: 'When using a spawn egg on a rail, the spawned entity will be placed inside a minecart on that rail.', onEnableCallback: () => this.subscribeToEvent(), onDisableCallback: () => this.unsubscribeFromEvent() })); diff --git a/Canopy [BP]/scripts/src/rules/tntFuse.js b/Canopy[BP]/scripts/src/rules/tntFuse.js similarity index 90% rename from Canopy [BP]/scripts/src/rules/tntFuse.js rename to Canopy[BP]/scripts/src/rules/tntFuse.js index a8ba5989..37bee97a 100644 --- a/Canopy [BP]/scripts/src/rules/tntFuse.js +++ b/Canopy[BP]/scripts/src/rules/tntFuse.js @@ -6,6 +6,8 @@ class TNTFuseRule extends IntegerRule { constructor() { super(GlobalRule.morphOptions({ identifier: 'tntFuse', + wikiDescription: 'Sets the TNT fuse time (in ticks) for all TNT.', + suggestedOptions: [1, 10, 80, 200, 72000], defaultValue: TNTFuse.VANILLA_FUSE_TICKS, valueRange: { range: { min: 1, max: 72000 } } })); diff --git a/Canopy [BP]/scripts/src/rules/tntPrimeMomentum.js b/Canopy[BP]/scripts/src/rules/tntPrimeMomentum.js similarity index 90% rename from Canopy [BP]/scripts/src/rules/tntPrimeMomentum.js rename to Canopy[BP]/scripts/src/rules/tntPrimeMomentum.js index 36062afb..6da0dd9c 100644 --- a/Canopy [BP]/scripts/src/rules/tntPrimeMomentum.js +++ b/Canopy[BP]/scripts/src/rules/tntPrimeMomentum.js @@ -5,6 +5,8 @@ class TNTPrimeMomentum extends FloatRule { constructor() { super(GlobalRule.morphOptions({ identifier: 'tntPrimeMomentum', + wikiDescription: 'When enabled, gives all TNT a fixed priming momentum in a random direction. Set to `-1` to disable. Useful for testing TNT-based contraptions in a controlled environment.', + suggestedOptions: [-1, 0, 0.0196], defaultValue: -1, valueRange: { range: { min: 0, max: 0.0196 }, other: [-1] } // Max from vanilla TNT: 49/2500 })); diff --git a/Canopy [BP]/structures/bubble_column.mcstructure b/Canopy[BP]/structures/bubble_column.mcstructure similarity index 100% rename from Canopy [BP]/structures/bubble_column.mcstructure rename to Canopy[BP]/structures/bubble_column.mcstructure diff --git a/Canopy [RP]/manifest.json b/Canopy[RP]/manifest.json similarity index 63% rename from Canopy [RP]/manifest.json rename to Canopy[RP]/manifest.json index 10b81bb7..25c70314 100644 --- a/Canopy [RP]/manifest.json +++ b/Canopy[RP]/manifest.json @@ -1,32 +1,50 @@ { "format_version": 2, "header": { - "name": "Canopy [RP] v1.5.5", + "name": "Canopy [RP] v1.5.6", "description": "Technical informatics & features addon by §aForestOfLight§r.", "uuid": "bcf34368-ed0c-4cf7-938e-582cccf9950d", - "version": [1, 5, 5], - "min_engine_version": [1, 26, 10] + "version": [ + 1, + 5, + 6 + ], + "min_engine_version": [ + 1, + 26, + 20 + ] }, "modules": [ { "type": "resources", "uuid": "7f6b23df-a583-476b-b0e4-87457e65f7c0", - "version": [1, 0, 0] + "version": [ + 1, + 0, + 0 + ] } ], "dependencies": [ { "uuid": "7f6b23df-a583-476b-b0e4-87457e65f7c0", - "version": [1, 5, 5] + "version": [ + 1, + 5, + 6 + ] } ], "capabilities": [ "pbr" ], "metadata": { - "authors": [ "ForestOfLight" ], + "authors": [ + "ForestOfLight" + ], "license": "MIT", "url": "https://github.com/ForestOfLight/Canopy", "product_type": "addon" } -} \ No newline at end of file +} diff --git a/Canopy [RP]/pack_icon.png b/Canopy[RP]/pack_icon.png similarity index 100% rename from Canopy [RP]/pack_icon.png rename to Canopy[RP]/pack_icon.png diff --git a/Canopy [RP]/particles/fortress_hss_marker.particle.json b/Canopy[RP]/particles/fortress_hss_marker.particle.json similarity index 100% rename from Canopy [RP]/particles/fortress_hss_marker.particle.json rename to Canopy[RP]/particles/fortress_hss_marker.particle.json diff --git a/Canopy [RP]/particles/monument_hss_marker.particle.json b/Canopy[RP]/particles/monument_hss_marker.particle.json similarity index 100% rename from Canopy [RP]/particles/monument_hss_marker.particle.json rename to Canopy[RP]/particles/monument_hss_marker.particle.json diff --git a/Canopy [RP]/particles/pillager_outpost_hss_marker.particle.json b/Canopy[RP]/particles/pillager_outpost_hss_marker.particle.json similarity index 100% rename from Canopy [RP]/particles/pillager_outpost_hss_marker.particle.json rename to Canopy[RP]/particles/pillager_outpost_hss_marker.particle.json diff --git a/Canopy [RP]/particles/swamp_hut_hss_marker.particle.json b/Canopy[RP]/particles/swamp_hut_hss_marker.particle.json similarity index 100% rename from Canopy [RP]/particles/swamp_hut_hss_marker.particle.json rename to Canopy[RP]/particles/swamp_hut_hss_marker.particle.json diff --git a/Canopy [RP]/texts/cy_GB.lang b/Canopy[RP]/texts/cy_GB.lang similarity index 100% rename from Canopy [RP]/texts/cy_GB.lang rename to Canopy[RP]/texts/cy_GB.lang diff --git a/Canopy [RP]/texts/de_DE.lang b/Canopy[RP]/texts/de_DE.lang similarity index 100% rename from Canopy [RP]/texts/de_DE.lang rename to Canopy[RP]/texts/de_DE.lang diff --git a/Canopy [RP]/texts/en_US.lang b/Canopy[RP]/texts/en_US.lang similarity index 98% rename from Canopy [RP]/texts/en_US.lang rename to Canopy[RP]/texts/en_US.lang index a970e166..e764b2b5 100644 --- a/Canopy [RP]/texts/en_US.lang +++ b/Canopy[RP]/texts/en_US.lang @@ -89,6 +89,7 @@ commands.cleanup.success=§7Cleaned up %1 entities (r%2). commands.counter=Manages hopper counters. (Alias: ct) commands.counter.channel.notfound=§cInvalid color: %s. Please use one of the wool block colors. commands.counter.query=Displays the count and rates of the hopper counter for the specified color. +commands.counter.query.all=Displays the count and rates of all hopper counters. commands.counter.query.empty=§7There are no hopper counters in use. commands.counter.query.channel=§7Items for %1§7 (%2%3 min.), total: §f%4§7, (§f%5§7): commands.counter.realtime=Displays count and rates, but uses real-world time instead of tick-based time. @@ -151,6 +152,7 @@ commands.gamemode.sp=Set your gamemode to spectator. commands.generator=Manages hopper generators. (Alias: gt) commands.generator.channel.notfound=§cInvalid color: %s. Please use one of the wool block colors. +commands.generator.query.all=Displays the count and rates of all hopper generators. commands.generator.query=Displays the count and rates of the hopper generator for the specified color. commands.generator.query.empty=§7There are no hopper generators in use. commands.generator.query.channel=§7Generated items for %1§7 (%2%3 min.), total: §f%4§7, (§f%5§7): @@ -395,6 +397,7 @@ rules.quickFillContainer=Using an item on a container with an arrow in inventory rules.quickFillContainer.filled=§7Filled %1 with all %2 rules.quickFillContainer.taken=§7Took all %1 from %2 rules.refillHand=Refills your hand with items from your inventory when you run out. Put an arrow in inventory slot 10 (next to the top left) to use. +rules.renderEndGatewayExits=Renders the exit locations of end gateways after passing through them. rules.renewableElytraDropChance=Gives phantoms a chance to drop elytra when killed by a shulker bullet. rules.renewableSponge=Guardians transform into elder guardians when hurt by lightning. rules.serverSideCollisionBoxes=Renders collision boxes according to the entity's server position instead of its client position. @@ -415,6 +418,8 @@ rules.infoDisplay.entities.display=Entities: %s rules.infoDisplay.eventTrackers=Shows the counts of tracked events. rules.infoDisplay.facing=Shows your facing direction using yaw and pitch. rules.infoDisplay.facing.display=Facing: %1 %2 +rules.infoDisplay.heldItemDurability=Shows the durability of the item in your main hand. +rules.infoDisplay.heldItemDurability.display=Durability %s rules.infoDisplay.hopperCounterCounts=Shows all active hopper counter counts in their respective colors. Hopper counter mode controls this info. rules.infoDisplay.light=Shows the light level of the block where your foot is. rules.infoDisplay.light.display=Light: %1 §r(%2 sky§r) @@ -431,6 +436,7 @@ rules.infoDisplay.moonPhase.waxingCrescent=Waxing Crescent Moon rules.infoDisplay.moonPhase.waxingGibbous=Waxing Gibbous Moon rules.infoDisplay.peekInventory=Shows the inventory of the block or entity you are targeting. rules.infoDisplay.peekInventory.empty=Empty +rules.infoDisplay.renderSignalStrength=Renders signal strength values on top of nearby redstone dust. rules.infoDisplay.sessionTime=Shows the time since you joined the world. rules.infoDisplay.sessionTime.display=Session: %s rules.infoDisplay.signalStrength=Shows the signal strength of the block you are targeting. diff --git a/Canopy [RP]/texts/id_ID.lang b/Canopy[RP]/texts/id_ID.lang similarity index 100% rename from Canopy [RP]/texts/id_ID.lang rename to Canopy[RP]/texts/id_ID.lang diff --git a/Canopy [RP]/texts/ja_JP.lang b/Canopy[RP]/texts/ja_JP.lang similarity index 98% rename from Canopy [RP]/texts/ja_JP.lang rename to Canopy[RP]/texts/ja_JP.lang index 7e3e292c..3a523b63 100644 --- a/Canopy [RP]/texts/ja_JP.lang +++ b/Canopy[RP]/texts/ja_JP.lang @@ -367,6 +367,7 @@ rules.carefulBreak=スニークしながらブロックを破壊したときに rules.cauldronConcreteConversion=コンクリートパウダーアイテムを水入り大釜の中に入れると、一定時間経過後にコンクリートアイテムに変換されます。 rules.chunkBorders=チャンク境界を表示します。インベントリスロット13(上段中央)に矢を入れると有効になります。 rules.collisionBoxes=エンティティの当たり判定を表示します。インベントリスロット14(上段中央の右隣)に矢を入れると有効になります。 +rules.creativeHotbarSwitching=複数のホットバーを素早く切り替えられるようにします。インベントリスロット17(右上)に矢を入れ、スニークしながらスクロールして切り替えます。 rules.creativeInstantTame=クリエイティブモードで、対応する食べ物を使って動物を即座に手懐けられるようにします。 rules.creativeNetherWaterPlacement=クリエイティブモードで、ネザーに水を置けるようにします。 rules.creativeNoTileDrops=クリエイティブモードで、破壊したブロックからアイテムがドロップしなくなります。 @@ -376,14 +377,16 @@ rules.durabilityNotifier=ツールの耐久値が残り %s になったときに rules.durabilityNotifier.alert=§c残り耐久値: %s rules.durabilitySwap=耐久値が0になったアイテムを手から外します。 rules.echoShardsEnableShriekers=スカルクシュリーカーに残響の欠片を使用するとウォーデンを召喚できるようにします。 +rules.enderPearlChunkLoading=エンダーパールが周囲の指定されたチャンク半径(正方形)をティックできるようにします。 rules.entityInstantDeath=20gtの死亡アニメーションを削除します。エンティティは経験値もドロップしなくなります。 +rules.entitySeparation=スタックされたエンティティが感圧板を踏んだとき、隣接するドロッパーが向いている方向に1体が分離されます。 rules.explosionChainReactionOnly=爆発がTNTブロックのみに影響するようにします。 rules.explosionNoBlockDamage=爆発がブロックに影響しないようにします。 rules.explosionOff=爆発を完全に無効にします。 rules.flippinArrows=矢をブロックに使うと、反転・回転・開閉できるようにします。矢をオフハンドに持つと、向きを反転してブロックを設置できます。 -rules.creativeHotbarSwitching=複数のホットバーを素早く切り替えられるようにします。インベントリスロット17(右上)に矢を入れ、スニークしながらスクロールして切り替えます。 rules.instaminableDeepslate=ネザライト、効率強化V、採掘速度上昇IIを使用して深層岩を即座に壊せるようにします。 rules.instaminableEndstone=ネザライト、効率強化V、採掘速度上昇IIを使用してエンドストーンを即座に壊せるようにします。 +rules.minecartChunkLoading=トロッコがスポーンしてから10秒間、周囲の指定されたチャンク半径(正方形)をティックできるようにします。 rules.noWelcomeMessage=§lCanopy§r§8 のウェルカムメッセージを無効にします。 rules.pistonBedrockBreaking=ピストンが岩盤ブロックから離れる方向に伸びたとき、岩盤を壊せるようにします。 rules.playerSit=素早く %s 回スニークするとプレイヤーが座れるようになります。 @@ -394,10 +397,10 @@ rules.quickFillContainer.taken=§7%2 からすべての %1 を取り出しまし rules.refillHand=持っているアイテムが無くなったときにインベントリから補充します。インベントリスロット10(左上の右隣)に矢を入れると有効になります。 rules.renewableElytraDropChance=ファントムがシュルカーの弾で倒されると、確率でエリトラをドロップします。 rules.renewableSponge=ガーディアンが雷に打たれるとエルダーガーディアンに変化します。 +rules.serverSideCollisionBoxes=エンティティの当たり判定ボックスを、クライアント座標ではなくサーバー座標に基づいて描画します。 rules.spawnEggSpawnWithMinecart=レールの上でスポーンエッグを使用すると、スポーンしたエンティティがトロッコに乗った状態で現れます。 rules.tntFuse=TNTの起爆時間(ティック単位)。 rules.tntPrimeMomentum=TNT着火時の運動量を固定値にします。 -rules.universalChunkLoading=トロッコがスポーンしたとき、周囲5x5チャンクを10秒間読み込むようにします。 rules.infoDisplay.biome=現在の座標のバイオームを表示します。 rules.infoDisplay.blockStates=ターゲット中のブロックの状態を表示します。 @@ -447,4 +450,4 @@ rules.infoDisplay.velocity=現在のx, y, z速度をメートル毎チック[m/t rules.infoDisplay.weather=現在のディメンションの天気を表示します。 rules.infoDisplay.weather.display=天気: %s rules.infoDisplay.worldDay=ワールド開始からのMinecraft日数を表示します。 -rules.infoDisplay.worldDay.display=日数: %s +rules.infoDisplay.worldDay.display=日数: %s \ No newline at end of file diff --git a/Canopy [RP]/texts/languages.json b/Canopy[RP]/texts/languages.json similarity index 100% rename from Canopy [RP]/texts/languages.json rename to Canopy[RP]/texts/languages.json diff --git a/Canopy [RP]/texts/zh_CN.lang b/Canopy[RP]/texts/zh_CN.lang similarity index 100% rename from Canopy [RP]/texts/zh_CN.lang rename to Canopy[RP]/texts/zh_CN.lang diff --git a/Canopy [RP]/textures/particle/fortress_hss_marker.png b/Canopy[RP]/textures/particle/fortress_hss_marker.png similarity index 100% rename from Canopy [RP]/textures/particle/fortress_hss_marker.png rename to Canopy[RP]/textures/particle/fortress_hss_marker.png diff --git a/Canopy [RP]/textures/particle/ocean_monument_hss_marker.png b/Canopy[RP]/textures/particle/ocean_monument_hss_marker.png similarity index 100% rename from Canopy [RP]/textures/particle/ocean_monument_hss_marker.png rename to Canopy[RP]/textures/particle/ocean_monument_hss_marker.png diff --git a/Canopy [RP]/textures/particle/outpost_hss_marker.png b/Canopy[RP]/textures/particle/outpost_hss_marker.png similarity index 100% rename from Canopy [RP]/textures/particle/outpost_hss_marker.png rename to Canopy[RP]/textures/particle/outpost_hss_marker.png diff --git a/Canopy [RP]/textures/particle/witch_hut_hss_marker.png b/Canopy[RP]/textures/particle/witch_hut_hss_marker.png similarity index 100% rename from Canopy [RP]/textures/particle/witch_hut_hss_marker.png rename to Canopy[RP]/textures/particle/witch_hut_hss_marker.png diff --git a/Canopy [RP]/textures/ui/d_b.json b/Canopy[RP]/textures/ui/d_b.json similarity index 100% rename from Canopy [RP]/textures/ui/d_b.json rename to Canopy[RP]/textures/ui/d_b.json diff --git a/Canopy [RP]/textures/ui/d_b.png b/Canopy[RP]/textures/ui/d_b.png similarity index 100% rename from Canopy [RP]/textures/ui/d_b.png rename to Canopy[RP]/textures/ui/d_b.png diff --git a/Canopy [RP]/textures/ui/d_g.json b/Canopy[RP]/textures/ui/d_g.json similarity index 100% rename from Canopy [RP]/textures/ui/d_g.json rename to Canopy[RP]/textures/ui/d_g.json diff --git a/Canopy [RP]/textures/ui/d_g.png b/Canopy[RP]/textures/ui/d_g.png similarity index 100% rename from Canopy [RP]/textures/ui/d_g.png rename to Canopy[RP]/textures/ui/d_g.png diff --git a/Canopy [RP]/textures/ui/item_background.json b/Canopy[RP]/textures/ui/item_background.json similarity index 100% rename from Canopy [RP]/textures/ui/item_background.json rename to Canopy[RP]/textures/ui/item_background.json diff --git a/Canopy [RP]/textures/ui/item_background.png b/Canopy[RP]/textures/ui/item_background.png similarity index 100% rename from Canopy [RP]/textures/ui/item_background.png rename to Canopy[RP]/textures/ui/item_background.png diff --git a/Canopy [RP]/ui/_global_variables.json b/Canopy[RP]/ui/_global_variables.json similarity index 100% rename from Canopy [RP]/ui/_global_variables.json rename to Canopy[RP]/ui/_global_variables.json diff --git a/Canopy [RP]/ui/_ui_defs.json b/Canopy[RP]/ui/_ui_defs.json similarity index 100% rename from Canopy [RP]/ui/_ui_defs.json rename to Canopy[RP]/ui/_ui_defs.json diff --git a/Canopy [RP]/ui/chest_inventory_system.json b/Canopy[RP]/ui/chest_inventory_system.json similarity index 100% rename from Canopy [RP]/ui/chest_inventory_system.json rename to Canopy[RP]/ui/chest_inventory_system.json diff --git a/Canopy [RP]/ui/chest_server_form.json b/Canopy[RP]/ui/chest_server_form.json similarity index 100% rename from Canopy [RP]/ui/chest_server_form.json rename to Canopy[RP]/ui/chest_server_form.json diff --git a/Canopy [RP]/ui/furnace_server_form.json b/Canopy[RP]/ui/furnace_server_form.json similarity index 100% rename from Canopy [RP]/ui/furnace_server_form.json rename to Canopy[RP]/ui/furnace_server_form.json diff --git a/Canopy [RP]/ui/hud_screen.json b/Canopy[RP]/ui/hud_screen.json similarity index 100% rename from Canopy [RP]/ui/hud_screen.json rename to Canopy[RP]/ui/hud_screen.json diff --git a/Canopy [RP]/ui/server_form.json b/Canopy[RP]/ui/server_form.json similarity index 100% rename from Canopy [RP]/ui/server_form.json rename to Canopy[RP]/ui/server_form.json diff --git a/README.md b/README.md index b96d3ae7..9dcc0956 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![GitHub Downloads](https://img.shields.io/github/downloads/ForestOfLight/Canopy/total?label=Github%20downloads&logo=github)](https://github.com/ForestOfLight/Canopy/releases/latest) [![Curseforge Downloads](https://cf.way2muchnoise.eu/full_1062078_downloads.svg)](https://www.curseforge.com/minecraft-bedrock/addons/canopy) - [![Minecraft - Version](https://img.shields.io/badge/Minecraft-v26.0_(Bedrock)-brightgreen)](https://feedback.minecraft.net/hc/en-us/sections/360001186971-Release-Changelogs) + [![Minecraft - Version](https://img.shields.io/badge/Minecraft-v26.20_(Bedrock)-brightgreen)](https://feedback.minecraft.net/hc/en-us/sections/360001186971-Release-Changelogs) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/d674d2720001423a9590dcaa6e7edbaf)](https://app.codacy.com/gh/ForestOfLight/Canopy/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [![CI](https://github.com/ForestOfLight/Canopy/actions/workflows/ci.yml/badge.svg)](https://github.com/ForestOfLight/Canopy/actions/workflows/ci.yml) [![Discord](https://badgen.net/discord/members/9KGche8fxm?icon=discord&label=Discord&list=what)](https://discord.gg/9KGche8fxm) @@ -47,19 +47,7 @@ Need help, want to discuss technical Minecraft, or follow future updates? [**Joi ## Contributing -We welcome community contributions! If you’re passionate about improving the technical Bedrock Edition experience, here’s how you can help: - -### Reporting Bugs - -Found a bug? Let us know by opening an [issue](https://github.com/ForestOfLight/Canopy/issues) with clear steps to reproduce it. - -### Submitting Code - -Got a fix or a new feature? Submit a **pull request**! Just follow our coding standards and include tests where possible. - -### Adding Translations - -**Canopy** currently supports American English, German (thanks to [theonlytruemuck](https://www.github.com/theonlytruemuck)), Indonesian (thanks to [IdotIcom](https://www.github.com/IdotIcom)), Chinese (thanks to [TickPoints](https://www.github.com/TickPoints)), Welsh (thanks to [Firebee45](https://www.github.com/Firebee45)), and Japanese (thanks to [ru-in-1](https://www.github.com/ru-in-1)). If you would like to contribute a translation, please join our Discord and reach out! +We welcome community contributions! If you’re passionate about improving the technical Bedrock Edition experience, see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on reporting bugs, submitting code, and adding translations. ## An Amelix Foundation Project diff --git a/__mocks__/@minecraft/server-ui.js b/__mocks__/@minecraft/server-ui.js deleted file mode 100644 index 9437843a..00000000 --- a/__mocks__/@minecraft/server-ui.js +++ /dev/null @@ -1,8 +0,0 @@ -export const FormCancelationReason = { - UserBusy: "UserBusy", - UserClosed: "UserClosed", -} -export const uiManager = { - closeAllForms: () => {} -} -export const ActionFormData = {}; \ No newline at end of file diff --git a/__mocks__/@minecraft/server.js b/__mocks__/@minecraft/server.js deleted file mode 100644 index 9c452a37..00000000 --- a/__mocks__/@minecraft/server.js +++ /dev/null @@ -1,11 +0,0 @@ -export const world = {}; -export const system = {}; -export const ItemStack = {}; -export const DimensionTypes = {}; -export const ScriptEventSource = {}; -export const InputButton = {}; -export const ButtonState = {}; -export const EntityComponentTypes = {}; -export const ItemComponentTypes = {}; -export const TicksPerSecond = 20.0; -export const Player = class {}; \ No newline at end of file diff --git a/__tests__/BP/scripts/include/data.test.js b/__tests__/BP/scripts/include/data.test.js index 7571b3ea..da30daa5 100644 --- a/__tests__/BP/scripts/include/data.test.js +++ b/__tests__/BP/scripts/include/data.test.js @@ -1,19 +1,9 @@ -import { describe, it, expect, vi } from 'vitest'; +import { describe, it, expect } from 'vitest'; import axios from 'axios'; -import { MC_VERSION } from '../../../../Canopy [BP]/scripts/constants.js'; -import { categoryToMobMap, meleeMobs } from '../../../../Canopy [BP]/scripts/include/data.js'; +import { MC_VERSION } from '../../../../Canopy[BP]/scripts/constants.js'; +import { categoryToMobMap, meleeMobs } from '../../../../Canopy[BP]/scripts/include/data.js'; import stripJsonComments from 'strip-json-comments'; -vi.mock('@minecraft/server', { - world: {}, - ItemStack: {}, - DimensionTypes: {} -}); - -vi.mock("@minecraft/server-ui", () => ({ - ModalFormData: vi.fn() -})); - const bedrockSamplesRawUrl = `https://raw.githubusercontent.com/Mojang/bedrock-samples/refs/tags/v${MC_VERSION}/`; describe.concurrent('categoryToMobMap', () => { diff --git a/__tests__/BP/scripts/include/utils.test.js b/__tests__/BP/scripts/include/utils.test.js index 3d9f5a95..41b982c3 100644 --- a/__tests__/BP/scripts/include/utils.test.js +++ b/__tests__/BP/scripts/include/utils.test.js @@ -3,17 +3,8 @@ import { calcDistance, isString, isNumeric, parseName, getClosestTarget, stringifyLocation, getColorCode, wait, getInventory, locationInArea, getColoredDimensionName, getScriptEventSourceName, getScriptEventSourceObject, recolor, titleCase, formatColorStr -} from '../../../../Canopy [BP]/scripts/include/utils.js'; +} from '../../../../Canopy[BP]/scripts/include/utils.js'; -vi.mock('@minecraft/server', { - world: {}, - ItemStack: {}, - DimensionTypes: {} -}); - -vi.mock("@minecraft/server-ui", () => ({ - ModalFormData: vi.fn() -})); describe('calcDistance()', () => { it.each([ diff --git a/__tests__/BP/scripts/lib/canopy/Canopy.test.js b/__tests__/BP/scripts/lib/canopy/Canopy.test.js index fa3339fa..04265799 100644 --- a/__tests__/BP/scripts/lib/canopy/Canopy.test.js +++ b/__tests__/BP/scripts/lib/canopy/Canopy.test.js @@ -1,32 +1,5 @@ -import { describe, it, expect, vi } from "vitest"; -import * as Canopy from "../../../../../Canopy [BP]/scripts/lib/canopy/Canopy"; - -vi.mock('@minecraft/server', () => ({ - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() - } - }, - afterEvents: { - worldLoad: { - subscribe: vi.fn() - } - } - }, - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } - }, - runJob: vi.fn() - } -})); - -vi.mock("@minecraft/server-ui", () => ({ - ModalFormData: vi.fn() -})); +import { describe, it, expect } from "vitest"; +import * as Canopy from "../../../../../Canopy[BP]/scripts/lib/canopy/Canopy"; describe('Canopy module', () => { it('should export Commands', () => { diff --git a/__tests__/BP/scripts/lib/canopy/Extension.test.js b/__tests__/BP/scripts/lib/canopy/Extension.test.js index bc7d8129..f0f22feb 100644 --- a/__tests__/BP/scripts/lib/canopy/Extension.test.js +++ b/__tests__/BP/scripts/lib/canopy/Extension.test.js @@ -1,30 +1,10 @@ -import { describe, it, expect, vi, beforeAll, afterAll } from 'vitest'; -import { Extension } from '../../../../../Canopy [BP]/scripts/lib/canopy/Extension.js'; -import { Command } from '../../../../../Canopy [BP]/scripts/lib/canopy/commands/Command.js'; -import { BooleanRule } from '../../../../../Canopy [BP]/scripts/lib/canopy/rules/BooleanRule.js'; - -vi.mock('@minecraft/server', () => ({ - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() - } - }, - afterEvents: { - worldLoad: { - subscribe: vi.fn() - } - } - }, - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } - }, - runJob: vi.fn() - } -})); +import { describe, it, expect, vi, beforeAll, afterAll, beforeEach, afterEach } from 'vitest'; +import { Extension } from '../../../../../Canopy[BP]/scripts/lib/canopy/Extension.js'; +import { Command } from '../../../../../Canopy[BP]/scripts/lib/canopy/commands/Command.js'; +import { BooleanRule } from '../../../../../Canopy[BP]/scripts/lib/canopy/rules/BooleanRule.js'; +import IPC from '../../../../../Canopy[BP]/scripts/lib/MCBE-IPC/ipc.js'; +import { Commands } from '../../../../../Canopy[BP]/scripts/lib/canopy/commands/Commands.js'; +import { Rules } from '../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rules.js'; describe('Extension', () => { const extensionData = { @@ -147,6 +127,86 @@ describe('Extension', () => { }); }); + describe('runCommand', () => { + it('should call sender.runCommand when isEndstone is true', () => { + const endstoneExt = new Extension({ name: 'Endstone Ext', version: '1.0.0', author: 'Author', description: 'Test', isEndstone: true }); + const sender = { runCommand: vi.fn() }; + endstoneExt.runCommand(sender, 'myCmd', { arg1: 'val1' }); + expect(sender.runCommand).toHaveBeenCalledWith('myCmd val1'); + }); + }); + + describe('IPC registration callbacks', () => { + let ipcExt; + let capturedCallbacks; + + beforeEach(() => { + capturedCallbacks = {}; + Commands.clear(); + Rules.clear(); + vi.spyOn(IPC, 'on').mockImplementation((channel, _deserializer, callback) => { + capturedCallbacks[channel] = callback; + }); + ipcExt = new Extension({ name: 'IPC Test Ext', version: '1.0.0', author: 'Author', description: 'Test' }); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should push a Command when the registerCommand IPC message is received', () => { + capturedCallbacks['canopyExtension:ipc_test_ext:registerCommand']({ + name: 'ipc_cmd', + usage: 'ipc_cmd' + }); + expect(ipcExt.getCommands().length).toBe(1); + }); + + it('should push a BooleanRule when registerRule receives type boolean', () => { + capturedCallbacks['canopyExtension:ipc_test_ext:registerRule']({ + identifier: 'ipc_bool_rule', + type: 'boolean', + defaultValue: 'false', + description: 'test' + }); + expect(ipcExt.getRules().length).toBe(1); + expect(ipcExt.getRules()[0].getType()).toBe('boolean'); + }); + + it('should push an IntegerRule when registerRule receives type integer', () => { + capturedCallbacks['canopyExtension:ipc_test_ext:registerRule']({ + identifier: 'ipc_int_rule', + type: 'integer', + defaultValue: '5', + description: 'test', + valueRange: { range: { min: 0, max: 10 } } + }); + expect(ipcExt.getRules().length).toBe(1); + expect(ipcExt.getRules()[0].getType()).toBe('integer'); + }); + + it('should push a FloatRule when registerRule receives type float', () => { + capturedCallbacks['canopyExtension:ipc_test_ext:registerRule']({ + identifier: 'ipc_float_rule', + type: 'float', + defaultValue: '0.5', + description: 'test', + valueRange: { range: { min: 0.0, max: 1.0 } } + }); + expect(ipcExt.getRules().length).toBe(1); + expect(ipcExt.getRules()[0].getType()).toBe('float'); + }); + + it('should throw for an invalid rule type', () => { + expect(() => capturedCallbacks['canopyExtension:ipc_test_ext:registerRule']({ + identifier: 'ipc_bad_rule', + type: 'invalid', + defaultValue: 'x', + description: 'test' + })).toThrow('[Canopy] Could not register rule: ipc_bad_rule. Invalid data type.'); + }); + }); + describe('makeID()', () => { const warn = console.warn; beforeAll(() => { diff --git a/__tests__/BP/scripts/lib/canopy/Extensions.test.js b/__tests__/BP/scripts/lib/canopy/Extensions.test.js index 0531c734..ff33b6ac 100644 --- a/__tests__/BP/scripts/lib/canopy/Extensions.test.js +++ b/__tests__/BP/scripts/lib/canopy/Extensions.test.js @@ -1,31 +1,31 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; -import { Extensions } from "../../../../../Canopy [BP]/scripts/lib/canopy/Extensions.js"; -import { Extension } from "../../../../../Canopy [BP]/scripts/lib/canopy/Extension.js"; - -vi.mock("@minecraft/server", () => ({ - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() - } - }, - afterEvents: { - worldLoad: { - subscribe: vi.fn() - } +import { describe, it, expect, vi, beforeAll, beforeEach } from "vitest"; + +vi.mock('../../../../../Canopy[BP]/scripts/lib/MCBE-IPC/ipc.js', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + default: { + ...actual.default, + on: vi.fn(), + send: vi.fn(), + invoke: vi.fn(), + handle: vi.fn(), } - }, - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } - }, - runJob: vi.fn() - } -})); + }; +}); + +import { Extensions } from "../../../../../Canopy[BP]/scripts/lib/canopy/Extensions.js"; +import { Extension } from "../../../../../Canopy[BP]/scripts/lib/canopy/Extension.js"; +import IPC from "../../../../../Canopy[BP]/scripts/lib/MCBE-IPC/ipc.js"; describe("Extensions", () => { + let registerCallback; + + beforeAll(() => { + const registerCall = IPC.on.mock.calls.find(([channel]) => channel === 'canopyExtension:registerExtension'); + registerCallback = registerCall?.[2]; + }); + beforeEach(() => { Extensions.clear(); Extensions.extensions["test_extension"] = new Extension({ @@ -101,7 +101,17 @@ describe("Extensions", () => { }); }); - describe.skip('setupExtensionRegistration()', () => { - // Gametest + describe('setupExtensionRegistration()', () => { + it('should register an extension and log when the IPC message is received', () => { + const callback = registerCallback; + + Extensions.clear(); + const consoleSpy = vi.spyOn(console, 'info').mockImplementation(() => {}); + callback({ name: 'Remote Ext', version: '2.0.0', author: 'Dev', description: 'Remote' }); + + expect(Extensions.get('remote_ext')).toBeDefined(); + expect(consoleSpy).toHaveBeenCalledWith('[Canopy] Registered Remote Ext v2.0.0.'); + consoleSpy.mockRestore(); + }); }); -}); \ No newline at end of file +}); diff --git a/__tests__/BP/scripts/lib/canopy/commands/ArgumentParser.test.js b/__tests__/BP/scripts/lib/canopy/commands/ArgumentParser.test.js index 2609870b..5598876a 100644 --- a/__tests__/BP/scripts/lib/canopy/commands/ArgumentParser.test.js +++ b/__tests__/BP/scripts/lib/canopy/commands/ArgumentParser.test.js @@ -1,28 +1,5 @@ -import { describe, it, expect, vi } from 'vitest'; -import { ArgumentParser } from '../../../../../../Canopy [BP]/scripts/lib/canopy/commands/ArgumentParser.js'; - -vi.mock("@minecraft/server", () => ({ - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() - } - }, - afterEvents: { - worldLoad: { - subscribe: vi.fn() - } - } - }, - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } - }, - runJob: vi.fn() - } -})); +import { describe, it, expect } from 'vitest'; +import { ArgumentParser } from '../../../../../../Canopy[BP]/scripts/lib/canopy/commands/ArgumentParser.js'; describe('ArgumentParser', () => { describe('parseCommandString', () => { @@ -103,6 +80,15 @@ describe('ArgumentParser', () => { }); }); + it('should parse a command string with a float argument', () => { + const commandString = 'command 1.5'; + const result = ArgumentParser.parseCommandString(commandString); + expect(result).toEqual({ + name: 'command', + args: [1.5] + }); + }); + it('should be able to parse args back to their command string', () => { const commandString = 'command true 42 "test string" [1,2,3] "@e[type=creeper]"'; const result = ArgumentParser.parseCommandString(commandString); diff --git a/__tests__/BP/scripts/lib/canopy/commands/BlockCommandOrigin.test.js b/__tests__/BP/scripts/lib/canopy/commands/BlockCommandOrigin.test.js index dfb75166..4d35bfd8 100644 --- a/__tests__/BP/scripts/lib/canopy/commands/BlockCommandOrigin.test.js +++ b/__tests__/BP/scripts/lib/canopy/commands/BlockCommandOrigin.test.js @@ -1,4 +1,4 @@ -import { BlockCommandOrigin } from "../../../../../../Canopy [BP]/scripts/lib/canopy/commands/BlockCommandOrigin"; +import { BlockCommandOrigin } from "../../../../../../Canopy[BP]/scripts/lib/canopy/commands/BlockCommandOrigin"; import { beforeEach, describe, expect, it } from "vitest"; describe("BlockCommandOrigin", () => { diff --git a/__tests__/BP/scripts/lib/canopy/commands/Command.test.js b/__tests__/BP/scripts/lib/canopy/commands/Command.test.js index 905ad028..a847717f 100644 --- a/__tests__/BP/scripts/lib/canopy/commands/Command.test.js +++ b/__tests__/BP/scripts/lib/canopy/commands/Command.test.js @@ -1,33 +1,10 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; -import { Command } from "../../../../../../Canopy [BP]/scripts/lib/canopy/commands/Command"; -import { Commands } from "../../../../../../Canopy [BP]/scripts/lib/canopy/commands/Commands"; -import IPC from "../../../../../../Canopy [BP]/scripts/lib/MCBE-IPC/ipc"; -import { Extension } from "../../../../../../Canopy [BP]/scripts/lib/canopy/Extension"; -import { Extensions } from "../../../../../../Canopy [BP]/scripts/lib/canopy/Extensions"; -import { CommandCallbackRequest } from "../../../../../../Canopy [BP]/scripts/lib/canopy/extension.ipc"; - -vi.mock("@minecraft/server", () => ({ - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() - } - }, - afterEvents: { - worldLoad: { - subscribe: vi.fn() - } - } - }, - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } - }, - runJob: vi.fn() - } -})); +import { Command } from "../../../../../../Canopy[BP]/scripts/lib/canopy/commands/Command"; +import { Commands } from "../../../../../../Canopy[BP]/scripts/lib/canopy/commands/Commands"; +import IPC from "../../../../../../Canopy[BP]/scripts/lib/MCBE-IPC/ipc"; +import { Extension } from "../../../../../../Canopy[BP]/scripts/lib/canopy/Extension"; +import { Extensions } from "../../../../../../Canopy[BP]/scripts/lib/canopy/Extensions"; +import { CommandCallbackRequest } from "../../../../../../Canopy[BP]/scripts/lib/canopy/extension.ipc"; describe("Command", () => { beforeEach(() => { diff --git a/__tests__/BP/scripts/lib/canopy/commands/CommandOrigin.test.js b/__tests__/BP/scripts/lib/canopy/commands/CommandOrigin.test.js index 43ade319..9e51b12e 100644 --- a/__tests__/BP/scripts/lib/canopy/commands/CommandOrigin.test.js +++ b/__tests__/BP/scripts/lib/canopy/commands/CommandOrigin.test.js @@ -1,6 +1,6 @@ -import { CommandOrigin } from "../../../../../../Canopy [BP]/scripts/lib/canopy/commands/CommandOrigin"; +import { CommandOrigin } from "../../../../../../Canopy[BP]/scripts/lib/canopy/commands/CommandOrigin"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { FeedbackMessageType } from "../../../../../../Canopy [BP]/scripts/lib/canopy/commands/FeedbackMessageType"; +import { FeedbackMessageType } from "../../../../../../Canopy[BP]/scripts/lib/canopy/commands/FeedbackMessageType"; describe("CommandOrigin", () => { let mockOrigin; diff --git a/__tests__/BP/scripts/lib/canopy/commands/Commands.test.js b/__tests__/BP/scripts/lib/canopy/commands/Commands.test.js index e89f8b05..5a3d17dd 100644 --- a/__tests__/BP/scripts/lib/canopy/commands/Commands.test.js +++ b/__tests__/BP/scripts/lib/canopy/commands/Commands.test.js @@ -1,42 +1,35 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; -import { Commands } from "../../../../../../Canopy [BP]/scripts/lib/canopy/commands/Commands"; -import { BooleanRule } from "../../../../../../Canopy [BP]/scripts/lib/canopy/rules/BooleanRule"; -import { Rules } from "../../../../../../Canopy [BP]/scripts/lib/canopy/rules/Rules"; - -vi.mock("@minecraft/server", () => ({ - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() +import { describe, it, expect, vi, beforeAll, beforeEach } from "vitest"; +import { Commands } from "../../../../../../Canopy[BP]/scripts/lib/canopy/commands/Commands"; +import { BooleanRule } from "../../../../../../Canopy[BP]/scripts/lib/canopy/rules/BooleanRule"; +import { Rules } from "../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rules"; +import { world } from "@minecraft/server"; + +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + world: { + ...original.world, + afterEvents: { + ...original.world.afterEvents, + worldLoad: { subscribe: (callback) => callback() } } }, - afterEvents: { - worldLoad: { - subscribe: (callback) => { - callback(); - } - } - } - }, - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } + system: { + ...original.system, + run: (callback) => callback() }, - runJob: vi.fn(), - run: (callback) => callback() - }, - CommandPermissionLevel: { - Any: 0, - GameDirectors: 1, - Admin: 2, - Host: 3, - Owner: 4 - } -})); + CommandPermissionLevel: { Any: 0, GameDirectors: 1, Admin: 2, Host: 3, Owner: 4 } + }; +}); describe('Commands', () => { + let chatCallback; + + beforeAll(() => { + chatCallback = world.beforeEvents.chatSend.subscribe.mock.calls[0][0]; + }); + beforeEach(() => { Commands.clear(); }); @@ -211,6 +204,20 @@ describe('Commands', () => { expect(command.runCallback).toHaveBeenCalledWith(sender, { strArg: null, boolArg: null, intArg: null, floatArg: null, entityArg: null, arrayArg: null, playerArg: null }); }); + it('should return null for a multi-type arg when no type matches', async () => { + const command2 = { name: 'test2', + getName: () => 'test2', + isOpOnly: () => false, + getContingentRules: () => ['rule1'], + getArgs: () => [ + { type: 'boolean|integer', name: 'multiArg' } + ], + runCallback: vi.fn() }; + Commands.register(command2); + await Commands.executeCommand(sender, 'test2', [{}]); + expect(command2.runCallback).toHaveBeenCalledWith(sender, { multiArg: null }); + }); + it('should interpret any multiarguments', async () => { const command2 = { name: 'test2', getName: () => 'test2', @@ -276,9 +283,9 @@ describe('Commands', () => { }); it('should interpret the player argument without spaces', async () => { - const command2 = { name: 'test2', - getName: () => 'test2', - isOpOnly: () => false, + const command2 = { name: 'test2', + getName: () => 'test2', + isOpOnly: () => false, getContingentRules: () => ['rule1'], getArgs: () => [ { type: 'string|boolean|integer|identifier', name: 'multiArg' } @@ -289,4 +296,34 @@ describe('Commands', () => { expect(command2.runCallback).toHaveBeenCalledWith(sender, { multiArg: '@player' }); }); }); + + describe('chatSend event handler', () => { + let chatSender; + + beforeEach(() => { + Commands.clear(); + Rules.clear(); + chatSender = { sendMessage: vi.fn(), commandPermissionLevel: 1 }; + }); + + it('should cancel and execute the command when message starts with the prefix', () => { + const command = { + getName: () => 'test', + isOpOnly: () => false, + getContingentRules: () => [], + getArgs: () => [], + runCallback: vi.fn() + }; + Commands.register(command); + const event = { sender: chatSender, message: './test', cancel: false }; + chatCallback(event); + expect(event.cancel).toBe(true); + }); + + it('should not cancel when the message does not start with the prefix', () => { + const event = { sender: chatSender, message: 'hello world', cancel: false }; + chatCallback(event); + expect(event.cancel).toBe(false); + }); + }); }); \ No newline at end of file diff --git a/__tests__/BP/scripts/lib/canopy/commands/EntityCommandOrigin.test.js b/__tests__/BP/scripts/lib/canopy/commands/EntityCommandOrigin.test.js index 81241db4..10eeebfd 100644 --- a/__tests__/BP/scripts/lib/canopy/commands/EntityCommandOrigin.test.js +++ b/__tests__/BP/scripts/lib/canopy/commands/EntityCommandOrigin.test.js @@ -1,4 +1,4 @@ -import { EntityCommandOrigin } from "../../../../../../Canopy [BP]/scripts/lib/canopy/commands/EntityCommandOrigin"; +import { EntityCommandOrigin } from "../../../../../../Canopy[BP]/scripts/lib/canopy/commands/EntityCommandOrigin"; import { beforeEach, describe, expect, it } from "vitest"; describe("EntityCommandOrigin", () => { diff --git a/__tests__/BP/scripts/lib/canopy/commands/PlayerCommandOrigin.test.js b/__tests__/BP/scripts/lib/canopy/commands/PlayerCommandOrigin.test.js index 73ad48cf..4e566aec 100644 --- a/__tests__/BP/scripts/lib/canopy/commands/PlayerCommandOrigin.test.js +++ b/__tests__/BP/scripts/lib/canopy/commands/PlayerCommandOrigin.test.js @@ -1,5 +1,5 @@ -import { FeedbackMessageType } from "../../../../../../Canopy [BP]/scripts/lib/canopy/commands/FeedbackMessageType"; -import { PlayerCommandOrigin } from "../../../../../../Canopy [BP]/scripts/lib/canopy/commands/PlayerCommandOrigin"; +import { FeedbackMessageType } from "../../../../../../Canopy[BP]/scripts/lib/canopy/commands/FeedbackMessageType"; +import { PlayerCommandOrigin } from "../../../../../../Canopy[BP]/scripts/lib/canopy/commands/PlayerCommandOrigin"; import { beforeEach, describe, expect, it, vi } from "vitest"; describe("PlayerCommandOrigin", () => { diff --git a/__tests__/BP/scripts/lib/canopy/commands/ServerCommandOrigin.test.js b/__tests__/BP/scripts/lib/canopy/commands/ServerCommandOrigin.test.js index f96880de..cdc80945 100644 --- a/__tests__/BP/scripts/lib/canopy/commands/ServerCommandOrigin.test.js +++ b/__tests__/BP/scripts/lib/canopy/commands/ServerCommandOrigin.test.js @@ -1,4 +1,4 @@ -import { ServerCommandOrigin } from "../../../../../../Canopy [BP]/scripts/lib/canopy/commands/ServerCommandOrigin"; +import { ServerCommandOrigin } from "../../../../../../Canopy[BP]/scripts/lib/canopy/commands/ServerCommandOrigin"; import { beforeEach, describe, expect, it } from "vitest"; describe("ServerCommandOrigin", () => { diff --git a/__tests__/BP/scripts/lib/canopy/commands/VanillaCommand.test.js b/__tests__/BP/scripts/lib/canopy/commands/VanillaCommand.test.js index 4ea2c265..26da6609 100644 --- a/__tests__/BP/scripts/lib/canopy/commands/VanillaCommand.test.js +++ b/__tests__/BP/scripts/lib/canopy/commands/VanillaCommand.test.js @@ -1,12 +1,12 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; -import { VanillaCommand } from "../../../../../../Canopy [BP]/scripts/lib/canopy/commands/VanillaCommand"; -import { BooleanRule } from "../../../../../../Canopy [BP]/scripts/lib/canopy/rules/BooleanRule"; -import { Rules } from "../../../../../../Canopy [BP]/scripts/lib/canopy/rules/Rules"; -import { FeedbackMessageType } from "../../../../../../Canopy [BP]/scripts/lib/canopy/commands/FeedbackMessageType"; -import { BlockCommandOrigin } from "../../../../../../Canopy [BP]/scripts/lib/canopy/commands/BlockCommandOrigin"; -import { EntityCommandOrigin } from "../../../../../../Canopy [BP]/scripts/lib/canopy/commands/EntityCommandOrigin"; -import { ServerCommandOrigin } from "../../../../../../Canopy [BP]/scripts/lib/canopy/commands/ServerCommandOrigin"; -import { PlayerCommandOrigin } from "../../../../../../Canopy [BP]/scripts/lib/canopy/commands/PlayerCommandOrigin"; +import { VanillaCommand } from "../../../../../../Canopy[BP]/scripts/lib/canopy/commands/VanillaCommand"; +import { BooleanRule } from "../../../../../../Canopy[BP]/scripts/lib/canopy/rules/BooleanRule"; +import { Rules } from "../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rules"; +import { FeedbackMessageType } from "../../../../../../Canopy[BP]/scripts/lib/canopy/commands/FeedbackMessageType"; +import { BlockCommandOrigin } from "../../../../../../Canopy[BP]/scripts/lib/canopy/commands/BlockCommandOrigin"; +import { EntityCommandOrigin } from "../../../../../../Canopy[BP]/scripts/lib/canopy/commands/EntityCommandOrigin"; +import { ServerCommandOrigin } from "../../../../../../Canopy[BP]/scripts/lib/canopy/commands/ServerCommandOrigin"; +import { PlayerCommandOrigin } from "../../../../../../Canopy[BP]/scripts/lib/canopy/commands/PlayerCommandOrigin"; import { Player } from "@minecraft/server"; const mockCustomCommandRegistry = { @@ -14,50 +14,28 @@ const mockCustomCommandRegistry = { registerEnum: vi.fn() }; -vi.mock("@minecraft/server", () => ({ - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + world: { + ...original.world, + afterEvents: { + ...original.world.afterEvents, + worldLoad: { subscribe: (callback) => callback() } } }, - afterEvents: { - worldLoad: { - subscribe: (callback) => { - callback(); + system: { + ...original.system, + beforeEvents: { + startup: { + subscribe: () => ({ customCommandRegistry: mockCustomCommandRegistry }), + unsubscribe: vi.fn() } } - }, - setDynamicProperty: vi.fn(), - getDynamicProperty: vi.fn() - }, - system: { - beforeEvents: { - startup: { - subscribe: () => ({ customCommandRegistry: mockCustomCommandRegistry }), - unsubscribe: vi.fn() - } - }, - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } - }, - runJob: vi.fn() - }, - CustomCommandSource: { - Block: "Block", - Entity: "Entity", - Server: "Server" - }, - CustomCommandStatus: { - Failure: "Failure", - Success: "Success" - }, - Block: class {}, - Entity: class {}, - Player: class { sendMessage = vi.fn() } -})); + } + }; +}); describe("VanillaCommand", () => { let mockCommand; diff --git a/__tests__/BP/scripts/lib/canopy/commands/VanillaCommands.test.js b/__tests__/BP/scripts/lib/canopy/commands/VanillaCommands.test.js new file mode 100644 index 00000000..3567885b --- /dev/null +++ b/__tests__/BP/scripts/lib/canopy/commands/VanillaCommands.test.js @@ -0,0 +1,50 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { VanillaCommands } from '../../../../../../Canopy[BP]/scripts/lib/canopy/commands/VanillaCommands.js'; +import { VanillaCommand } from '../../../../../../Canopy[BP]/scripts/lib/canopy/commands/VanillaCommand.js'; + +describe('VanillaCommands registry', () => { + beforeEach(() => { + VanillaCommands.clear(); + }); + + it('should register a VanillaCommand on construction', () => { + new VanillaCommand({ name: 'canopy:testcmd', description: 'test', callback: vi.fn() }); + expect(VanillaCommands.getAll()).toHaveLength(1); + }); + + it('should return all registered commands', () => { + new VanillaCommand({ name: 'canopy:cmd1', description: 'a', callback: vi.fn() }); + new VanillaCommand({ name: 'canopy:cmd2', description: 'b', callback: vi.fn() }); + expect(VanillaCommands.getAll()).toHaveLength(2); + }); + + it('should clear all commands', () => { + new VanillaCommand({ name: 'canopy:clearcmd', description: 'x', callback: vi.fn() }); + VanillaCommands.clear(); + expect(VanillaCommands.getAll()).toHaveLength(0); + }); +}); + +describe('VanillaCommand.getName', () => { + beforeEach(() => { VanillaCommands.clear(); }); + + it('should return the name without the namespace prefix', () => { + const cmd = new VanillaCommand({ name: 'canopy:biomeedges', description: 'test', callback: vi.fn() }); + expect(cmd.getName()).toBe('biomeedges'); + }); +}); + +describe('VanillaCommand.getSubCommandWikiDescription', () => { + beforeEach(() => { VanillaCommands.clear(); }); + + it('should return the subCommandWikiDescription map when provided', () => { + const sub = { 'add': { description: 'Adds.', params: ['from', 'to'] } }; + const cmd = new VanillaCommand({ name: 'canopy:test', description: 'x', subCommandWikiDescription: sub, callback: vi.fn() }); + expect(cmd.getSubCommandWikiDescription()).toEqual(sub); + }); + + it('should return an empty object when not provided', () => { + const cmd = new VanillaCommand({ name: 'canopy:plain', description: 'x', callback: vi.fn() }); + expect(cmd.getSubCommandWikiDescription()).toEqual({}); + }); +}); diff --git a/__tests__/BP/scripts/lib/canopy/help/CommandHelpEntry.test.js b/__tests__/BP/scripts/lib/canopy/help/CommandHelpEntry.test.js index b02eb09f..cb4ea446 100644 --- a/__tests__/BP/scripts/lib/canopy/help/CommandHelpEntry.test.js +++ b/__tests__/BP/scripts/lib/canopy/help/CommandHelpEntry.test.js @@ -1,29 +1,6 @@ -import { describe, it, expect, vi } from "vitest"; -import { CommandHelpEntry } from "../../../../../../Canopy [BP]/scripts/lib/canopy/help/CommandHelpEntry"; -import { Commands } from "../../../../../../Canopy [BP]/scripts/lib/canopy/commands/Commands"; - -vi.mock('@minecraft/server', () => ({ - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() - } - }, - afterEvents: { - worldLoad: { - subscribe: vi.fn() - } - } - }, - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } - }, - runJob: vi.fn() - } -})); +import { describe, it, expect } from "vitest"; +import { CommandHelpEntry } from "../../../../../../Canopy[BP]/scripts/lib/canopy/help/CommandHelpEntry"; +import { Commands } from "../../../../../../Canopy[BP]/scripts/lib/canopy/commands/Commands"; describe('CommandHelpEntry', () => { const mockCommand = { diff --git a/__tests__/BP/scripts/lib/canopy/help/CommandHelpPage.test.js b/__tests__/BP/scripts/lib/canopy/help/CommandHelpPage.test.js index b19c9f90..9449880f 100644 --- a/__tests__/BP/scripts/lib/canopy/help/CommandHelpPage.test.js +++ b/__tests__/BP/scripts/lib/canopy/help/CommandHelpPage.test.js @@ -1,31 +1,8 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; -import { CommandHelpPage } from "../../../../../../Canopy [BP]/scripts/lib/canopy/help/CommandHelpPage"; -import { CommandHelpEntry } from "../../../../../../Canopy [BP]/scripts/lib/canopy/help/CommandHelpEntry"; -import { Command } from "../../../../../../Canopy [BP]/scripts/lib/canopy/commands/Command"; -import { Commands } from "../../../../../../Canopy [BP]/scripts/lib/canopy/commands/Commands"; - -vi.mock("@minecraft/server", () => ({ - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() - } - }, - afterEvents: { - worldLoad: { - subscribe: vi.fn() - } - } - }, - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } - }, - runJob: vi.fn() - } -})); +import { describe, it, expect, beforeEach } from "vitest"; +import { CommandHelpPage } from "../../../../../../Canopy[BP]/scripts/lib/canopy/help/CommandHelpPage"; +import { CommandHelpEntry } from "../../../../../../Canopy[BP]/scripts/lib/canopy/help/CommandHelpEntry"; +import { Command } from "../../../../../../Canopy[BP]/scripts/lib/canopy/commands/Command"; +import { Commands } from "../../../../../../Canopy[BP]/scripts/lib/canopy/commands/Commands"; describe('CommandHelpPage', () => { describe('constructor', () => { diff --git a/__tests__/BP/scripts/lib/canopy/help/HelpBook.test.js b/__tests__/BP/scripts/lib/canopy/help/HelpBook.test.js index ec8d0104..5af61858 100644 --- a/__tests__/BP/scripts/lib/canopy/help/HelpBook.test.js +++ b/__tests__/BP/scripts/lib/canopy/help/HelpBook.test.js @@ -1,42 +1,14 @@ import { describe, it, expect, beforeEach, vi } from "vitest"; -import { HelpBook } from "../../../../../../Canopy [BP]/scripts/lib/canopy/help/HelpBook"; -import { RuleHelpPage } from "../../../../../../Canopy [BP]/scripts/lib/canopy/help/RuleHelpPage"; -import { BooleanRule } from "../../../../../../Canopy [BP]/scripts/lib/canopy/rules/BooleanRule"; -import { Rules } from "../../../../../../Canopy [BP]/scripts/lib/canopy/rules/Rules"; -import { CommandHelpPage } from "../../../../../../Canopy [BP]/scripts/lib/canopy/help/CommandHelpPage"; -import { Command } from "../../../../../../Canopy [BP]/scripts/lib/canopy/commands/Command"; -import { Commands } from "../../../../../../Canopy [BP]/scripts/lib/canopy/commands/Commands"; - -vi.mock("@minecraft/server", () => ({ - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() - } - }, - afterEvents: { - worldLoad: { - subscribe: vi.fn() - } - }, - getDynamicProperty: vi.fn() - }, - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } - }, - runJob: vi.fn() - } -})); - -vi.mock("@minecraft/server-ui", () => ({ - ModalFormData: vi.fn() -})); +import { HelpBook } from "../../../../../../Canopy[BP]/scripts/lib/canopy/help/HelpBook"; +import { RuleHelpPage } from "../../../../../../Canopy[BP]/scripts/lib/canopy/help/RuleHelpPage"; +import { BooleanRule } from "../../../../../../Canopy[BP]/scripts/lib/canopy/rules/BooleanRule"; +import { Rules } from "../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rules"; +import { CommandHelpPage } from "../../../../../../Canopy[BP]/scripts/lib/canopy/help/CommandHelpPage"; +import { Command } from "../../../../../../Canopy[BP]/scripts/lib/canopy/commands/Command"; +import { Commands } from "../../../../../../Canopy[BP]/scripts/lib/canopy/commands/Commands"; // Terrible practice. This is a workaround for a Vitest rollup error that causes the Rule class to not be imported properly in BooleanRule. -vi.mock('../../../../../../Canopy [BP]/scripts/lib/canopy/rules/Rule', () => ({ +vi.mock('../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rule', () => ({ Rule: class Rule { #category; #identifier; diff --git a/__tests__/BP/scripts/lib/canopy/help/HelpEntry.test.js b/__tests__/BP/scripts/lib/canopy/help/HelpEntry.test.js index 026a493c..9c02674b 100644 --- a/__tests__/BP/scripts/lib/canopy/help/HelpEntry.test.js +++ b/__tests__/BP/scripts/lib/canopy/help/HelpEntry.test.js @@ -1,6 +1,5 @@ -/* eslint-disable max-classes-per-file */ import { describe, it, expect } from "vitest"; -import { HelpEntry } from "../../../../../../Canopy [BP]/scripts/lib/canopy/help/HelpEntry"; +import { HelpEntry } from "../../../../../../Canopy[BP]/scripts/lib/canopy/help/HelpEntry"; describe('HelpEntry', () => { it('should throw an error when instantiated directly', () => { diff --git a/__tests__/BP/scripts/lib/canopy/help/HelpPage.test.js b/__tests__/BP/scripts/lib/canopy/help/HelpPage.test.js index 8e26f49f..de45dfc8 100644 --- a/__tests__/BP/scripts/lib/canopy/help/HelpPage.test.js +++ b/__tests__/BP/scripts/lib/canopy/help/HelpPage.test.js @@ -1,6 +1,5 @@ -/* eslint-disable max-classes-per-file */ import { describe, it, expect } from 'vitest'; -import { HelpPage } from '../../../../../../Canopy [BP]/scripts/lib/canopy/help/HelpPage'; +import { HelpPage } from '../../../../../../Canopy[BP]/scripts/lib/canopy/help/HelpPage'; describe('HelpPage', () => { describe('constructor', () => { diff --git a/__tests__/BP/scripts/lib/canopy/help/InfoDisplayRuleHelpEntry.test.js b/__tests__/BP/scripts/lib/canopy/help/InfoDisplayRuleHelpEntry.test.js index d9a9d504..182d7d88 100644 --- a/__tests__/BP/scripts/lib/canopy/help/InfoDisplayRuleHelpEntry.test.js +++ b/__tests__/BP/scripts/lib/canopy/help/InfoDisplayRuleHelpEntry.test.js @@ -1,30 +1,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { InfoDisplayRuleHelpEntry } from '../../../../../../Canopy [BP]/scripts/lib/canopy/help/InfoDisplayRuleHelpEntry'; -import { InfoDisplayRule } from '../../../../../../Canopy [BP]/scripts/lib/canopy/rules/InfoDisplayRule'; -import { Rules } from '../../../../../../Canopy [BP]/scripts/lib/canopy/rules/Rules'; - -vi.mock('@minecraft/server', () => ({ - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() - } - }, - afterEvents: { - worldLoad: { - subscribe: vi.fn() - } - } - }, - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } - }, - runJob: vi.fn() - } -})); +import { InfoDisplayRuleHelpEntry } from '../../../../../../Canopy[BP]/scripts/lib/canopy/help/InfoDisplayRuleHelpEntry'; +import { InfoDisplayRule } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/InfoDisplayRule'; +import { Rules } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rules'; describe('InfoDisplayRuleHelpEntry', () => { let entry; diff --git a/__tests__/BP/scripts/lib/canopy/help/InfoDisplayRuleHelpPage.test.js b/__tests__/BP/scripts/lib/canopy/help/InfoDisplayRuleHelpPage.test.js index b8522f3f..561e4daa 100644 --- a/__tests__/BP/scripts/lib/canopy/help/InfoDisplayRuleHelpPage.test.js +++ b/__tests__/BP/scripts/lib/canopy/help/InfoDisplayRuleHelpPage.test.js @@ -1,35 +1,11 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { InfoDisplayRuleHelpPage } from '../../../../../../Canopy [BP]/scripts/lib/canopy/help/InfoDisplayRuleHelpPage'; -import { InfoDisplayRule } from '../../../../../../Canopy [BP]/scripts/lib/canopy/rules/InfoDisplayRule'; -import { InfoDisplayRuleHelpEntry } from '../../../../../../Canopy [BP]/scripts/lib/canopy/help/InfoDisplayRuleHelpEntry'; -import { Rules } from '../../../../../../Canopy [BP]/scripts/lib/canopy/rules/Rules'; - -vi.mock("@minecraft/server", () => ({ - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() - } - }, - afterEvents: { - worldLoad: { - subscribe: vi.fn() - } - }, - getDynamicProperty: vi.fn() - }, - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } - }, - runJob: vi.fn() - } -})); +import { InfoDisplayRuleHelpPage } from '../../../../../../Canopy[BP]/scripts/lib/canopy/help/InfoDisplayRuleHelpPage'; +import { InfoDisplayRule } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/InfoDisplayRule'; +import { InfoDisplayRuleHelpEntry } from '../../../../../../Canopy[BP]/scripts/lib/canopy/help/InfoDisplayRuleHelpEntry'; +import { Rules } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rules'; // Terrible practice. This is a workaround for a Vitest rollup error that causes the Rule class to not be imported properly in BooleanRule. -vi.mock('../../../../../../Canopy [BP]/scripts/lib/canopy/rules/Rule', () => ({ +vi.mock('../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rule', () => ({ Rule: class Rule { #category; #identifier; diff --git a/__tests__/BP/scripts/lib/canopy/help/RuleHelpEntry.test.js b/__tests__/BP/scripts/lib/canopy/help/RuleHelpEntry.test.js index 29344e45..b2b10f0f 100644 --- a/__tests__/BP/scripts/lib/canopy/help/RuleHelpEntry.test.js +++ b/__tests__/BP/scripts/lib/canopy/help/RuleHelpEntry.test.js @@ -1,5 +1,5 @@ import { describe, it, expect, vi } from "vitest"; -import { RuleHelpEntry } from "../../../../../../Canopy [BP]/scripts/lib/canopy/help/RuleHelpEntry"; +import { RuleHelpEntry } from "../../../../../../Canopy[BP]/scripts/lib/canopy/help/RuleHelpEntry"; describe('RuleHelpEntry', () => { describe('constructor', () => { @@ -50,5 +50,41 @@ describe('RuleHelpEntry', () => { ] }); }); + + it('should prefix the value with §u for integer type', async () => { + const mockRule = { + getID: () => 'testRule', + getDescription: () => 'An integer rule', + getValue: vi.fn().mockResolvedValue(42), + getType: () => 'integer' + }; + const entry = new RuleHelpEntry(mockRule); + const coloredValue = await entry.fetchColoredValue(); + expect(coloredValue).toBe('§u42'); + }); + + it('should prefix the value with §d for float type', async () => { + const mockRule = { + getID: () => 'testRule', + getDescription: () => 'A float rule', + getValue: vi.fn().mockResolvedValue(1.5), + getType: () => 'float' + }; + const entry = new RuleHelpEntry(mockRule); + const coloredValue = await entry.fetchColoredValue(); + expect(coloredValue).toBe('§d1.5'); + }); + + it('should return the raw value for unknown types', async () => { + const mockRule = { + getID: () => 'testRule', + getDescription: () => 'An unknown rule', + getValue: vi.fn().mockResolvedValue('someValue'), + getType: () => 'unknown' + }; + const entry = new RuleHelpEntry(mockRule); + const coloredValue = await entry.fetchColoredValue(); + expect(coloredValue).toBe('someValue'); + }); }); }); \ No newline at end of file diff --git a/__tests__/BP/scripts/lib/canopy/help/RuleHelpPage.test.js b/__tests__/BP/scripts/lib/canopy/help/RuleHelpPage.test.js index 40e3021d..7baee2e8 100644 --- a/__tests__/BP/scripts/lib/canopy/help/RuleHelpPage.test.js +++ b/__tests__/BP/scripts/lib/canopy/help/RuleHelpPage.test.js @@ -1,35 +1,11 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; -import { RuleHelpPage } from "../../../../../../Canopy [BP]/scripts/lib/canopy/help/RuleHelpPage"; -import { RuleHelpEntry } from "../../../../../../Canopy [BP]/scripts/lib/canopy/help/RuleHelpEntry"; -import { BooleanRule } from "../../../../../../Canopy [BP]/scripts/lib/canopy/rules/BooleanRule"; -import { Rules } from "../../../../../../Canopy [BP]/scripts/lib/canopy/rules/Rules"; - -vi.mock("@minecraft/server", () => ({ - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() - } - }, - afterEvents: { - worldLoad: { - subscribe: vi.fn() - } - }, - getDynamicProperty: vi.fn() - }, - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } - }, - runJob: vi.fn() - } -})); +import { RuleHelpPage } from "../../../../../../Canopy[BP]/scripts/lib/canopy/help/RuleHelpPage"; +import { RuleHelpEntry } from "../../../../../../Canopy[BP]/scripts/lib/canopy/help/RuleHelpEntry"; +import { BooleanRule } from "../../../../../../Canopy[BP]/scripts/lib/canopy/rules/BooleanRule"; +import { Rules } from "../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rules"; // Terrible practice. This is a workaround for a Vitest rollup error that causes the Rule class to not be imported properly in BooleanRule. -vi.mock('../../../../../../Canopy [BP]/scripts/lib/canopy/rules/Rule', () => ({ +vi.mock('../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rule', () => ({ Rule: class Rule { #category; #identifier; diff --git a/__tests__/BP/scripts/lib/canopy/rules/AbilityRule.test.js b/__tests__/BP/scripts/lib/canopy/rules/AbilityRule.test.js index c38183c1..bd95d126 100644 --- a/__tests__/BP/scripts/lib/canopy/rules/AbilityRule.test.js +++ b/__tests__/BP/scripts/lib/canopy/rules/AbilityRule.test.js @@ -1,37 +1,31 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; -import { AbilityRule } from "../../../../../../Canopy [BP]/scripts/lib/canopy/rules/AbilityRule"; -import { Rules } from "../../../../../../Canopy [BP]/scripts/lib/canopy/rules/Rules"; - -vi.mock('@minecraft/server', () => ({ - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() - } - }, - afterEvents: { - worldLoad: { - subscribe: vi.fn() +import { AbilityRule } from "../../../../../../Canopy[BP]/scripts/lib/canopy/rules/AbilityRule"; +import { Rules } from "../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rules"; +import { world } from "@minecraft/server"; + +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + world: { + ...original.world, + beforeEvents: { + ...original.world.beforeEvents, + playerLeave: { subscribe: vi.fn(), unsubscribe: vi.fn() }, }, - playerInventoryItemChange: { - subscribe: vi.fn(), - unsubscribe: vi.fn() - } - } - }, - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } + afterEvents: { + ...original.world.afterEvents, + playerJoin: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + }, + getAllPlayers: vi.fn().mockReturnValue([]), }, - runJob: vi.fn() - } -})); - -vi.mock("@minecraft/server-ui", () => ({ - ModalFormData: vi.fn() -})); + system: { + ...original.system, + currentTick: 0, + run: vi.fn((cb) => cb()), + } + }; +}); const onPlayerEnableCallback = vi.fn(); const onPlayerDisableCallback = vi.fn(); @@ -41,10 +35,14 @@ describe('AbilityRule', () => { const testRuleData = { category: 'Rules', identifier: 'testRule', + onEnableCallback: vi.fn(), + onDisableCallback: vi.fn(), }; beforeEach(() => { Rules.clear(); + vi.clearAllMocks(); + world.getAllPlayers.mockReturnValue([]); abilityRule = new AbilityRule(testRuleData, { slotNumber: 1, onPlayerEnableCallback, onPlayerDisableCallback }); }); @@ -69,7 +67,119 @@ describe('AbilityRule', () => { }); it('should use a custom action item if provided', () => { - const customArrowAbility = new AbilityRule(testRuleData, { slotNumber: 1, actionItem: 'minecraft:other_item'}); + const customArrowAbility = new AbilityRule(testRuleData, { slotNumber: 1, actionItem: 'minecraft:other_item' }); expect(customArrowAbility.getActionItemId()).toBe('minecraft:other_item'); }); + + it('should return early from onActionSlotItemChange when player is null', () => { + expect(() => abilityRule.onActionSlotItemChange({ player: null })).not.toThrow(); + expect(onPlayerEnableCallback).not.toHaveBeenCalled(); + }); + + it('should set isSilent to true when the player joined on the same tick', () => { + const player = { id: 'player1', onScreenDisplay: { setActionBar: vi.fn() } }; + abilityRule.playerJoinTick['player1'] = 0; + abilityRule.onActionSlotItemChange({ + itemStack: { typeId: 'minecraft:arrow' }, + player + }); + expect(player.onScreenDisplay.setActionBar).not.toHaveBeenCalled(); + expect(onPlayerEnableCallback).toHaveBeenCalled(); + }); + + it('should track enabled state per player', () => { + const player = { id: 'player1' }; + abilityRule.enableForPlayer(player); + expect(abilityRule.isEnabledForPlayer(player)).toBe(true); + abilityRule.disableForPlayer(player); + expect(abilityRule.isEnabledForPlayer(player)).toBe(false); + }); + + it('should return true when action item is in the correct slot', () => { + const player = { + getComponent: vi.fn().mockReturnValue({ + container: { getItem: vi.fn().mockReturnValue({ typeId: 'minecraft:arrow' }) } + }) + }; + expect(abilityRule.isActionItemInActionSlot(player)).toBe(true); + }); + + it('should return false when action item is not in the slot', () => { + const player = { + getComponent: vi.fn().mockReturnValue({ + container: { getItem: vi.fn().mockReturnValue(null) } + }) + }; + expect(abilityRule.isActionItemInActionSlot(player)).toBe(false); + }); + + it('should enable players that have the action item in slot when refreshed', () => { + const player = { + id: 'player1', + getComponent: vi.fn().mockReturnValue({ + container: { getItem: vi.fn().mockReturnValue({ typeId: 'minecraft:arrow' }) } + }), + onScreenDisplay: { setActionBar: vi.fn() } + }; + world.getAllPlayers.mockReturnValue([player]); + vi.spyOn(abilityRule, 'getNativeValue').mockReturnValue(true); + abilityRule.refreshOnlinePlayers(); + expect(onPlayerEnableCallback).toHaveBeenCalledWith(player); + }); + + it('should disable players that do not have the action item in slot when refreshed', () => { + const player = { + id: 'player1', + getComponent: vi.fn().mockReturnValue({ + container: { getItem: vi.fn().mockReturnValue(null) } + }), + onScreenDisplay: { setActionBar: vi.fn() } + }; + world.getAllPlayers.mockReturnValue([player]); + abilityRule.refreshOnlinePlayers(); + expect(onPlayerDisableCallback).toHaveBeenCalledWith(player); + }); + + it('should skip null players during refresh', () => { + world.getAllPlayers.mockReturnValue([null]); + expect(() => abilityRule.refreshOnlinePlayers()).not.toThrow(); + }); + + it('should subscribe to events when onEnable is called', () => { + abilityRule.onEnable(); + expect(world.afterEvents.playerInventoryItemChange.subscribe).toHaveBeenCalled(); + expect(world.afterEvents.playerJoin.subscribe).toHaveBeenCalled(); + expect(world.beforeEvents.playerLeave.subscribe).toHaveBeenCalled(); + }); + + it('should unsubscribe from events when onDisable is called', () => { + abilityRule.onDisable(); + expect(world.afterEvents.playerInventoryItemChange.unsubscribe).toHaveBeenCalled(); + expect(world.afterEvents.playerJoin.unsubscribe).toHaveBeenCalled(); + expect(world.beforeEvents.playerLeave.unsubscribe).toHaveBeenCalled(); + }); + + it('should record the player join tick', () => { + abilityRule.onPlayerJoin({ playerId: 'player1' }); + expect(abilityRule.playerJoinTick['player1']).toBe(0); + }); + + it('should disable the player on leave', () => { + const player = { id: 'player1', onScreenDisplay: { setActionBar: vi.fn() } }; + abilityRule.enableForPlayer(player); + abilityRule.onPlayerLeave({ player }); + expect(onPlayerDisableCallback).toHaveBeenCalledWith(player); + }); + + it('should return early from onPlayerLeave when player is null', () => { + expect(() => abilityRule.onPlayerLeave({ player: null })).not.toThrow(); + expect(onPlayerDisableCallback).not.toHaveBeenCalled(); + }); + + it('should use default no-op callbacks when onPlayerEnableCallback and onPlayerDisableCallback are not provided', () => { + const ruleWithDefaults = new AbilityRule({ ...testRuleData, identifier: 'default_cb_rule' }); + const player = { id: 'p1', onScreenDisplay: { setActionBar: vi.fn() } }; + expect(() => ruleWithDefaults.enableForPlayer(player, { isSilent: false })).not.toThrow(); + expect(() => ruleWithDefaults.disableForPlayer(player, { isSilent: false })).not.toThrow(); + }); }); diff --git a/__tests__/BP/scripts/lib/canopy/rules/BooleanRule.test.js b/__tests__/BP/scripts/lib/canopy/rules/BooleanRule.test.js index 29fb06b7..3f4488a3 100644 --- a/__tests__/BP/scripts/lib/canopy/rules/BooleanRule.test.js +++ b/__tests__/BP/scripts/lib/canopy/rules/BooleanRule.test.js @@ -1,41 +1,26 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { BooleanRule } from '../../../../../../Canopy [BP]/scripts/lib/canopy/rules/BooleanRule.js'; -import { Rules } from '../../../../../../Canopy [BP]/scripts/lib/canopy/rules/Rules.js'; -import IPC from '../../../../../../Canopy [BP]/scripts/lib/MCBE-IPC/ipc.js'; -import { Extensions } from '../../../../../../Canopy [BP]/scripts/lib/canopy/Extensions.js'; -import { Extension } from '../../../../../../Canopy [BP]/scripts/lib/canopy/Extension.js'; -import { RuleValueSet } from '../../../../../../Canopy [BP]/scripts/lib/canopy/extension.ipc.js'; +import { BooleanRule } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/BooleanRule.js'; +import { Rules } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rules.js'; +import IPC from '../../../../../../Canopy[BP]/scripts/lib/MCBE-IPC/ipc.js'; +import { Extensions } from '../../../../../../Canopy[BP]/scripts/lib/canopy/Extensions.js'; +import { Extension } from '../../../../../../Canopy[BP]/scripts/lib/canopy/Extension.js'; +import { RuleValueSet } from '../../../../../../Canopy[BP]/scripts/lib/canopy/extension.ipc.js'; +import { world } from '@minecraft/server'; -vi.mock('@minecraft/server', () => ({ - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() - } - }, - afterEvents: { - worldLoad: { - subscribe: (callback) => { - callback(); - } - } - }, - setDynamicProperty: vi.fn(), - getDynamicProperty: vi.fn(() => false) - }, - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } - }, - runJob: vi.fn() - } -})); - -vi.mock("@minecraft/server-ui", () => ({ - ModalFormData: vi.fn() -})); +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + world: { + ...original.world, + afterEvents: { + ...original.world.afterEvents, + worldLoad: { subscribe: (callback) => callback() } + }, + getDynamicProperty: vi.fn(() => false) + } + }; +}); describe('BooleanRule', () => { const testExtension = new Extension({ @@ -169,14 +154,30 @@ describe('BooleanRule', () => { expect(value).toEqual(false); }); - it.skip('should return the value from the world if it does not exist in the extension', async () => { - // Gametest DP + it('should return the value from the world if it does not exist in the extension', async () => { + const rule = new BooleanRule({ + category: 'test_category', + identifier: 'native_rule', + description: 'test_description', + contingentRules: [], + independentRules: [] + }); + const value = await rule.getValue(); + expect(value).toEqual(false); }); }); describe('getNativeValue', () => { - it.skip('should return the value from the world if it exists', () => { - // Gametest DP + it('should return the value from the world if it exists', () => { + const rule = new BooleanRule({ + category: 'test_category', + identifier: 'native_rule', + description: 'test_description', + contingentRules: [], + independentRules: [] + }); + const value = rule.getNativeValue(); + expect(value).toEqual(false); }); it('should throw an error if the rule is from an extension', () => { @@ -185,10 +186,19 @@ describe('BooleanRule', () => { }); describe('setValue', () => { - it.skip('should set the value in the world', () => { - // Gametest DP + it('should set the value in the world', () => { + world.setDynamicProperty = vi.fn(); + const rule = new BooleanRule({ + category: 'test_category', + identifier: 'native_set_rule', + description: 'test_description', + contingentRules: [], + independentRules: [] + }); + rule.setValue(true); + expect(world.setDynamicProperty).toHaveBeenCalledWith('native_set_rule', true); }); - + it('should call onEnable if the value is true', () => { const onEnableCallback = vi.fn(); const rule = new BooleanRule({ diff --git a/__tests__/BP/scripts/lib/canopy/rules/FloatRule.test.js b/__tests__/BP/scripts/lib/canopy/rules/FloatRule.test.js new file mode 100644 index 00000000..0b05f6fa --- /dev/null +++ b/__tests__/BP/scripts/lib/canopy/rules/FloatRule.test.js @@ -0,0 +1,88 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { FloatRule } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/FloatRule.js'; +import { Rules } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rules.js'; + +describe('FloatRule', () => { + let rule; + + beforeEach(() => { + Rules.clear(); + rule = new FloatRule({ + category: 'test', + identifier: 'test_float_rule', + description: 'Test float rule', + valueRange: { range: { min: 0.0, max: 1.0 } } + }); + }); + + describe('constructor', () => { + it('should initialize with correct properties', () => { + expect(rule.getID()).toBe('test_float_rule'); + expect(rule.getDefaultValue()).toBe(0); + }); + + it('should use the provided defaultValue', () => { + const r = new FloatRule({ + category: 'test', + identifier: 'float_with_default', + defaultValue: 0.5, + valueRange: { range: { min: 0.0, max: 1.0 } } + }); + expect(r.getDefaultValue()).toBe(0.5); + }); + }); + + describe('getDescription', () => { + it('should return a rawtext object containing the description and default value', () => { + const desc = rule.getDescription(); + expect(desc.rawtext).toBeDefined(); + expect(desc.rawtext[0]).toEqual({ text: 'Test float rule' }); + expect(desc.rawtext[2]).toEqual({ translate: 'rules.generic.defaultvalue', with: ['0'] }); + }); + }); + + describe('getType', () => { + it('should return "float"', () => { + expect(rule.getType()).toBe('float'); + }); + }); + + describe('isInDomain', () => { + it('should return true for numbers', () => { + expect(rule.isInDomain(0.5)).toBe(true); + }); + + it('should return true for integers (integers are numbers)', () => { + expect(rule.isInDomain(1)).toBe(true); + }); + + it('should return false for non-numbers', () => { + expect(rule.isInDomain('hello')).toBe(false); + }); + }); + + describe('getAllowedValues', () => { + it('should return the value range', () => { + expect(rule.getAllowedValues()).toEqual({ range: { min: 0.0, max: 1.0 } }); + }); + }); + + describe('isInRange', () => { + it('should return true for values within range', () => { + expect(rule.isInRange(0.5)).toBe(true); + }); + + it('should return false for values outside range', () => { + expect(rule.isInRange(1.5)).toBe(false); + }); + + it('should return true for values in the other array', () => { + const r = new FloatRule({ + category: 'test', + identifier: 'float_with_other', + valueRange: { range: { min: 0.0, max: 1.0 }, other: [99.9] } + }); + expect(r.isInRange(99.9)).toBe(true); + }); + }); +}); diff --git a/__tests__/BP/scripts/lib/canopy/rules/GlobalRule.test.js b/__tests__/BP/scripts/lib/canopy/rules/GlobalRule.test.js index ebf04e6e..95fd09de 100644 --- a/__tests__/BP/scripts/lib/canopy/rules/GlobalRule.test.js +++ b/__tests__/BP/scripts/lib/canopy/rules/GlobalRule.test.js @@ -1,32 +1,20 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; -import { GlobalRule } from "../../../../../../Canopy [BP]/scripts/lib/canopy/rules/GlobalRule"; -import { Rules } from "../../../../../../Canopy [BP]/scripts/lib/canopy/rules/Rules"; +import { GlobalRule } from "../../../../../../Canopy[BP]/scripts/lib/canopy/rules/GlobalRule"; +import { Rules } from "../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rules"; -vi.mock('@minecraft/server', () => ({ - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + world: { + ...original.world, + afterEvents: { + ...original.world.afterEvents, + worldLoad: { subscribe: (callback) => callback() } } - }, - afterEvents: { - worldLoad: { - subscribe: (callback) => { - callback(); - } - } - }, - getDynamicProperty: vi.fn() - }, - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } - }, - runJob: vi.fn() - } -})); + } + }; +}); describe('GlobalRule', () => { beforeEach(() => { diff --git a/__tests__/BP/scripts/lib/canopy/rules/InfoDisplayRule.test.js b/__tests__/BP/scripts/lib/canopy/rules/InfoDisplayRule.test.js index aca01ceb..a46f09e3 100644 --- a/__tests__/BP/scripts/lib/canopy/rules/InfoDisplayRule.test.js +++ b/__tests__/BP/scripts/lib/canopy/rules/InfoDisplayRule.test.js @@ -1,32 +1,21 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { Rules } from '../../../../../../Canopy [BP]/scripts/lib/canopy/rules/Rules.js'; -import { InfoDisplayRule } from '../../../../../../Canopy [BP]/scripts/lib/canopy/rules/InfoDisplayRule.js'; -import { BooleanRule } from '../../../../../../Canopy [BP]/scripts/lib/canopy/rules/BooleanRule.js'; - -vi.mock('@minecraft/server', () => ({ - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() - } - }, - afterEvents: { - worldLoad: { - subscribe: (callback) => { - callback(); - } +import { Rules } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rules.js'; +import { InfoDisplayRule } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/InfoDisplayRule.js'; +import { BooleanRule } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/BooleanRule.js'; + +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + world: { + ...original.world, + afterEvents: { + ...original.world.afterEvents, + worldLoad: { subscribe: (callback) => callback() } } } - }, - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } - }, - runJob: vi.fn() - } -})); + }; +}); describe('InfoDisplayRule', () => { let rule; @@ -145,4 +134,19 @@ describe('InfoDisplayRule', () => { expect(rules[0].getID()).toBe('test_rule'); }); }); + + describe('getGlobalContingentRuleIDs', () => { + it('should return an empty array by default', () => { + expect(rule.getGlobalContingentRuleIDs()).toEqual([]); + }); + + it('should return the provided globalContingentRules', () => { + const ruleWithGlobals = new InfoDisplayRule({ + identifier: 'global_rule', + description: 'rule with globals', + globalContingentRules: ['someRule', 'anotherRule'] + }); + expect(ruleWithGlobals.getGlobalContingentRuleIDs()).toEqual(['someRule', 'anotherRule']); + }); + }); }); \ No newline at end of file diff --git a/__tests__/BP/scripts/lib/canopy/rules/IntegerRule.test.js b/__tests__/BP/scripts/lib/canopy/rules/IntegerRule.test.js new file mode 100644 index 00000000..3a4d00b5 --- /dev/null +++ b/__tests__/BP/scripts/lib/canopy/rules/IntegerRule.test.js @@ -0,0 +1,84 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { IntegerRule } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/IntegerRule.js'; +import { Rules } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rules.js'; + +describe('IntegerRule', () => { + let rule; + + beforeEach(() => { + Rules.clear(); + rule = new IntegerRule({ + category: 'test', + identifier: 'test_int_rule', + description: 'Test integer rule', + valueRange: { range: { min: 0, max: 10 } } + }); + }); + + describe('constructor', () => { + it('should initialize with correct properties', () => { + expect(rule.getID()).toBe('test_int_rule'); + expect(rule.getDefaultValue()).toBe(0); + }); + + it('should use the provided defaultValue', () => { + const r = new IntegerRule({ + category: 'test', + identifier: 'int_with_default', + defaultValue: 5, + valueRange: { range: { min: 0, max: 10 } } + }); + expect(r.getDefaultValue()).toBe(5); + }); + }); + + describe('getDescription', () => { + it('should return a rawtext object containing the description and default value', () => { + const desc = rule.getDescription(); + expect(desc.rawtext).toBeDefined(); + expect(desc.rawtext[0]).toEqual({ text: 'Test integer rule' }); + expect(desc.rawtext[2]).toEqual({ translate: 'rules.generic.defaultvalue', with: ['0'] }); + }); + }); + + describe('getType', () => { + it('should return "integer"', () => { + expect(rule.getType()).toBe('integer'); + }); + }); + + describe('isInDomain', () => { + it('should return true for integers', () => { + expect(rule.isInDomain(5)).toBe(true); + }); + + it('should return false for non-integers', () => { + expect(rule.isInDomain(1.5)).toBe(false); + }); + }); + + describe('getAllowedValues', () => { + it('should return the value range', () => { + expect(rule.getAllowedValues()).toEqual({ range: { min: 0, max: 10 } }); + }); + }); + + describe('isInRange', () => { + it('should return true for values within range', () => { + expect(rule.isInRange(5)).toBe(true); + }); + + it('should return false for values outside range', () => { + expect(rule.isInRange(11)).toBe(false); + }); + + it('should return true for values in the other array', () => { + const r = new IntegerRule({ + category: 'test', + identifier: 'int_with_other', + valueRange: { range: { min: 0, max: 10 }, other: [100] } + }); + expect(r.isInRange(100)).toBe(true); + }); + }); +}); diff --git a/__tests__/BP/scripts/lib/canopy/rules/Rule.test.js b/__tests__/BP/scripts/lib/canopy/rules/Rule.test.js new file mode 100644 index 00000000..a446aebd --- /dev/null +++ b/__tests__/BP/scripts/lib/canopy/rules/Rule.test.js @@ -0,0 +1,112 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { Rule } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rule.js'; +import { BooleanRule } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/BooleanRule.js'; +import { IntegerRule } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/IntegerRule.js'; +import { Rules } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rules.js'; + +class ConcreteRule extends Rule {} + +describe('Rule', () => { + beforeEach(() => { + Rules.clear(); + }); + + describe('constructor', () => { + it('should throw when instantiated directly', () => { + expect(() => new Rule({ category: 'test', identifier: 'test_rule' })) + .toThrow("Abstract class 'Rule' cannot be instantiated directly."); + }); + }); + + describe('getType', () => { + it('should throw when not overridden', () => { + const rule = new ConcreteRule({ category: 'test', identifier: 'concrete_rule' }); + expect(() => rule.getType()).toThrow('[Canopy] getType() must be implemented.'); + }); + }); + + describe('isInDomain', () => { + it('should throw when not overridden', () => { + const rule = new ConcreteRule({ category: 'test', identifier: 'concrete_rule' }); + expect(() => rule.isInDomain(true)).toThrow('[Canopy] isInDomain() must be implemented.'); + }); + }); + + describe('isInRange', () => { + it('should throw when not overridden', () => { + const rule = new ConcreteRule({ category: 'test', identifier: 'concrete_rule' }); + expect(() => rule.isInRange(true)).toThrow('[Canopy] isInRange() must be implemented.'); + }); + }); + + describe('setValue', () => { + it('should throw when the value is not in the domain', () => { + const rule = new BooleanRule({ category: 'test', identifier: 'domain_check_rule' }); + expect(() => rule.setValue('not_a_boolean')).toThrow('[Canopy] Incorrect value type for rule'); + }); + + it('should throw when the value is in domain but out of range', () => { + const rule = new IntegerRule({ + category: 'test', + identifier: 'range_check_rule', + valueRange: { range: { min: 0, max: 10 } } + }); + expect(() => rule.setValue(100)).toThrow('[Canopy] Value out of range for rule'); + }); + + it('should use a no-op default for onModifyCallback', () => { + const rule = new IntegerRule({ + category: 'test', + identifier: 'default_modify_rule', + valueRange: { range: { min: 0, max: 10 } } + }); + expect(() => rule.setValue(5)).not.toThrow(); + }); + }); + + describe('getValue', () => { + it('should return NaN when the stored value is the string "NaN"', async () => { + const { world } = await import('@minecraft/server'); + world.getDynamicProperty.mockReturnValue('NaN'); + const rule = new BooleanRule({ category: 'test', identifier: 'nan_rule' }); + const value = await rule.getValue(); + expect(value).toBeNaN(); + }); + + it('should throw when the stored value is unparseable non-NaN', async () => { + const { world } = await import('@minecraft/server'); + world.getDynamicProperty.mockReturnValue('not-valid-json{'); + const rule = new BooleanRule({ category: 'test', identifier: 'bad_json_rule' }); + await expect(rule.getValue()).rejects.toThrow('Failed to parse value for DynamicProperty'); + }); + }); + + describe('getWikiDescription', () => { + it('should return the wikiDescription when provided', () => { + const rule = new BooleanRule({ category: 'Rules', identifier: 'wiki_desc_rule', wikiDescription: 'My **wiki** description.' }); + expect(rule.getWikiDescription()).toBe('My **wiki** description.'); + }); + + it('should return undefined when wikiDescription is not provided', () => { + const rule = new BooleanRule({ category: 'Rules', identifier: 'no_wiki_desc_rule' }); + expect(rule.getWikiDescription()).toBeUndefined(); + }); + }); + + describe('getSuggestedOptions', () => { + it('should return suggestedOptions when provided', () => { + const rule = new IntegerRule({ category: 'Rules', identifier: 'opts_rule', suggestedOptions: [1, 10, 80], valueRange: { range: { min: 1, max: 80 } } }); + expect(rule.getSuggestedOptions()).toEqual([1, 10, 80]); + }); + + it('should return [false, true] for BooleanRule by default', () => { + const rule = new BooleanRule({ category: 'Rules', identifier: 'bool_opts_rule' }); + expect(rule.getSuggestedOptions()).toEqual([false, true]); + }); + + it('should return undefined for IntegerRule when not provided', () => { + const rule = new IntegerRule({ category: 'Rules', identifier: 'no_opts_rule', valueRange: { range: { min: 0, max: 10 } } }); + expect(rule.getSuggestedOptions()).toBeUndefined(); + }); + }); +}); diff --git a/__tests__/BP/scripts/lib/canopy/rules/Rules.test.js b/__tests__/BP/scripts/lib/canopy/rules/Rules.test.js index f4a1d1d7..5641dbe6 100644 --- a/__tests__/BP/scripts/lib/canopy/rules/Rules.test.js +++ b/__tests__/BP/scripts/lib/canopy/rules/Rules.test.js @@ -1,36 +1,21 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; -import { Rules } from "../../../../../../Canopy [BP]/scripts/lib/canopy/rules/Rules.js"; -import { BooleanRule } from "../../../../../../Canopy [BP]/scripts/lib/canopy/rules/BooleanRule.js"; -import { Rule } from "../../../../../../Canopy [BP]/scripts/lib/canopy/Canopy.js"; - -vi.mock('@minecraft/server', () => ({ - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() - } - }, - afterEvents: { - worldLoad: { - subscribe: (callback) => { - callback(); - } +import { Rules } from "../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rules.js"; +import { BooleanRule } from "../../../../../../Canopy[BP]/scripts/lib/canopy/rules/BooleanRule.js"; +import { Rule } from "../../../../../../Canopy[BP]/scripts/lib/canopy/Canopy.js"; + +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + world: { + ...original.world, + afterEvents: { + ...original.world.afterEvents, + worldLoad: { subscribe: (callback) => callback() } } } - }, - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } - }, - runJob: vi.fn() - } -})); - -vi.mock("@minecraft/server-ui", () => ({ - ModalFormData: vi.fn() -})); + }; +}); describe('Rules', () => { let testRule; @@ -249,6 +234,47 @@ describe('Rules', () => { }); }); + describe('registerQueuedRules', () => { + it('should register all queued rules', async () => { + Rules.clear(); + const mockRule = { + getID: () => 'queued_rule', + getCategory: () => 'test', + }; + Rules.rulesToRegister = [mockRule]; + await Rules.registerQueuedRules(); + expect(Rules.get('queued_rule')).toBe(mockRule); + expect(Rules.rulesToRegister).toHaveLength(0); + }); + }); + + describe('register with worldLoaded and Rules category', () => { + it('should call onModify when the rule has a defined value', async () => { + const onModify = vi.fn(); + const mockRule = { + getID: () => 'rules_category_rule', + getCategory: () => 'Rules', + getValue: vi.fn().mockResolvedValue(true), + onModify + }; + await Rules.register(mockRule); + expect(onModify).toHaveBeenCalledWith(true); + }); + + it('should call resetToDefaultValue when the rule value is undefined', async () => { + const resetToDefaultValue = vi.fn(); + const mockRule = { + getID: () => 'rules_category_rule_2', + getCategory: () => 'Rules', + getValue: vi.fn().mockResolvedValue(undefined), + resetToDefaultValue, + onModify: vi.fn() + }; + await Rules.register(mockRule); + expect(resetToDefaultValue).toHaveBeenCalled(); + }); + }); + describe('getByCategory', () => { it('should return all rules of a specific category', () => { new BooleanRule({ diff --git a/__tests__/BP/scripts/src/classes/EndGatewayExitFinder.test.js b/__tests__/BP/scripts/src/classes/EndGatewayExitFinder.test.js new file mode 100644 index 00000000..1ba6900d --- /dev/null +++ b/__tests__/BP/scripts/src/classes/EndGatewayExitFinder.test.js @@ -0,0 +1,257 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { EndGatewayExitFinder } from '../../../../../Canopy[BP]/scripts/src/classes/EndGatewayExitFinder'; +import { world, system, UnloadedChunksError } from '@minecraft/server'; +import { scheduler } from '@forestoflight/minecraft-vitest-mocks'; +import { EndGatewayExits } from '../../../../../Canopy[BP]/scripts/src/classes/EndGatewayExits'; +import { Vector } from '../../../../../Canopy[BP]/scripts/lib/Vector'; + +vi.mock('@minecraft/server', async () => { + const serverModule = await import('@forestoflight/minecraft-vitest-mocks/server'); + class MockUnloadedChunksError extends Error {} + return { ...serverModule, UnloadedChunksError: MockUnloadedChunksError }; +}); + +describe('EndGatewayExitFinder', () => { + let finder; + let mockDimension; + let mockPlayer; + + beforeEach(() => { + mockDimension = { + id: 'minecraft:the_end', + heightRange: { min: -64, max: 320 }, + getBlocks: vi.fn(() => ({ getCapacity: () => 0 })), + getBlock: vi.fn(() => ({ id: 'minecraft:end_stone' })), + }; + mockPlayer = { + location: { x: 0, y: 64, z: 0 }, + dimension: mockDimension, + }; + world.getDimension.mockReturnValue(mockDimension); + world.getPlayers.mockReturnValue([mockPlayer]); + system.runInterval.mockImplementation((callback, interval) => scheduler.scheduleInterval(callback, interval)); + system.runTimeout.mockImplementation((callback, delay) => scheduler.scheduleDelay(callback, delay)); + system.clearRun.mockImplementation((id) => scheduler.delete(id)); + EndGatewayExits.setLocations([]); + finder = new EndGatewayExitFinder(); + }); + + afterEach(() => { + finder.stop(); + finder.destroy(); + }); + + describe('constructor', () => { + it('initializes with empty gatewayExits', () => { + expect(finder.gatewayExits).toEqual([]); + }); + + it('populates known exits from storage', () => { + EndGatewayExits.setLocations([ + { dimension: { id: 'minecraft:the_end' }, x: 1, y: 64, z: 1 } + ]); + const finder2 = new EndGatewayExitFinder(); + expect(finder2.gatewayExits).toHaveLength(1); + finder2.stop(); + finder2.destroy(); + }); + }); + + describe('destroy', () => { + it('clears all gateway exits', () => { + finder.addGatewayExit(mockDimension, Vector.from({ x: 0, y: 64, z: 0 })); + finder.destroy(); + expect(finder.gatewayExits).toHaveLength(0); + }); + + it('calls destroy on each render', () => { + finder.addGatewayExit(mockDimension, Vector.from({ x: 0, y: 64, z: 0 })); + const renderSpy = vi.spyOn(finder.gatewayExits[0].render, 'destroy'); + finder.destroy(); + expect(renderSpy).toHaveBeenCalled(); + }); + }); + + describe('start', () => { + it('registers an interval', () => { + finder.start(); + expect(scheduler.scheduled.size).toBeGreaterThan(0); + }); + + it('does not register a second interval when already running', () => { + finder.start(); + const countAfterFirstStart = scheduler.scheduled.size; + finder.start(); + expect(scheduler.scheduled.size).toBe(countAfterFirstStart); + }); + }); + + describe('stop', () => { + it('cancels the interval so onTick no longer fires', () => { + finder.start(); + const onTickSpy = vi.spyOn(finder, 'onTick'); + finder.stop(); + scheduler.advanceTicks(1); + expect(onTickSpy).not.toHaveBeenCalled(); + }); + + it('does not throw when not running', () => { + expect(() => finder.stop()).not.toThrow(); + }); + }); + + describe('onTick', () => { + it('calls onTickPlayer for each player', () => { + const spy = vi.spyOn(finder, 'onTickPlayer'); + finder.onTick(); + expect(spy).toHaveBeenCalledWith(mockPlayer); + }); + }); + + describe('onTickPlayer', () => { + it('schedules tryAddEndGatewayExit when the player is near a gateway', () => { + mockDimension.getBlocks.mockReturnValue({ getCapacity: () => 1 }); + const spy = vi.spyOn(finder, 'tryAddEndGatewayExit'); + finder.onTickPlayer(mockPlayer); + scheduler.advanceTicks(1); + expect(spy).toHaveBeenCalled(); + }); + + it('does not schedule tryAddEndGatewayExit when not near a gateway', () => { + mockDimension.getBlocks.mockReturnValue({ getCapacity: () => 0 }); + const spy = vi.spyOn(finder, 'tryAddEndGatewayExit'); + finder.onTickPlayer(mockPlayer); + scheduler.advanceTicks(1); + expect(spy).not.toHaveBeenCalled(); + }); + }); + + describe('isNearEndGateway', () => { + it('returns true when a gateway block is present', () => { + mockDimension.getBlocks.mockReturnValue({ getCapacity: () => 1 }); + expect(finder.isNearEndGateway(mockDimension, { x: 0, y: 64, z: 0 })).toBe(true); + }); + + it('returns false when no gateway block is present', () => { + mockDimension.getBlocks.mockReturnValue({ getCapacity: () => 0 }); + expect(finder.isNearEndGateway(mockDimension, { x: 0, y: 64, z: 0 })).toBe(false); + }); + + it('returns false when chunks are unloaded', () => { + mockDimension.getBlocks.mockImplementation(() => { throw new UnloadedChunksError(); }); + expect(finder.isNearEndGateway(mockDimension, { x: 0, y: 64, z: 0 })).toBe(false); + }); + + it('rethrows unknown errors', () => { + mockDimension.getBlocks.mockImplementation(() => { throw new Error('unexpected'); }); + expect(() => finder.isNearEndGateway(mockDimension, { x: 0, y: 64, z: 0 })).toThrow('unexpected'); + }); + }); + + describe('tryAddEndGatewayExit', () => { + it('does nothing when the player has not traveled far and exit is unknown', () => { + const spy = vi.spyOn(finder, 'addGatewayExit'); + finder.tryAddEndGatewayExit(mockDimension, { x: 0, y: 64, z: 0 }, { x: 0, y: 64, z: 0 }); + expect(spy).not.toHaveBeenCalled(); + }); + + it('adds a gateway exit when the player has traveled far', () => { + const spy = vi.spyOn(finder, 'addGatewayExit'); + finder.tryAddEndGatewayExit(mockDimension, { x: 0, y: 0, z: 0 }, { x: 0, y: 0, z: 200 }); + expect(spy).toHaveBeenCalled(); + }); + + it('adds a gateway exit when the exit location is already known', () => { + finder.addGatewayExit(mockDimension, Vector.from({ x: 0, y: 64, z: 0 })); + const spy = vi.spyOn(finder, 'addGatewayExit'); + finder.tryAddEndGatewayExit(mockDimension, { x: 0, y: 64, z: 0 }, { x: 0, y: 64, z: 0 }); + expect(spy).toHaveBeenCalled(); + }); + }); + + describe('hasTraveledFar', () => { + it('returns true when distance exceeds 100', () => { + expect(finder.hasTraveledFar({ x: 0, y: 0, z: 0 }, { x: 0, y: 0, z: 200 })).toBe(true); + }); + + it('returns false when distance is 100 or less', () => { + expect(finder.hasTraveledFar({ x: 0, y: 0, z: 0 }, { x: 0, y: 0, z: 50 })).toBe(false); + }); + }); + + describe('exitIsKnown', () => { + it('returns true when a matching exit exists', () => { + finder.addGatewayExit(mockDimension, Vector.from({ x: 1, y: 64, z: 1 })); + expect(finder.exitIsKnown({ x: 1, y: 64, z: 1 })).toBe(true); + }); + + it('returns false when no matching exit exists', () => { + expect(finder.exitIsKnown({ x: 1, y: 64, z: 1 })).toBe(false); + }); + }); + + describe('removeNearbyInvalidExits', () => { + it('removes exits that lack end_stone below them', () => { + finder.addGatewayExit(mockDimension, Vector.from({ x: 0, y: 64, z: 0 })); + mockDimension.getBlock.mockReturnValue({ id: 'minecraft:air' }); + finder.removeNearbyInvalidExits(mockDimension, { x: 0, y: 64, z: 0 }); + expect(finder.gatewayExits).toHaveLength(0); + }); + + it('keeps exits that have end_stone below them', () => { + finder.addGatewayExit(mockDimension, Vector.from({ x: 0, y: 64, z: 0 })); + mockDimension.getBlock.mockReturnValue({ id: 'minecraft:end_stone' }); + finder.removeNearbyInvalidExits(mockDimension, { x: 0, y: 64, z: 0 }); + expect(finder.gatewayExits).toHaveLength(1); + }); + + it('removes exits when the block below is undefined', () => { + finder.addGatewayExit(mockDimension, Vector.from({ x: 0, y: 64, z: 0 })); + mockDimension.getBlock.mockReturnValue(undefined); + finder.removeNearbyInvalidExits(mockDimension, { x: 0, y: 64, z: 0 }); + expect(finder.gatewayExits).toHaveLength(0); + }); + + it('skips exits when getBlock throws UnloadedChunksError', () => { + finder.addGatewayExit(mockDimension, Vector.from({ x: 0, y: 64, z: 0 })); + mockDimension.getBlock.mockImplementation(() => { throw new UnloadedChunksError(); }); + expect(() => finder.removeNearbyInvalidExits(mockDimension, { x: 0, y: 64, z: 0 })).not.toThrow(); + expect(finder.gatewayExits).toHaveLength(1); + }); + + it('rethrows unknown errors from getBlock', () => { + finder.addGatewayExit(mockDimension, Vector.from({ x: 0, y: 64, z: 0 })); + mockDimension.getBlock.mockImplementation(() => { throw new Error('unexpected'); }); + expect(() => finder.removeNearbyInvalidExits(mockDimension, { x: 0, y: 64, z: 0 })).toThrow('unexpected'); + }); + + it('does not check exits outside the search area', () => { + finder.addGatewayExit(mockDimension, Vector.from({ x: 100, y: 64, z: 100 })); + mockDimension.getBlock.mockReturnValue({ id: 'minecraft:air' }); + finder.removeNearbyInvalidExits(mockDimension, { x: 0, y: 64, z: 0 }); + expect(finder.gatewayExits).toHaveLength(1); + }); + }); + + describe('addGatewayExit', () => { + it('adds the exit to gatewayExits', () => { + finder.addGatewayExit(mockDimension, Vector.from({ x: 1, y: 64, z: 1 })); + expect(finder.gatewayExits).toHaveLength(1); + }); + + it('persists the exit to storage', () => { + finder.addGatewayExit(mockDimension, Vector.from({ x: 1, y: 64, z: 1 })); + expect(EndGatewayExits.getLocations()).toHaveLength(1); + }); + }); + + describe('removeGatewayExit', () => { + it('removes the exit from gatewayExits and storage', () => { + finder.addGatewayExit(mockDimension, Vector.from({ x: 1, y: 64, z: 1 })); + const exit = finder.gatewayExits[0]; + finder.removeGatewayExit(exit); + expect(finder.gatewayExits).toHaveLength(0); + expect(EndGatewayExits.getLocations()).toHaveLength(0); + }); + }); +}); diff --git a/__tests__/BP/scripts/src/classes/EndGatewayExitRender.test.js b/__tests__/BP/scripts/src/classes/EndGatewayExitRender.test.js new file mode 100644 index 00000000..c44b814f --- /dev/null +++ b/__tests__/BP/scripts/src/classes/EndGatewayExitRender.test.js @@ -0,0 +1,59 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { EndGatewayExitRender } from '../../../../../Canopy[BP]/scripts/src/classes/EndGatewayExitRender'; +import { debugDrawer } from '@minecraft/debug-utilities'; + +describe('EndGatewayExitRender', () => { + const dimension = { id: 'minecraft:the_end' }; + const location = { x: 5, y: 64, z: 5 }; + let render; + + beforeEach(() => { + render = new EndGatewayExitRender(dimension, location, 32); + }); + + afterEach(() => { + render.destroy(); + }); + + describe('constructor', () => { + it('sets dimension, location, and searchAreaSize', () => { + expect(render.dimension).toEqual(dimension); + expect(render.location).toEqual({ x: 5, y: 64, z: 5 }); + expect(render.searchAreaSize).toBe(32); + }); + + it('defaults searchAreaSize to 1 when omitted', () => { + const r = new EndGatewayExitRender(dimension, location); + expect(r.searchAreaSize).toBe(1); + r.destroy(); + }); + + it('renders shapes on construction', () => { + expect(render.debugShapes.length).toBeGreaterThan(0); + expect(debugDrawer.addShape).toHaveBeenCalled(); + }); + }); + + describe('destroy', () => { + it('calls remove on each debug shape', () => { + const mockShape = { remove: vi.fn() }; + render.debugShapes.push(mockShape); + render.destroy(); + expect(mockShape.remove).toHaveBeenCalled(); + }); + + it('clears the debugShapes array', () => { + render.destroy(); + expect(render.debugShapes).toHaveLength(0); + }); + }); + + describe('drawShape', () => { + it('registers the shape with debugDrawer and tracks it in debugShapes', () => { + const shape = { remove: vi.fn() }; + render.drawShape(shape); + expect(debugDrawer.addShape).toHaveBeenCalledWith(shape); + expect(render.debugShapes).toContain(shape); + }); + }); +}); diff --git a/__tests__/BP/scripts/src/classes/EndGatewayExits.test.js b/__tests__/BP/scripts/src/classes/EndGatewayExits.test.js new file mode 100644 index 00000000..41c4adb3 --- /dev/null +++ b/__tests__/BP/scripts/src/classes/EndGatewayExits.test.js @@ -0,0 +1,77 @@ +import { beforeEach, describe, expect, it } from 'vitest'; +import { EndGatewayExits } from '../../../../../Canopy[BP]/scripts/src/classes/EndGatewayExits'; +import { world } from '@minecraft/server'; + +describe('EndGatewayExits', () => { + beforeEach(() => { + world.setDynamicProperty('end_gateway_exit_locations', undefined); + }); + + describe('getLocations', () => { + it('returns empty array when no data exists', () => { + expect(EndGatewayExits.getLocations()).toEqual([]); + }); + + it('returns parsed locations when data exists', () => { + const locations = [{ dimension: { id: 'minecraft:the_end' }, x: 1, y: 64, z: 1 }]; + world.setDynamicProperty('end_gateway_exit_locations', JSON.stringify(locations)); + expect(EndGatewayExits.getLocations()).toEqual(locations); + }); + + it('returns empty array when stored data parses to null', () => { + world.setDynamicProperty('end_gateway_exit_locations', 'null'); + expect(EndGatewayExits.getLocations()).toEqual([]); + }); + }); + + describe('setLocations', () => { + it('stores formatted locations', () => { + const locations = [{ dimension: { id: 'minecraft:the_end' }, x: 1, y: 64, z: 1 }]; + EndGatewayExits.setLocations(locations); + expect(EndGatewayExits.getLocations()).toEqual(locations); + }); + + it('stores an empty array when passed null', () => { + EndGatewayExits.setLocations(null); + expect(EndGatewayExits.getLocations()).toEqual([]); + }); + }); + + describe('addLocation', () => { + it('adds a location to the list', () => { + EndGatewayExits.addLocation({ id: 'minecraft:the_end' }, { x: 1, y: 64, z: 1 }); + expect(EndGatewayExits.getLocations()).toEqual([ + { dimension: { id: 'minecraft:the_end' }, x: 1, y: 64, z: 1 } + ]); + }); + + it('appends to existing locations', () => { + const dimension = { id: 'minecraft:the_end' }; + EndGatewayExits.addLocation(dimension, { x: 1, y: 64, z: 1 }); + EndGatewayExits.addLocation(dimension, { x: 2, y: 64, z: 2 }); + expect(EndGatewayExits.getLocations()).toHaveLength(2); + }); + }); + + describe('removeLocation', () => { + it('removes a matching location', () => { + const dimension = { id: 'minecraft:the_end' }; + EndGatewayExits.addLocation(dimension, { x: 1, y: 64, z: 1 }); + EndGatewayExits.removeLocation(dimension, { x: 1, y: 64, z: 1 }); + expect(EndGatewayExits.getLocations()).toEqual([]); + }); + + it('keeps non-matching locations', () => { + const dimension = { id: 'minecraft:the_end' }; + EndGatewayExits.addLocation(dimension, { x: 1, y: 64, z: 1 }); + EndGatewayExits.removeLocation(dimension, { x: 2, y: 64, z: 2 }); + expect(EndGatewayExits.getLocations()).toHaveLength(1); + }); + + it('keeps locations from other dimensions', () => { + EndGatewayExits.addLocation({ id: 'minecraft:the_end' }, { x: 1, y: 64, z: 1 }); + EndGatewayExits.removeLocation({ id: 'minecraft:overworld' }, { x: 1, y: 64, z: 1 }); + expect(EndGatewayExits.getLocations()).toHaveLength(1); + }); + }); +}); diff --git a/__tests__/BP/scripts/src/classes/EntityMovementLog.test.js b/__tests__/BP/scripts/src/classes/EntityMovementLog.test.js index abea7ba4..259da859 100644 --- a/__tests__/BP/scripts/src/classes/EntityMovementLog.test.js +++ b/__tests__/BP/scripts/src/classes/EntityMovementLog.test.js @@ -1,48 +1,33 @@ import { describe, it, expect, vi, beforeAll } from "vitest"; -import { EntityMovementLog } from "../../../../../Canopy [BP]/scripts/src/classes/EntityMovementLog"; +import { EntityMovementLog } from "../../../../../Canopy[BP]/scripts/src/classes/EntityMovementLog"; -vi.mock("@minecraft/server", () => ({ - system: { - runInterval: vi.fn((callback, interval) => { - const intervalId = setInterval(callback, interval * 50); - return { - clear: () => clearInterval(intervalId) - }; - }), - runTimeout: vi.fn((callback, timeout) => { - const timeoutId = setTimeout(callback, timeout * 50); - return { - clear: () => clearTimeout(timeoutId) - }; - }), - clearRun: vi.fn((runner) => { - runner.clear(); - }), - currentTick: 0 - }, - world: { - afterEvents: { - entitySpawn: { - subscribe: vi.fn() - } +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + system: { + ...original.system, + runInterval: vi.fn((callback, interval) => { + const intervalId = setInterval(callback, interval * 50); + return { clear: () => clearInterval(intervalId) }; + }), + runTimeout: vi.fn((callback, timeout) => { + const timeoutId = setTimeout(callback, timeout * 50); + return { clear: () => clearTimeout(timeoutId) }; + }), + clearRun: vi.fn((runner) => { runner.clear(); }) }, - beforeEvents: { - entityRemove: { - subscribe: vi.fn() - }, - playerLeave: { - subscribe: vi.fn() - } - }, - getDimension: vi.fn(() => ({ - getEntities: vi.fn(() => [ - { typeId: 'minecraft:falling_block', id: 'entity1', location: { x: 1, y: 2, z: 3 }, dimension: { id: 'overworld' }, - getComponent: vi.fn(() => ({ })), - isValid: vi.fn(() => true) - }, - { typeId: 'minecraft:projectile', id: 'entity2', location: { x: 4, y: 5, z: 6 }, dimension: { id: 'overworld' }, - getComponent: vi.fn(() => ({ projectile: { isValid: true } })), - isValid: vi.fn(() => true) + world: { + ...original.world, + getDimension: vi.fn(() => ({ + getEntities: vi.fn(() => [ + { typeId: 'minecraft:falling_block', id: 'entity1', location: { x: 1, y: 2, z: 3 }, dimension: { id: 'overworld' }, + getComponent: vi.fn(() => ({ })), + isValid: vi.fn(() => true) + }, + { typeId: 'minecraft:projectile', id: 'entity2', location: { x: 4, y: 5, z: 6 }, dimension: { id: 'overworld' }, + getComponent: vi.fn(() => ({ projectile: { isValid: true } })), + isValid: vi.fn(() => true) }, { typeId: 'minecraft:item', id: 'entity3', location: { x: 7, y: 8, z: 9 }, dimension: { id: 'overworld' }, getComponent: vi.fn(() => ({ })), @@ -51,7 +36,8 @@ vi.mock("@minecraft/server", () => ({ ]) })) } -})); + }; +}); describe('EntitMovementLog', () => { let entityLog; diff --git a/__tests__/BP/scripts/src/classes/EntityTntLog.test.js b/__tests__/BP/scripts/src/classes/EntityTntLog.test.js index 1d9d940a..adf44054 100644 --- a/__tests__/BP/scripts/src/classes/EntityTntLog.test.js +++ b/__tests__/BP/scripts/src/classes/EntityTntLog.test.js @@ -1,40 +1,24 @@ import { describe, it, expect, vi, beforeAll, beforeEach } from "vitest"; -import { EntityTntLog } from "../../../../../Canopy [BP]/scripts/src/classes/EntityTntLog"; +import { EntityTntLog } from "../../../../../Canopy[BP]/scripts/src/classes/EntityTntLog"; -vi.mock("@minecraft/server", () => ({ - system: { - runInterval: vi.fn((callback, interval) => { - const intervalId = setInterval(callback, interval * 50); - return { - clear: () => clearInterval(intervalId) - }; - }), - runTimeout: vi.fn((callback, timeout) => { - const timeoutId = setTimeout(callback, timeout * 50); - return { - clear: () => clearTimeout(timeoutId) - }; - }), - clearRun: vi.fn((runner) => { - runner.clear(); - }) - }, - world: { - afterEvents: { - entitySpawn: { - subscribe: vi.fn() - } - }, - beforeEvents: { - entityRemove: { - subscribe: vi.fn() - }, - playerLeave: { - subscribe: vi.fn() - } +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + system: { + ...original.system, + runInterval: vi.fn((callback, interval) => { + const intervalId = setInterval(callback, interval * 50); + return { clear: () => clearInterval(intervalId) }; + }), + runTimeout: vi.fn((callback, timeout) => { + const timeoutId = setTimeout(callback, timeout * 50); + return { clear: () => clearTimeout(timeoutId) }; + }), + clearRun: vi.fn((runner) => { runner.clear(); }) } - } -})); + }; +}); vi.mock("@minecraft/server-ui", () => ({ ModalFormData: vi.fn() diff --git a/__tests__/BP/scripts/src/classes/InventoryUI.test.js b/__tests__/BP/scripts/src/classes/InventoryUI.test.js index 7e1ed882..b989d93d 100644 --- a/__tests__/BP/scripts/src/classes/InventoryUI.test.js +++ b/__tests__/BP/scripts/src/classes/InventoryUI.test.js @@ -1,41 +1,23 @@ -import { InventoryUI } from "../../../../../Canopy [BP]/scripts/src/classes/InventoryUI"; +import { InventoryUI } from "../../../../../Canopy[BP]/scripts/src/classes/InventoryUI"; import { describe, it, expect, vi, afterEach } from "vitest"; -import * as Utils from "../../../../../Canopy [BP]/scripts/include/utils"; +import * as Utils from "../../../../../Canopy[BP]/scripts/include/utils"; -vi.mock("@minecraft/server", () => ({ - EntityComponentTypes: { - Inventory: 'inventory' - }, - ItemComponentTypes: { - Durability: 'durability', - Enchantable: 'enchantable' - }, - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } - }, - runJob: vi.fn(), - currentTick: (Date.now() / 50), - runInterval: vi.fn((callback, interval) => { - const intervalId = setInterval(callback, interval * 50); - return { - clear: () => clearInterval(intervalId) - }; - }), - clearRun: vi.fn((runner) => { - runner.clear(); - }), - run: vi.fn((callback) => { - callback(); - }) - } -})); - -vi.mock("@minecraft/server-ui", () => ({ - ModalFormData: vi.fn() -})); +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + system: { + ...original.system, + currentTick: (Date.now() / 50), + runInterval: vi.fn((callback, interval) => { + const intervalId = setInterval(callback, interval * 50); + return { clear: () => clearInterval(intervalId) }; + }), + clearRun: vi.fn((runner) => { runner.clear(); }), + run: vi.fn((callback) => { callback(); }) + } + }; +}); describe('InventoryPeeker', () => { afterEach(() => { diff --git a/__tests__/BP/scripts/src/classes/Profiler.test.js b/__tests__/BP/scripts/src/classes/Profiler.test.js index 666604a0..276169d7 100644 --- a/__tests__/BP/scripts/src/classes/Profiler.test.js +++ b/__tests__/BP/scripts/src/classes/Profiler.test.js @@ -1,30 +1,24 @@ import { describe, it, expect, vi } from "vitest"; -import { Profiler } from "../../../../../Canopy [BP]/scripts/src/classes/Profiler"; - -vi.mock("@minecraft/server", () => ({ - system: { - runInterval: vi.fn((callback, interval) => { - const intervalId = setInterval(callback, interval * 50); - return { - clear: () => clearInterval(intervalId) - }; - }), - runTimeout: vi.fn((callback, timeout) => { - const timeoutId = setTimeout(callback, timeout * 50); - return { - clear: () => clearTimeout(timeoutId) - }; - }), - clearRun: vi.fn((runner) => { - runner.clear(); - }) - }, - TicksPerSecond: 20.0 -})); - -vi.mock("@minecraft/server-ui", () => ({ - ModalFormData: vi.fn() -})); +import { Profiler } from "../../../../../Canopy[BP]/scripts/src/classes/Profiler"; + +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + system: { + ...original.system, + runInterval: vi.fn((callback, interval) => { + const intervalId = setInterval(callback, interval * 50); + return { clear: () => clearInterval(intervalId) }; + }), + runTimeout: vi.fn((callback, timeout) => { + const timeoutId = setTimeout(callback, timeout * 50); + return { clear: () => clearTimeout(timeoutId) }; + }), + clearRun: vi.fn((runner) => { runner.clear(); }) + } + }; +}); describe('Profiler', () => { it('should have a getter for the instant MS', () => { diff --git a/__tests__/BP/scripts/src/classes/TNTFuse.test.js b/__tests__/BP/scripts/src/classes/TNTFuse.test.js index c7a53f07..c14f1d6a 100644 --- a/__tests__/BP/scripts/src/classes/TNTFuse.test.js +++ b/__tests__/BP/scripts/src/classes/TNTFuse.test.js @@ -1,45 +1,31 @@ import { describe, it, expect, vi, afterEach, beforeAll, afterAll } from "vitest"; -import { TNTFuse } from "../../../../../Canopy [BP]/scripts/src/classes/TNTFuse.js"; +import { TNTFuse } from "../../../../../Canopy[BP]/scripts/src/classes/TNTFuse.js"; -vi.mock("@minecraft/server", () => ({ - system: { - currentTick: (Date.now() / 50), - runInterval: vi.fn((callback, interval) => { - const intervalId = setInterval(callback, interval * 50); - return { - clear: () => clearInterval(intervalId) - }; - }), - clearRun: vi.fn((runner) => { - runner.clear(); - }) - }, - world: { - afterEvents: { - entitySpawn: { - subscribe: vi.fn() - } - }, - beforeEvents: { - entityRemove: { - subscribe: vi.fn() - } +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + system: { + ...original.system, + currentTick: (Date.now() / 50), + runInterval: vi.fn((callback, interval) => { + const intervalId = setInterval(callback, interval * 50); + return { clear: () => clearInterval(intervalId) }; + }), + clearRun: vi.fn((runner) => { runner.clear(); }) }, - getDimension: vi.fn(() => ({ - getEntities: vi.fn(() => [ - { typeId: 'minecraft:tnt', id: 'entity1', location: { x: 1, y: 2, z: 3 }, dimension: { id: 'overworld' }, - isValid: vi.fn(() => true) - }, - { typeId: 'minecraft:tnt', id: 'entity2', location: { x: 4, y: 5, z: 6 }, dimension: { id: 'overworld' }, - isValid: vi.fn(() => true) - }, - { typeId: 'minecraft:tnt', id: 'entity3', location: { x: 7, y: 8, z: 9 }, dimension: { id: 'overworld' }, - isValid: vi.fn(() => false) - } - ]) - })) - } -})); + world: { + ...original.world, + getDimension: vi.fn(() => ({ + getEntities: vi.fn(() => [ + { typeId: 'minecraft:tnt', id: 'entity1', location: { x: 1, y: 2, z: 3 }, dimension: { id: 'overworld' }, isValid: vi.fn(() => true) }, + { typeId: 'minecraft:tnt', id: 'entity2', location: { x: 4, y: 5, z: 6 }, dimension: { id: 'overworld' }, isValid: vi.fn(() => true) }, + { typeId: 'minecraft:tnt', id: 'entity3', location: { x: 7, y: 8, z: 9 }, dimension: { id: 'overworld' }, isValid: vi.fn(() => false) } + ]) + })) + } + }; +}); const tnt = { typeId: 'minecraft:tnt', diff --git a/__tests__/BP/scripts/src/commands/log.test.js b/__tests__/BP/scripts/src/commands/log.test.js index 836bba64..8684dde5 100644 --- a/__tests__/BP/scripts/src/commands/log.test.js +++ b/__tests__/BP/scripts/src/commands/log.test.js @@ -1,98 +1,48 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; -import { logCommand } from "../../../../../Canopy [BP]/scripts/src/commands/log"; +import { logCommand } from "../../../../../Canopy[BP]/scripts/src/commands/log"; import { Player } from "@minecraft/server"; -import { PlayerCommandOrigin } from "../../../../../Canopy [BP]/scripts/lib/canopy/commands/PlayerCommandOrigin"; +import { PlayerCommandOrigin } from "../../../../../Canopy[BP]/scripts/lib/canopy/commands/PlayerCommandOrigin"; -vi.mock("@minecraft/server", () => ({ - system: { - runInterval: vi.fn((callback, interval) => { - const intervalId = setInterval(callback, interval * 50); - return { - clear: () => clearInterval(intervalId) - }; - }), - runTimeout: vi.fn((callback, timeout) => { - const timeoutId = setTimeout(callback, timeout * 50); - return { - clear: () => clearTimeout(timeoutId) - }; - }), - runJob: vi.fn(), - clearRun: vi.fn((runner) => { - runner.clear(); - }), - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn(), - unsubscribe: vi.fn() - } +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + system: { + ...original.system, + runInterval: vi.fn((callback, interval) => { + const intervalId = setInterval(callback, interval * 50); + return { clear: () => clearInterval(intervalId) }; + }), + runTimeout: vi.fn((callback, timeout) => { + const timeoutId = setTimeout(callback, timeout * 50); + return { clear: () => clearTimeout(timeoutId) }; + }), + clearRun: vi.fn((runner) => { runner.clear(); }) }, - beforeEvents: { - startup: { - subscribe: vi.fn() - } - } - }, - world: { - afterEvents: { - entitySpawn: { - subscribe: vi.fn() - }, - worldLoad: { - subscribe: vi.fn() - } - }, - beforeEvents: { - entityRemove: { - subscribe: vi.fn() - }, - chatSend: { - subscribe: vi.fn() - }, - playerLeave: { - subscribe: vi.fn() - } + world: { + ...original.world, + getDimension: vi.fn(() => ({ + id: 'overworld', + runCommand: vi.fn(), + getEntities: vi.fn(() => [ + { typeId: 'minecraft:falling_block', id: 'entity1', location: { x: 1, y: 2, z: 3 }, dimension: { id: 'overworld' }, + getComponent: vi.fn(() => ({})), isValid: vi.fn(() => true) }, + { typeId: 'minecraft:projectile', id: 'entity2', location: { x: 4, y: 5, z: 6 }, dimension: { id: 'overworld' }, + getComponent: vi.fn(() => ({ projectile: { isValid: true } })), isValid: vi.fn(() => true) }, + { typeId: 'minecraft:item', id: 'entity3', location: { x: 7, y: 8, z: 9 }, dimension: { id: 'overworld' }, + getComponent: vi.fn(() => ({})), isValid: vi.fn(() => false) } + ]) + })) }, - getDimension: vi.fn(() => ({ - id: 'overworld', - runCommand: vi.fn(), - getEntities: vi.fn(() => [ - { typeId: 'minecraft:falling_block', id: 'entity1', location: { x: 1, y: 2, z: 3 }, dimension: { id: 'overworld' }, - getComponent: vi.fn(() => ({ })), - isValid: vi.fn(() => true) - }, - { typeId: 'minecraft:projectile', id: 'entity2', location: { x: 4, y: 5, z: 6 }, dimension: { id: 'overworld' }, - getComponent: vi.fn(() => ({ projectile: { isValid: true } })), - isValid: vi.fn(() => true) - }, - { typeId: 'minecraft:item', id: 'entity3', location: { x: 7, y: 8, z: 9 }, dimension: { id: 'overworld' }, - getComponent: vi.fn(() => ({ })), - isValid: vi.fn(() => false) - } - ]) - })) - }, - CommandPermissionLevel: { - Any: 'Any', - }, - CustomCommandParamType: { - Enum: 'Enum', - Integer: 'Integer', - }, - CustomCommandStatus: { - Success: 'Success', - Failure: 'Failure', - }, - Player: class { - sendMessage = vi.fn(); - getDynamicProperty = vi.fn(); - setDynamicProperty = vi.fn(); - } -})); - -vi.mock("@minecraft/server-ui", () => ({ - ModalFormData: vi.fn() -})); + CommandPermissionLevel: { Any: 'Any' }, + CustomCommandParamType: { Enum: 'Enum', Integer: 'Integer' }, + Player: class { + sendMessage = vi.fn(); + getDynamicProperty = vi.fn(); + setDynamicProperty = vi.fn(); + } + }; +}); describe('logCommand', () => { let mockPlayer; diff --git a/__tests__/BP/scripts/src/commands/velocity.test.js b/__tests__/BP/scripts/src/commands/velocity.test.js index 7f89dcff..b628d36f 100644 --- a/__tests__/BP/scripts/src/commands/velocity.test.js +++ b/__tests__/BP/scripts/src/commands/velocity.test.js @@ -1,81 +1,34 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; -import { velocityCommand } from "../../../../../Canopy [BP]/scripts/src/commands/velocity"; -import { Vector } from "../../../../../Canopy [BP]/scripts/lib/Vector"; -import { PlayerCommandOrigin } from "../../../../../Canopy [BP]/scripts/lib/canopy/Canopy"; +import { velocityCommand } from "../../../../../Canopy[BP]/scripts/src/commands/velocity"; +import { Vector } from "../../../../../Canopy[BP]/scripts/lib/Vector"; +import { PlayerCommandOrigin } from "../../../../../Canopy[BP]/scripts/lib/canopy/Canopy"; -vi.mock("@minecraft/server", () => ({ - system: { - runInterval: vi.fn((callback, interval) => { - const intervalId = setInterval(callback, interval * 50); - return { - clear: () => clearInterval(intervalId) - }; - }), - runTimeout: vi.fn((callback, timeout) => { - const timeoutId = setTimeout(callback, timeout * 50); - return { - clear: () => clearTimeout(timeoutId) - }; - }), - runJob: vi.fn(), - clearRun: vi.fn((runner) => { - runner.clear(); - }), - run: vi.fn((callback) => callback()), - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn(), - unsubscribe: vi.fn() - } +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + system: { + ...original.system, + runInterval: vi.fn((callback, interval) => { + const intervalId = setInterval(callback, interval * 50); + return { clear: () => clearInterval(intervalId) }; + }), + runTimeout: vi.fn((callback, timeout) => { + const timeoutId = setTimeout(callback, timeout * 50); + return { clear: () => clearTimeout(timeoutId) }; + }), + clearRun: vi.fn((runner) => { runner.clear(); }), + run: vi.fn((callback) => callback()) }, - beforeEvents: { - startup: { - subscribe: vi.fn() - } + CommandPermissionLevel: { GameDirectors: 'GameDirectors' }, + CustomCommandParamType: { Enum: 'Enum', Float: 'Float' }, + Player: class { + sendMessage = vi.fn(); + getDynamicProperty = vi.fn(); + setDynamicProperty = vi.fn(); } - }, - world: { - afterEvents: { - entitySpawn: { - subscribe: vi.fn() - }, - worldLoad: { - subscribe: vi.fn() - } - }, - beforeEvents: { - entityRemove: { - subscribe: vi.fn() - }, - chatSend: { - subscribe: vi.fn() - }, - playerLeave: { - subscribe: vi.fn() - } - }, - }, - CommandPermissionLevel: { - GameDirectors: 'GameDirectors', - }, - CustomCommandParamType: { - Enum: 'Enum', - Float: 'Float', - }, - CustomCommandStatus: { - Success: 'Success', - Failure: 'Failure', - }, - Player: class { - sendMessage = vi.fn(); - getDynamicProperty = vi.fn(); - setDynamicProperty = vi.fn(); - } -})); - -vi.mock("@minecraft/server-ui", () => ({ - ModalFormData: vi.fn() -})); + }; +}); describe('velocityCommand', () => { let mockPlayerOrigin; diff --git a/__tests__/BP/scripts/src/events/PlayerChangeSubChunkEvent.test.js b/__tests__/BP/scripts/src/events/PlayerChangeSubChunkEvent.test.js index ce21e933..9a157eab 100644 --- a/__tests__/BP/scripts/src/events/PlayerChangeSubChunkEvent.test.js +++ b/__tests__/BP/scripts/src/events/PlayerChangeSubChunkEvent.test.js @@ -1,26 +1,28 @@ -import { PlayerChangeSubChunkEvent, playerChangeSubChunkEvent } from "../../../../../Canopy [BP]/scripts/src/events/PlayerChangeSubChunkEvent"; +import { PlayerChangeSubChunkEvent, playerChangeSubChunkEvent } from "../../../../../Canopy[BP]/scripts/src/events/PlayerChangeSubChunkEvent"; import { expect, it, describe, vi, beforeEach } from "vitest"; const mockPlayer1 = { id: 'player1' }; const mockPlayer2 = { id: 'player2' }; -vi.mock("@minecraft/server", () => ({ - system: { - currentTick: (Date.now() / 50), - runInterval: vi.fn((callback, interval) => { - const intervalId = setInterval(callback, interval * 50); - return { - clear: () => clearInterval(intervalId) - }; - }), - clearRun: vi.fn((runner) => { - runner.clear(); - }) - }, - world: { - getAllPlayers: vi.fn(() => [void 0, mockPlayer1, mockPlayer2]) - } -})); +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + system: { + ...original.system, + currentTick: (Date.now() / 50), + runInterval: vi.fn((callback, interval) => { + const intervalId = setInterval(callback, interval * 50); + return { clear: () => clearInterval(intervalId) }; + }), + clearRun: vi.fn((runner) => { runner.clear(); }) + }, + world: { + ...original.world, + getAllPlayers: vi.fn(() => [void 0, mockPlayer1, mockPlayer2]) + } + }; +}); describe('PlayerChangeSubChunkEvent', () => { let tracker; diff --git a/__tests__/BP/scripts/src/events/PlayerStartSneakEvent.test.js b/__tests__/BP/scripts/src/events/PlayerStartSneakEvent.test.js index 3e97dfb4..ad0d90c0 100644 --- a/__tests__/BP/scripts/src/events/PlayerStartSneakEvent.test.js +++ b/__tests__/BP/scripts/src/events/PlayerStartSneakEvent.test.js @@ -1,33 +1,30 @@ -import { PlayerStartSneakEvent, playerStartSneakEvent } from "../../../../../Canopy [BP]/scripts/src/events/PlayerStartSneakEvent"; +import { PlayerStartSneakEvent, playerStartSneakEvent } from "../../../../../Canopy[BP]/scripts/src/events/PlayerStartSneakEvent"; import { expect, test, describe, vi } from "vitest"; const mockPlayer1 = { id: 'player1', inputInfo: { getButtonState: vi.fn(() => "Pressed" ) }, isOnGround: true, location: { x: 0, y: 0, z: 0 }, getRotation: vi.fn(() => ({ x: 0, y: 0 })) }; const mockPlayer2 = { id: 'player2', inputInfo: { getButtonState: vi.fn(() => "Released" ) }, isOnGround: true, location: { x: 1, y: 1, z: 1 }, getRotation: vi.fn(() => ({ x: 0, y: 0 })) }; -vi.mock("@minecraft/server", () => ({ - system: { - currentTick: (Date.now() / 50), - runInterval: vi.fn((callback, interval) => { - const intervalId = setInterval(callback, interval * 50); - return { - clear: () => clearInterval(intervalId) - }; - }), - clearRun: vi.fn((runner) => { - runner.clear(); - }) - }, - InputButton: { - Sneak: 'sneak' - }, - ButtonState: { - Pressed: 'Pressed', - Released: 'Released' - }, - world: { - getAllPlayers: vi.fn(() => [undefined, mockPlayer1, mockPlayer2]) - } -})); +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + system: { + ...original.system, + currentTick: (Date.now() / 50), + runInterval: vi.fn((callback, interval) => { + const intervalId = setInterval(callback, interval * 50); + return { clear: () => clearInterval(intervalId) }; + }), + clearRun: vi.fn((runner) => { runner.clear(); }) + }, + InputButton: { Sneak: 'sneak' }, + ButtonState: { Pressed: 'Pressed', Released: 'Released' }, + world: { + ...original.world, + getAllPlayers: vi.fn(() => [undefined, mockPlayer1, mockPlayer2]) + } + }; +}); describe('PlayerStartSneakEvent', () => { test('should initialize properties correctly', () => { diff --git a/__tests__/BP/scripts/src/events/SpawnEggSpawnEntityEvent.test.js b/__tests__/BP/scripts/src/events/SpawnEggSpawnEntityEvent.test.js index 2ce50a35..912e63c8 100644 --- a/__tests__/BP/scripts/src/events/SpawnEggSpawnEntityEvent.test.js +++ b/__tests__/BP/scripts/src/events/SpawnEggSpawnEntityEvent.test.js @@ -1,36 +1,30 @@ -import { SpawnEggSpawnEntityEvent, spawnEggSpawnEntityEvent } from "../../../../../Canopy [BP]/scripts/src/events/SpawnEggSpawnEntityEvent"; +import { SpawnEggSpawnEntityEvent, spawnEggSpawnEntityEvent } from "../../../../../Canopy[BP]/scripts/src/events/SpawnEggSpawnEntityEvent"; import { expect, test, describe, vi, beforeEach } from "vitest"; -vi.mock("@minecraft/server", () => ({ - system: { - currentTick: (Date.now() / 50), - runInterval: vi.fn((callback, interval) => { - const intervalId = setInterval(callback, interval * 50); - return { - clear: () => clearInterval(intervalId) - }; - }), - clearRun: vi.fn((runner) => { - runner.clear(); - }) - }, - world: { - afterEvents: { - entitySpawn: { - subscribe: vi.fn(), - unsubscribe: vi.fn() - }, - playerInteractWithBlock: { - subscribe: vi.fn(), - unsubscribe: vi.fn() +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + system: { + ...original.system, + currentTick: (Date.now() / 50), + runInterval: vi.fn((callback, interval) => { + const intervalId = setInterval(callback, interval * 50); + return { clear: () => clearInterval(intervalId) }; + }), + clearRun: vi.fn((runner) => { runner.clear(); }) + }, + world: { + ...original.world, + afterEvents: { + ...original.world.afterEvents, + entitySpawn: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + playerInteractWithBlock: { subscribe: vi.fn(), unsubscribe: vi.fn() } } - } - }, - EntityInitializationCause: { - Spawned: 'Spawned', - Loaded: 'Loaded' - } -})); + }, + EntityInitializationCause: { Spawned: 'Spawned', Loaded: 'Loaded' } + }; +}); describe('SpawnEggSpawnEntityEvent', () => { let tracker; diff --git a/__tests__/BP/scripts/src/rules/allowBubbleColumnPlacement.test.js b/__tests__/BP/scripts/src/rules/allowBubbleColumnPlacement.test.js index 3e3b45ad..7371fc5e 100644 --- a/__tests__/BP/scripts/src/rules/allowBubbleColumnPlacement.test.js +++ b/__tests__/BP/scripts/src/rules/allowBubbleColumnPlacement.test.js @@ -1,45 +1,16 @@ -import { allowBubbleColumnPlacement } from "../../../../../Canopy [BP]/scripts/src/rules/allowBubbleColumnPlacement"; +import { allowBubbleColumnPlacement } from "../../../../../Canopy[BP]/scripts/src/rules/allowBubbleColumnPlacement"; import { expect, test, describe, vi, afterEach } from "vitest"; -vi.mock("@minecraft/server", () => ({ - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - }, - playerPlaceBlock: { - subscribe: vi.fn() - } - }, - runJob: vi.fn(), - run: vi.fn((callback) => callback()) - }, - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() - }, - playerPlaceBlock: { - subscribe: vi.fn(), - unsubscribe: vi.fn() - } - }, - afterEvents: { - worldLoad: { - subscribe: vi.fn() - } - }, - getDynamicProperty: vi.fn(), - setDynamicProperty: vi.fn(), - structureManager: { - place: vi.fn() +vi.mock("@minecraft/server", async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + system: { + ...original.system, + run: vi.fn((callback) => callback()) } - } -})); - -vi.mock("@minecraft/server-ui", () => ({ - ModalFormData: vi.fn() -})); + }; +}); describe('allowBubbleColumnPlacement', () => { afterEach(() => { diff --git a/__tests__/BP/scripts/src/rules/allowPeekInventory.test.js b/__tests__/BP/scripts/src/rules/allowPeekInventory.test.js index 7feaa194..88ae8312 100644 --- a/__tests__/BP/scripts/src/rules/allowPeekInventory.test.js +++ b/__tests__/BP/scripts/src/rules/allowPeekInventory.test.js @@ -1,66 +1,39 @@ -import { allowPeekInventory } from "../../../../../Canopy [BP]/scripts/src/rules/allowPeekInventory"; +import { allowPeekInventory } from "../../../../../Canopy[BP]/scripts/src/rules/allowPeekInventory"; import { expect, it, describe, vi, afterEach } from "vitest"; -import { InventoryUI } from "../../../../../Canopy [BP]/scripts/src/classes/InventoryUI"; +import { InventoryUI } from "../../../../../Canopy[BP]/scripts/src/classes/InventoryUI"; -vi.mock("@minecraft/server", () => ({ - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } - }, - runJob: vi.fn(), - currentTick: (Date.now() / 50), - runInterval: vi.fn((callback, interval) => { - const intervalId = setInterval(callback, interval * 50); - return { - clear: () => clearInterval(intervalId) - }; - }), - clearRun: vi.fn((runner) => { - runner.clear(); - }), - run: vi.fn((callback) => { - callback(); - }) - }, - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() - }, - playerInteractWithBlock: { - subscribe: vi.fn(), - unsubscribe: vi.fn() - }, - playerInteractWithEntity: { - subscribe: vi.fn(), - unsubscribe: vi.fn() - } +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + system: { + ...original.system, + currentTick: (Date.now() / 50), + runInterval: vi.fn((callback, interval) => { + const intervalId = setInterval(callback, interval * 50); + return { clear: () => clearInterval(intervalId) }; + }), + clearRun: vi.fn((runner) => { runner.clear(); }), + run: vi.fn((callback) => { callback(); }) }, - afterEvents: { - worldLoad: { - subscribe: vi.fn() + world: { + ...original.world, + beforeEvents: { + ...original.world.beforeEvents, + playerInteractWithBlock: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + playerInteractWithEntity: { subscribe: vi.fn(), unsubscribe: vi.fn() } } - }, - getDynamicProperty: vi.fn(), - setDynamicProperty: vi.fn() - }, - EntityComponentTypes: { - Inventory: 'inventory' - }, - ItemComponentTypes: { - Durability: 'durability', - Enchantable: 'enchantable' - } -})); + } + }; +}); -vi.mock("@minecraft/server-ui", () => ({ - ModalFormData: vi.fn(), - uiManager: { - closeAllForms: vi.fn() - } -})); +vi.mock("@minecraft/server-ui", async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + uiManager: { closeAllForms: vi.fn() } + }; +}); describe('allowPeekInventory', () => { afterEach(() => { diff --git a/__tests__/BP/scripts/src/rules/creativeNetherWaterPlacement.test.js b/__tests__/BP/scripts/src/rules/creativeNetherWaterPlacement.test.js index 717d8273..a234721f 100644 --- a/__tests__/BP/scripts/src/rules/creativeNetherWaterPlacement.test.js +++ b/__tests__/BP/scripts/src/rules/creativeNetherWaterPlacement.test.js @@ -1,60 +1,26 @@ -import { creativeNetherWaterPlacement } from "../../../../../Canopy [BP]/scripts/src/rules/creativeNetherWaterPlacement"; +import { creativeNetherWaterPlacement } from "../../../../../Canopy[BP]/scripts/src/rules/creativeNetherWaterPlacement"; import { expect, test, describe, vi, afterEach } from "vitest"; -vi.mock("@minecraft/server", () => ({ - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - }, - playerInteractWithBlock: { - subscribe: vi.fn() - } - }, - runJob: vi.fn(), - run: vi.fn((callback) => callback()) - }, - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() - }, - playerInteractWithBlock: { - subscribe: vi.fn(), - unsubscribe: vi.fn() - } +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + system: { + ...original.system, + run: vi.fn((callback) => callback()) }, - afterEvents: { - worldLoad: { - subscribe: vi.fn() + world: { + ...original.world, + beforeEvents: { + ...original.world.beforeEvents, + playerInteractWithBlock: { subscribe: vi.fn(), unsubscribe: vi.fn() } } }, - getDynamicProperty: vi.fn(), - setDynamicProperty: vi.fn(), - structureManager: { - place: vi.fn() - } - }, - GameMode: { - Creative: 'Creative', - Survival: 'Survival' - }, - LiquidType: { - Water: 'Water' - }, - Direction: { - Up: 'Up', - Down: 'Down', - North: 'North', - South: 'South', - West: 'West', - East: 'East' - } -})); - -vi.mock("@minecraft/server-ui", () => ({ - ModalFormData: vi.fn() -})); + GameMode: { Creative: 'Creative', Survival: 'Survival' }, + LiquidType: { Water: 'Water' }, + Direction: { Up: 'Up', Down: 'Down', North: 'North', South: 'South', West: 'West', East: 'East' } + }; +}); describe('creativeNetherWaterPlacement', () => { afterEach(() => { diff --git a/__tests__/BP/scripts/src/rules/dupeTnt.test.js b/__tests__/BP/scripts/src/rules/dupeTnt.test.js index 55e79011..c6e8f49a 100644 --- a/__tests__/BP/scripts/src/rules/dupeTnt.test.js +++ b/__tests__/BP/scripts/src/rules/dupeTnt.test.js @@ -1,71 +1,60 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { spawnedEntitiesThisTick, handleTntDuplication } from '../../../../../Canopy [BP]/scripts/src/rules/dupeTnt'; +import { describe, it, expect, vi, beforeAll, beforeEach } from 'vitest'; +import { spawnedEntitiesThisTick, handleTntDuplication } from '../../../../../Canopy[BP]/scripts/src/rules/dupeTnt'; import { system, world } from '@minecraft/server'; -import { BooleanRule, Rules } from '../../../../../Canopy [BP]/scripts/lib/canopy/Canopy'; - -vi.mock("@minecraft/server", () => ({ - system: { - runInterval: vi.fn((callback, interval) => { - const intervalId = setInterval(() => { - callback(); - }, interval * 50); - return { - clear: () => clearInterval(intervalId) - }; - }), - runTimeout: vi.fn((callback, timeout) => { - const timeoutId = setTimeout(() => { - callback(); - }, timeout * 50); - return { - clear: () => clearTimeout(timeoutId) - }; - }), - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn(), - unsubscribe: vi.fn() - } - } - }, - world: { - afterEvents: { - entitySpawn: { - subscribe: vi.fn() - }, - pistonActivate: { - subscribe: vi.fn() - } +import { BooleanRule, Rules } from '../../../../../Canopy[BP]/scripts/lib/canopy/Canopy'; + +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + system: { + ...original.system, + runInterval: vi.fn((callback, interval) => { + const intervalId = setInterval(() => { callback(); }, interval * 50); + return { clear: () => clearInterval(intervalId) }; + }), + runTimeout: vi.fn((callback, timeout) => { + const timeoutId = setTimeout(() => { callback(); }, timeout * 50); + return { clear: () => clearTimeout(timeoutId) }; + }) } - } -})); + }; +}); -vi.mock('../../../../../Canopy [BP]/scripts/lib/canopy/Canopy', () => ({ +vi.mock('../../../../../Canopy[BP]/scripts/lib/canopy/Canopy', () => ({ BooleanRule: vi.fn(), Rules: { getNativeValue: vi.fn() } })); -vi.mock("@minecraft/server-ui", () => ({ - ModalFormData: vi.fn() -})); - describe('dupeTnt Rule', () => { + let booleanRuleArgs; + let runIntervalCallback; + let entitySpawnCallback; + let pistonActivateCallback; + + beforeAll(() => { + booleanRuleArgs = BooleanRule.mock.calls[0]?.[0]; + runIntervalCallback = system.runInterval.mock.calls[0]?.[0]; + entitySpawnCallback = world.afterEvents.entitySpawn.subscribe.mock.calls[0]?.[0]; + pistonActivateCallback = world.afterEvents.pistonActivate.subscribe.mock.calls[0]?.[0]; + }); + beforeEach(() => { spawnedEntitiesThisTick.length = 0; }); it('should create a new rule', () => { - expect(BooleanRule).toHaveBeenCalledWith({ + expect(booleanRuleArgs).toEqual({ category: 'Rules', identifier: 'dupeTnt', - description: { translate: 'rules.dupeTnt' } + description: { translate: 'rules.dupeTnt' }, + wikiDescription: 'Enables/disables TNT duping. To dupe a block of TNT, it must be moved by a piston while adjacent to a note block, then ignited.\n\nThe TNT will drop with normal priming momentum in the block below where it was ignited. Note that using this rule alongside `tntPrimeMomentum` will cause a 1-gametick slowdown before the TNT drops.\n\n![Dupe TNT Example](./exampleAssets/dupeTnt.png)' }); }); it('should clear spawnedEntitiesThisTick every tick', () => { - const runIntervalCallback = system.runInterval.mock.calls[0][0]; const runTimeoutCallback = vi.fn(); system.runTimeout.mockImplementation((callback) => { runTimeoutCallback.mockImplementation(callback); @@ -78,15 +67,14 @@ describe('dupeTnt Rule', () => { }); it('should subscribe to entitySpawn event', () => { - expect(world.afterEvents.entitySpawn.subscribe).toHaveBeenCalled(); + expect(entitySpawnCallback).toBeDefined(); }); it('should subscribe to pistonActivate event', () => { - expect(world.afterEvents.pistonActivate.subscribe).toHaveBeenCalled(); + expect(pistonActivateCallback).toBeDefined(); }); it('should handle entitySpawn event correctly', () => { - const entitySpawnCallback = world.afterEvents.entitySpawn.subscribe.mock.calls[0][0]; const event = { entity: { typeId: 'minecraft:tnt', @@ -101,7 +89,6 @@ describe('dupeTnt Rule', () => { }); it('should handle pistonActivate event correctly', () => { - const pistonActivateCallback = world.afterEvents.pistonActivate.subscribe.mock.calls[0][0]; const event = { block: { permutation: { @@ -124,7 +111,6 @@ describe('dupeTnt Rule', () => { }); it('should not throw an error if the piston is removed', () => { - const pistonActivateCallback = world.afterEvents.pistonActivate.subscribe.mock.calls[0][0]; const event = { block: { permutation: { diff --git a/__tests__/BP/scripts/src/rules/echoShardsEnableShriekers.test.js b/__tests__/BP/scripts/src/rules/echoShardsEnableShriekers.test.js index f437774e..6076640c 100644 --- a/__tests__/BP/scripts/src/rules/echoShardsEnableShriekers.test.js +++ b/__tests__/BP/scripts/src/rules/echoShardsEnableShriekers.test.js @@ -1,58 +1,29 @@ import { vi, it, describe, expect } from "vitest"; -import { echoShardsEnableShriekers } from "../../../../../Canopy [BP]/scripts/src/rules/echoShardsEnableShriekers"; +import { echoShardsEnableShriekers } from "../../../../../Canopy[BP]/scripts/src/rules/echoShardsEnableShriekers"; -vi.mock("@minecraft/server", () => ({ - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + system: { + ...original.system, + run: vi.fn((callback) => callback()) }, - runJob: vi.fn(), - run: vi.fn((callback) => callback()) - }, - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() - }, - playerInteractWithBlock: { - subscribe: vi.fn(), - unsubscribe: vi.fn() + world: { + ...original.world, + beforeEvents: { + ...original.world.beforeEvents, + playerInteractWithBlock: { subscribe: vi.fn(), unsubscribe: vi.fn() } } }, - afterEvents: { - worldLoad: { - subscribe: vi.fn() - } + BlockPermutation: { + resolve: (typeId, states) => ({ typeId: typeId, getAllStates: () => states }) }, - getDynamicProperty: vi.fn(), - setDynamicProperty: vi.fn(), - structureManager: { - place: vi.fn() - } - }, - BlockPermutation: { - resolve: (typeId, states) => ({ - typeId: typeId, - getAllStates: () => states - }) - }, - EntityComponentTypes: { - Equippable: 'minecraft:equippable' - }, - EquipmentSlot: { - Mainhand: 'mainhand' - }, - GameMode: { - Creative: 'creative', - Survival: 'survival' - } -})); - -vi.mock("@minecraft/server-ui", () => ({ - ModalFormData: vi.fn() -})); + EntityComponentTypes: { ...original.EntityComponentTypes, Equippable: 'minecraft:equippable' }, + EquipmentSlot: { Mainhand: 'mainhand' }, + GameMode: { Creative: 'creative', Survival: 'survival' } + }; +}); describe('echoShardsEnableShriekers', () => { it('should subscribe to player block placements when enabled', () => { diff --git a/__tests__/BP/scripts/src/rules/entitySeparation.test.js b/__tests__/BP/scripts/src/rules/entitySeparation.test.js index fd757336..4f47c2b1 100644 --- a/__tests__/BP/scripts/src/rules/entitySeparation.test.js +++ b/__tests__/BP/scripts/src/rules/entitySeparation.test.js @@ -1,43 +1,24 @@ import { vi, it, describe, expect, beforeEach } from "vitest"; -import { entitySeparation } from "../../../../../Canopy [BP]/scripts/src/rules/entitySeparation"; -import { Vector } from "../../../../../Canopy [BP]/scripts/lib/Vector"; +import { entitySeparation } from "../../../../../Canopy[BP]/scripts/src/rules/entitySeparation"; +import { Vector } from "../../../../../Canopy[BP]/scripts/lib/Vector"; -vi.mock("@minecraft/server", () => ({ - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } - }, - runJob: vi.fn(), - run: vi.fn((callback) => callback()) - }, - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() - } +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + system: { + ...original.system, + run: vi.fn((callback) => callback()) }, - afterEvents: { - worldLoad: { - subscribe: vi.fn() - }, - pressurePlatePush: { - subscribe: vi.fn(), - unsubscribe: vi.fn() + world: { + ...original.world, + afterEvents: { + ...original.world.afterEvents, + pressurePlatePush: { subscribe: vi.fn(), unsubscribe: vi.fn() } } - }, - getDynamicProperty: vi.fn(), - setDynamicProperty: vi.fn(), - structureManager: { - place: vi.fn() } - } -})); - -vi.mock("@minecraft/server-ui", () => ({ - ModalFormData: vi.fn() -})); + }; +}); describe('entitySeparation', () => { let successfulEvent = {}; diff --git a/__tests__/BP/scripts/src/rules/infodisplay/BlockStates.test.js b/__tests__/BP/scripts/src/rules/infodisplay/BlockStates.test.js index 7ecf62ac..4db8cf02 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/BlockStates.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/BlockStates.test.js @@ -1,40 +1,22 @@ -import { BlockStates } from '../../../../../../Canopy [BP]/scripts/src/rules/infodisplay/BlockStates'; +import { BlockStates } from '../../../../../../Canopy[BP]/scripts/src/rules/infodisplay/BlockStates'; import { describe, it, expect, beforeAll, vi } from 'vitest'; -import { InfoDisplayElement } from '../../../../../../Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplayElement'; -import { Rules } from '../../../../../../Canopy [BP]/scripts/lib/canopy/rules/Rules'; +import { Rules } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rules'; +import { InfoDisplayTextElement } from '../../../../../../Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayTextElement'; -vi.mock('@minecraft/server', () => ({ - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + world: { + ...original.world, + afterEvents: { + ...original.world.afterEvents, + worldLoad: { subscribe: (callback) => callback() } } }, - afterEvents: { - worldLoad: { - subscribe: (callback) => { - callback(); - } - } - }, - setDynamicProperty: vi.fn() - }, - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } - }, - runJob: vi.fn() - }, - LiquidType: { - Water: 'Water' - } -})); - -vi.mock("@minecraft/server-ui", () => ({ - ModalFormData: vi.fn() -})); + LiquidType: { Water: 'Water' } + }; +}); const mockPlayer = { getBlockFromViewDirection: vi.fn(() => ({ @@ -59,8 +41,8 @@ describe('BlockStates', () => { blockStates = new BlockStates(mockPlayer, 0); }); - it('should inherit from InfoDisplayElement', () => { - expect(blockStates).toBeInstanceOf(InfoDisplayElement); + it('should inherit from InfoDisplayTextElement', () => { + expect(blockStates).toBeInstanceOf(InfoDisplayTextElement); }); it('should create a new InfoDisplay rule', () => { @@ -88,4 +70,4 @@ describe('BlockStates', () => { text: `§7isWaterlogged: §3true` }); }); -}); \ No newline at end of file +}); diff --git a/__tests__/BP/scripts/src/rules/infodisplay/ChunkCoords.test.js b/__tests__/BP/scripts/src/rules/infodisplay/ChunkCoords.test.js index 6035b4d3..424a8b55 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/ChunkCoords.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/ChunkCoords.test.js @@ -1,37 +1,21 @@ -import { ChunkCoords } from '../../../../../../Canopy [BP]/scripts/src/rules/infodisplay/ChunkCoords'; +import { ChunkCoords } from '../../../../../../Canopy[BP]/scripts/src/rules/infodisplay/ChunkCoords'; import { describe, it, expect, beforeAll, vi } from 'vitest'; -import { InfoDisplayElement } from '../../../../../../Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplayElement'; -import { Rules } from '../../../../../../Canopy [BP]/scripts/lib/canopy/rules/Rules'; +import { Rules } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rules'; +import { InfoDisplayTextElement } from '../../../../../../Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayTextElement'; -vi.mock('@minecraft/server', () => ({ - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + world: { + ...original.world, + afterEvents: { + ...original.world.afterEvents, + worldLoad: { subscribe: (callback) => callback() } } - }, - afterEvents: { - worldLoad: { - subscribe: (callback) => { - callback(); - } - } - }, - setDynamicProperty: vi.fn() - }, - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } - }, - runJob: vi.fn() - } -})); - -vi.mock("@minecraft/server-ui", () => ({ - ModalFormData: vi.fn() -})); + } + }; +}); const mockPlayer = { location: { @@ -47,8 +31,8 @@ describe('BlockStates', () => { chunkCoords = new ChunkCoords(mockPlayer, 0); }); - it('should inherit from InfoDisplayElement', () => { - expect(chunkCoords).toBeInstanceOf(InfoDisplayElement); + it('should inherit from InfoDisplayTextElement', () => { + expect(chunkCoords).toBeInstanceOf(InfoDisplayTextElement); }); it('should create a new InfoDisplay rule', () => { @@ -64,4 +48,4 @@ describe('BlockStates', () => { ] }); }); -}); \ No newline at end of file +}); diff --git a/__tests__/BP/scripts/src/rules/infodisplay/Dimension.test.js b/__tests__/BP/scripts/src/rules/infodisplay/Dimension.test.js index 8e65d220..d73e69d0 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/Dimension.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/Dimension.test.js @@ -1,37 +1,21 @@ -import { Dimension } from '../../../../../../Canopy [BP]/scripts/src/rules/infodisplay/Dimension'; +import { Dimension } from '../../../../../../Canopy[BP]/scripts/src/rules/infodisplay/Dimension'; import { describe, it, expect, beforeAll, vi } from 'vitest'; -import { InfoDisplayElement } from '../../../../../../Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplayElement'; -import { Rules } from '../../../../../../Canopy [BP]/scripts/lib/canopy/rules/Rules'; - -vi.mock('@minecraft/server', () => ({ - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() - } - }, - afterEvents: { - worldLoad: { - subscribe: (callback) => { - callback(); - } - } - }, - setDynamicProperty: vi.fn() - }, - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() +import { InfoDisplayTextElement } from '../../../../../../Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayTextElement'; +import { Rules } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rules'; + +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + world: { + ...original.world, + afterEvents: { + ...original.world.afterEvents, + worldLoad: { subscribe: (callback) => callback() } } - }, - runJob: vi.fn() - } -})); - -vi.mock("@minecraft/server-ui", () => ({ - ModalFormData: vi.fn() -})); + } + }; +}); const mockPlayer = { dimension: { @@ -46,8 +30,8 @@ describe('Dimension', () => { dimension = new Dimension(mockPlayer, 0); }); - it('should inherit from InfoDisplayElement', () => { - expect(dimension).toBeInstanceOf(InfoDisplayElement); + it('should inherit from InfoDisplayTextElement', () => { + expect(dimension).toBeInstanceOf(InfoDisplayTextElement); }); it('should create a new InfoDisplay rule', () => { @@ -70,4 +54,4 @@ describe('Dimension', () => { mockPlayer.dimension.id = "minecraft:the_end"; expect(dimension.getFormattedDataOwnLine().rawtext[0]).toEqual({ text: '§d' }); }); -}); \ No newline at end of file +}); diff --git a/__tests__/BP/scripts/src/rules/infodisplay/HeldItemDurability.test.js b/__tests__/BP/scripts/src/rules/infodisplay/HeldItemDurability.test.js new file mode 100644 index 00000000..b78451ea --- /dev/null +++ b/__tests__/BP/scripts/src/rules/infodisplay/HeldItemDurability.test.js @@ -0,0 +1,85 @@ +import { HeldItemDurability } from '../../../../../../Canopy[BP]/scripts/src/rules/infodisplay/HeldItemDurability'; +import { describe, it, expect, beforeAll, beforeEach, vi } from 'vitest'; +import { InfoDisplayTextElement } from '../../../../../../Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayTextElement'; +import { Rules } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rules'; +import { ItemComponentTypes, Player } from '@minecraft/server'; + +const mockDurabilityComponent = { + maxDurability: 250, + damage: 0, +}; + +const mockItemStack = { + getComponent: vi.fn(), +}; + +const mockEquippableComponent = { + getEquipment: vi.fn(), +}; + +const mockPlayer = new Player(); +vi.spyOn(mockPlayer, 'getComponent').mockImplementation((type) => type === 'equippable' ? mockEquippableComponent : undefined); + +describe('HeldItemDurability', () => { + let heldItemDurability; + beforeAll(() => { + heldItemDurability = new HeldItemDurability(mockPlayer, 0); + }); + beforeEach(() => { + mockDurabilityComponent.damage = 0; + mockDurabilityComponent.maxDurability = 250; + mockItemStack.getComponent.mockImplementation((type) => + type === ItemComponentTypes.Durability ? mockDurabilityComponent : undefined + ); + mockEquippableComponent.getEquipment.mockReturnValue(mockItemStack); + mockPlayer.getComponent.mockReturnValue(mockEquippableComponent); + }); + + it('should inherit from InfoDisplayTextElement', () => { + expect(heldItemDurability).toBeInstanceOf(InfoDisplayTextElement); + }); + + it('should create a new InfoDisplay rule', () => { + expect(Rules.get(heldItemDurability.identifier)).toBeDefined(); + }); + + it('should return empty text when no item is held', () => { + mockEquippableComponent.getEquipment.mockReturnValueOnce(undefined); + expect(heldItemDurability.getFormattedDataOwnLine()).toEqual({ text: '' }); + }); + + it('should return empty text when held item has no durability component', () => { + mockItemStack.getComponent.mockReturnValueOnce(undefined); + expect(heldItemDurability.getFormattedDataOwnLine()).toEqual({ text: '' }); + }); + + it('should show green remaining for high durability (>=50%)', () => { + mockDurabilityComponent.damage = 0; + expect(heldItemDurability.getFormattedDataOwnLine()).toEqual({ + translate: 'rules.infoDisplay.heldItemDurability.display', + with: ['§a250§7/§a250§r'] + }); + }); + + it('should show yellow remaining for mid durability (10-49%)', () => { + mockDurabilityComponent.damage = 175; + expect(heldItemDurability.getFormattedDataOwnLine()).toEqual({ + translate: 'rules.infoDisplay.heldItemDurability.display', + with: ['§e75§7/§a250§r'] + }); + }); + + it('should show red remaining for low durability (<10%)', () => { + mockDurabilityComponent.damage = 232; + expect(heldItemDurability.getFormattedDataOwnLine()).toEqual({ + translate: 'rules.infoDisplay.heldItemDurability.display', + with: ['§c18§7/§a250§r'] + }); + }); + + it('should return same data for shared line as own line', () => { + expect(heldItemDurability.getFormattedDataSharedLine()).toEqual( + heldItemDurability.getFormattedDataOwnLine() + ); + }); +}); diff --git a/__tests__/BP/scripts/src/rules/infodisplay/PeekInventory.test.js b/__tests__/BP/scripts/src/rules/infodisplay/PeekInventory.test.js index 5ad401bf..47c967e8 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/PeekInventory.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/PeekInventory.test.js @@ -1,56 +1,28 @@ -import { PeekInventory } from '../../../../../../Canopy [BP]/scripts/src/rules/infodisplay/PeekInventory'; +import { PeekInventory } from '../../../../../../Canopy[BP]/scripts/src/rules/infodisplay/PeekInventory'; import { describe, it, expect, beforeAll, vi } from 'vitest'; -import { InfoDisplayElement } from '../../../../../../Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplayElement'; -import { Rules } from '../../../../../../Canopy [BP]/scripts/lib/canopy/rules/Rules'; +import { InfoDisplayTextElement } from '../../../../../../Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayTextElement'; +import { Rules } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rules'; -vi.mock('@minecraft/server', () => ({ - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + world: { + ...original.world, + afterEvents: { + ...original.world.afterEvents, + worldLoad: { subscribe: (callback) => callback() } } }, - afterEvents: { - worldLoad: { - subscribe: (callback) => { - callback(); - } - } - }, - setDynamicProperty: vi.fn() - }, - system: { - beforeEvents: { - startup: { - subscribe: vi.fn() - } - }, - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } - }, - runJob: vi.fn() - }, - ItemStack: vi.fn((typeId, amount) => ({ - typeId: typeId, - amount: amount || 1, - localizationKey: `item.${typeId.replace("minecraft:", '')}.name` - })), - CommandPermissionLevel: { - Any: 'Any' - }, - CustomCommandParamType: { - String: 'String' - }, - CustomCommandStatus: { - Failure: 'Failure' - } -})); - -vi.mock("@minecraft/server-ui", () => ({ - ModalFormData: vi.fn() -})); + ItemStack: vi.fn((typeId, amount) => ({ + typeId: typeId, + amount: amount || 1, + localizationKey: `item.${typeId.replace("minecraft:", '')}.name` + })), + CommandPermissionLevel: { Any: 'Any' }, + CustomCommandParamType: { String: 'String' } + }; +}); const mockPlayer = { getBlockFromViewDirection: vi.fn(() => ({ @@ -78,8 +50,8 @@ describe('BlockStates', () => { peekInventory = new PeekInventory(mockPlayer, 0); }); - it('should inherit from InfoDisplayElement', () => { - expect(peekInventory).toBeInstanceOf(InfoDisplayElement); + it('should inherit from InfoDisplayTextElement', () => { + expect(peekInventory).toBeInstanceOf(InfoDisplayTextElement); }); it('should create a new InfoDisplay rule', () => { @@ -94,4 +66,4 @@ describe('BlockStates', () => { { text: ": 27\n" } ]}); }); -}); \ No newline at end of file +}); diff --git a/__tests__/BP/scripts/src/rules/infodisplay/Speed.test.js b/__tests__/BP/scripts/src/rules/infodisplay/Speed.test.js index 20fad6ca..1bb0d10e 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/Speed.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/Speed.test.js @@ -1,38 +1,21 @@ -import { Speed } from '../../../../../../Canopy [BP]/scripts/src/rules/infodisplay/Speed'; +import { Speed } from '../../../../../../Canopy[BP]/scripts/src/rules/infodisplay/Speed'; import { describe, it, expect, beforeAll, vi } from 'vitest'; -import { InfoDisplayElement } from '../../../../../../Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplayElement'; -import { Rules } from '../../../../../../Canopy [BP]/scripts/lib/canopy/rules/Rules'; - -vi.mock('@minecraft/server', () => ({ - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() - } - }, - afterEvents: { - worldLoad: { - subscribe: (callback) => { - callback(); - } +import { InfoDisplayTextElement } from '../../../../../../Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayTextElement'; +import { Rules } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rules'; + +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + world: { + ...original.world, + afterEvents: { + ...original.world.afterEvents, + worldLoad: { subscribe: (callback) => callback() } } - }, - setDynamicProperty: vi.fn() - }, - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } - }, - runJob: vi.fn() - }, - TicksPerSecond: 20 -})); - -vi.mock("@minecraft/server-ui", () => ({ - ModalFormData: vi.fn() -})); + } + }; +}); const mockPlayer = { getVelocity: vi.fn(() => ({ x: 0, y: 0, z: 0 })), @@ -44,8 +27,8 @@ describe('Speed', () => { speed = new Speed(mockPlayer, 0); }); - it('should inherit from InfoDisplayElement', () => { - expect(speed).toBeInstanceOf(InfoDisplayElement); + it('should inherit from InfoDisplayTextElement', () => { + expect(speed).toBeInstanceOf(InfoDisplayTextElement); }); it('should create a new InfoDisplay rule', () => { @@ -64,4 +47,4 @@ describe('Speed', () => { text: "§d100.000§r m/s", }); }); -}); \ No newline at end of file +}); diff --git a/__tests__/BP/scripts/src/rules/infodisplay/Structures.test.js b/__tests__/BP/scripts/src/rules/infodisplay/Structures.test.js index 473f8076..9f72ed0f 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/Structures.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/Structures.test.js @@ -1,37 +1,21 @@ -import { Structures } from '../../../../../../Canopy [BP]/scripts/src/rules/infodisplay/Structures'; +import { Structures } from '../../../../../../Canopy[BP]/scripts/src/rules/infodisplay/Structures'; import { describe, it, expect, beforeAll, vi } from 'vitest'; -import { InfoDisplayElement } from '../../../../../../Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplayElement'; -import { Rules } from '../../../../../../Canopy [BP]/scripts/lib/canopy/rules/Rules'; - -vi.mock('@minecraft/server', () => ({ - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() - } - }, - afterEvents: { - worldLoad: { - subscribe: (callback) => { - callback(); - } - } - }, - setDynamicProperty: vi.fn() - }, - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() +import { InfoDisplayTextElement } from '../../../../../../Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayTextElement'; +import { Rules } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rules'; + +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + world: { + ...original.world, + afterEvents: { + ...original.world.afterEvents, + worldLoad: { subscribe: (callback) => callback() } } - }, - runJob: vi.fn() - } -})); - -vi.mock("@minecraft/server-ui", () => ({ - ModalFormData: vi.fn() -})); + } + }; +}); const mockPlayer = { dimension: { @@ -45,8 +29,8 @@ describe('Structures', () => { structures = new Structures(mockPlayer, 0); }); - it('should inherit from InfoDisplayElement', () => { - expect(structures).toBeInstanceOf(InfoDisplayElement); + it('should inherit from InfoDisplayTextElement', () => { + expect(structures).toBeInstanceOf(InfoDisplayTextElement); }); it('should create a new InfoDisplay rule', () => { @@ -67,4 +51,4 @@ describe('Structures', () => { { "translate": "rules.infodisplay.structures.display.none" }, ] }); }); -}); \ No newline at end of file +}); diff --git a/__tests__/BP/scripts/src/rules/infodisplay/Weather.test.js b/__tests__/BP/scripts/src/rules/infodisplay/Weather.test.js index 15de5bcb..110fd85a 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/Weather.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/Weather.test.js @@ -1,37 +1,21 @@ -import { Weather } from '../../../../../../Canopy [BP]/scripts/src/rules/infodisplay/Weather'; +import { Weather } from '../../../../../../Canopy[BP]/scripts/src/rules/infodisplay/Weather'; import { describe, it, expect, beforeAll, vi } from 'vitest'; -import { InfoDisplayElement } from '../../../../../../Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplayElement'; -import { Rules } from '../../../../../../Canopy [BP]/scripts/lib/canopy/rules/Rules'; +import { InfoDisplayTextElement } from '../../../../../../Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayTextElement'; +import { Rules } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rules'; -vi.mock('@minecraft/server', () => ({ - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + world: { + ...original.world, + afterEvents: { + ...original.world.afterEvents, + worldLoad: { subscribe: (callback) => callback() } } - }, - afterEvents: { - worldLoad: { - subscribe: (callback) => { - callback(); - } - } - }, - setDynamicProperty: vi.fn() - }, - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } - }, - runJob: vi.fn() - } -})); - -vi.mock("@minecraft/server-ui", () => ({ - ModalFormData: vi.fn() -})); + } + }; +}); const mockPlayer = { dimension: { @@ -45,8 +29,8 @@ describe('Weather', () => { weather = new Weather(mockPlayer, 0); }); - it('should inherit from InfoDisplayElement', () => { - expect(weather).toBeInstanceOf(InfoDisplayElement); + it('should inherit from InfoDisplayTextElement', () => { + expect(weather).toBeInstanceOf(InfoDisplayTextElement); }); it('should create a new InfoDisplay rule', () => { @@ -56,4 +40,4 @@ describe('Weather', () => { it('should have a method to return formatted weather', () => { expect(weather.getFormattedDataOwnLine()).toEqual({ translate: 'rules.infoDisplay.weather.display', with: ['Clear'] }); }); -}); \ No newline at end of file +}); diff --git a/__tests__/BP/scripts/src/rules/playerSit.test.js b/__tests__/BP/scripts/src/rules/playerSit.test.js index 46b90291..e8b2d488 100644 --- a/__tests__/BP/scripts/src/rules/playerSit.test.js +++ b/__tests__/BP/scripts/src/rules/playerSit.test.js @@ -1,6 +1,6 @@ -import { playerSit } from "../../../../../Canopy [BP]/scripts/src/rules/playerSit"; +import { playerSit } from "../../../../../Canopy[BP]/scripts/src/rules/playerSit"; import { expect, test, describe, vi, beforeAll, afterAll, afterEach } from "vitest"; -import { playerStartSneakEvent } from "../../../../../Canopy [BP]/scripts/src/events/PlayerStartSneakEvent"; +import { playerStartSneakEvent } from "../../../../../Canopy[BP]/scripts/src/events/PlayerStartSneakEvent"; const rideableEntity = { type: 'canopy:rideable', @@ -12,65 +12,36 @@ const rideableEntity = { setRotation: vi.fn() }; -vi.mock("@minecraft/server", () => ({ - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + system: { + ...original.system, + currentTick: (Date.now() / 50), + runInterval: vi.fn((callback, interval) => { + const intervalId = setInterval(callback, interval * 50); + return { clear: () => clearInterval(intervalId) }; + }), + clearRun: vi.fn((runner) => { runner.clear(); }) }, - runJob: vi.fn(), - currentTick: (Date.now() / 50), - runInterval: vi.fn((callback, interval) => { - const intervalId = setInterval(callback, interval * 50); - return { - clear: () => clearInterval(intervalId) - }; - }), - clearRun: vi.fn((runner) => { - runner.clear(); - }) - }, - world: { - getAllPlayers: vi.fn(() => [ - undefined, - { id: 'player1', inputInfo: { getButtonState: vi.fn(() => "Pressed" ) }, isOnGround: true, location: { x: 0, y: 0, z: 0 }, getRotation: vi.fn(() => ({ x: 0, y: 0 })) }, - { id: 'player2', inputInfo: { getButtonState: vi.fn(() => "Released" ) }, isOnGround: true, location: { x: 1, y: 1, z: 1 }, getRotation: vi.fn(() => ({ x: 0, y: 0 })) } - ]), - beforeEvents: { - chatSend: { - subscribe: vi.fn() - } + world: { + ...original.world, + getAllPlayers: vi.fn(() => [ + undefined, + { id: 'player1', inputInfo: { getButtonState: vi.fn(() => "Pressed") }, isOnGround: true, location: { x: 0, y: 0, z: 0 }, getRotation: vi.fn(() => ({ x: 0, y: 0 })) }, + { id: 'player2', inputInfo: { getButtonState: vi.fn(() => "Released") }, isOnGround: true, location: { x: 1, y: 1, z: 1 }, getRotation: vi.fn(() => ({ x: 0, y: 0 })) } + ]), + getDimension: vi.fn(() => ({ + getEntities: vi.fn(() => [rideableEntity]) + })) }, - afterEvents: { - worldLoad: { - subscribe: vi.fn() - } - }, - getDynamicProperty: vi.fn(), - setDynamicProperty: vi.fn(), - getDimension: vi.fn(() => ({ - getEntities: vi.fn(() => [rideableEntity]) - })) - }, - InputButton: { - Sneak: 'sneak' - }, - ButtonState: { - Pressed: 'Pressed', - Released: 'Released' - }, - EntityComponentTypes: { - Rideable: 'rideable' - }, - DimensionTypes: { - getAll: vi.fn(() => [{ typeId: 'overworld' }]) - } -})); - -vi.mock("@minecraft/server-ui", () => ({ - ModalFormData: vi.fn() -})); + InputButton: { Sneak: 'sneak' }, + ButtonState: { Pressed: 'Pressed', Released: 'Released' }, + EntityComponentTypes: { ...original.EntityComponentTypes, Rideable: 'rideable' }, + DimensionTypes: { getAll: vi.fn(() => [{ typeId: 'overworld' }]) } + }; +}); describe('playerSit', () => { beforeAll(() => { diff --git a/__tests__/BP/scripts/src/rules/renderEndGatewayExits.test.js b/__tests__/BP/scripts/src/rules/renderEndGatewayExits.test.js new file mode 100644 index 00000000..e2025e98 --- /dev/null +++ b/__tests__/BP/scripts/src/rules/renderEndGatewayExits.test.js @@ -0,0 +1,81 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { renderEndGatewayExits } from '../../../../../Canopy[BP]/scripts/src/rules/renderEndGatewayExits'; +import { world, system } from '@minecraft/server'; +import { scheduler } from '@forestoflight/minecraft-vitest-mocks'; + +vi.mock('@minecraft/server', async () => { + const serverModule = await import('@forestoflight/minecraft-vitest-mocks/server'); + class MockUnloadedChunksError extends Error {} + return { ...serverModule, UnloadedChunksError: MockUnloadedChunksError }; +}); + +describe('RenderEndGatewayExits', () => { + let mockDimension; + + beforeEach(() => { + mockDimension = { + id: 'minecraft:the_end', + heightRange: { min: -64, max: 320 }, + getBlocks: vi.fn(() => ({ getCapacity: () => 0 })), + getBlock: vi.fn(() => ({ id: 'minecraft:end_stone' })), + }; + world.getDimension.mockReturnValue(mockDimension); + world.getPlayers.mockReturnValue([]); + system.runInterval.mockImplementation((callback, interval) => scheduler.scheduleInterval(callback, interval)); + system.runTimeout.mockImplementation((callback, delay) => scheduler.scheduleDelay(callback, delay)); + system.clearRun.mockImplementation((id) => scheduler.delete(id)); + renderEndGatewayExits.stop(); + }); + + afterEach(() => { + renderEndGatewayExits.stop(); + }); + + describe('constructor', () => { + it('has the correct rule identifier', () => { + expect(renderEndGatewayExits.getID()).toBe('renderEndGatewayExits'); + }); + }); + + describe('start', () => { + it('creates an endGatewayExitFinder', () => { + renderEndGatewayExits.start(); + expect(renderEndGatewayExits.endGatewayExitFinder).toBeDefined(); + }); + + it('destroys the existing finder before creating a new one', () => { + renderEndGatewayExits.start(); + const destroySpy = vi.spyOn(renderEndGatewayExits.endGatewayExitFinder, 'destroy'); + renderEndGatewayExits.start(); + expect(destroySpy).toHaveBeenCalled(); + }); + }); + + describe('stop', () => { + it('destroys the finder and sets it to undefined', () => { + renderEndGatewayExits.start(); + const destroySpy = vi.spyOn(renderEndGatewayExits.endGatewayExitFinder, 'destroy'); + renderEndGatewayExits.stop(); + expect(destroySpy).toHaveBeenCalled(); + expect(renderEndGatewayExits.endGatewayExitFinder).toBeUndefined(); + }); + + it('does nothing when no finder exists', () => { + expect(() => renderEndGatewayExits.stop()).not.toThrow(); + }); + }); + + describe('enable and disable callbacks', () => { + it('calls start() when the rule is enabled', () => { + const startSpy = vi.spyOn(renderEndGatewayExits, 'start'); + renderEndGatewayExits.setValue(true); + expect(startSpy).toHaveBeenCalled(); + }); + + it('calls stop() when the rule is disabled', () => { + const stopSpy = vi.spyOn(renderEndGatewayExits, 'stop'); + renderEndGatewayExits.setValue(false); + expect(stopSpy).toHaveBeenCalled(); + }); + }); +}); diff --git a/__tests__/BP/scripts/src/rules/spawnEggSpawnWithMinecart.test.js b/__tests__/BP/scripts/src/rules/spawnEggSpawnWithMinecart.test.js index 517eedc0..46fe5990 100644 --- a/__tests__/BP/scripts/src/rules/spawnEggSpawnWithMinecart.test.js +++ b/__tests__/BP/scripts/src/rules/spawnEggSpawnWithMinecart.test.js @@ -1,4 +1,4 @@ -import { spawnEggSpawnWithMinecart } from "../../../../../Canopy [BP]/scripts/src/rules/spawnEggSpawnWithMinecart"; +import { spawnEggSpawnWithMinecart } from "../../../../../Canopy[BP]/scripts/src/rules/spawnEggSpawnWithMinecart"; import { expect, describe, vi, afterEach, it } from "vitest"; const mockMinecart = { @@ -15,62 +15,30 @@ const mockMinecart = { })) }; -vi.mock("@minecraft/server", () => ({ - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - }, - playerPlaceBlock: { - subscribe: vi.fn() - } - }, - runJob: vi.fn(), - run: vi.fn((callback) => callback()), - runInterval: vi.fn((callback, interval) => { - const intervalId = setInterval(callback, interval * 50); - return { - clear: () => clearInterval(intervalId) - }; - }), - clearRun: vi.fn((runner) => { - runner.clear(); - }) - }, - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() - } +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + system: { + ...original.system, + run: vi.fn((callback) => callback()), + runInterval: vi.fn((callback, interval) => { + const intervalId = setInterval(callback, interval * 50); + return { clear: () => clearInterval(intervalId) }; + }), + clearRun: vi.fn((runner) => { runner.clear(); }) }, - afterEvents: { - worldLoad: { - subscribe: vi.fn() - }, - playerInteractWithBlock: { - subscribe: vi.fn(), - unsubscribe: vi.fn() - }, - entitySpawn: { - subscribe: vi.fn(), - unsubscribe: vi.fn() + world: { + ...original.world, + afterEvents: { + ...original.world.afterEvents, + playerInteractWithBlock: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + entitySpawn: { subscribe: vi.fn(), unsubscribe: vi.fn() } } }, - getDynamicProperty: vi.fn(), - setDynamicProperty: vi.fn(), - structureManager: { - place: vi.fn() - } - }, - EntityComponentTypes: { - Rideable: 'rideable', - Riding: 'riding' - } -})); - -vi.mock("@minecraft/server-ui", () => ({ - ModalFormData: vi.fn() -})); + EntityComponentTypes: { ...original.EntityComponentTypes, Rideable: 'rideable', Riding: 'riding' } + }; +}); describe('spawnEggSpawnWithMinecart', () => { afterEach(() => { diff --git a/__tests__/BP/scripts/src/rules/tntFuse.test.js b/__tests__/BP/scripts/src/rules/tntFuse.test.js index 11b5ab18..ea678491 100644 --- a/__tests__/BP/scripts/src/rules/tntFuse.test.js +++ b/__tests__/BP/scripts/src/rules/tntFuse.test.js @@ -1,5 +1,6 @@ import { describe, it, expect, vi, afterEach } from "vitest"; -import { tntFuseRule } from "../../../../../Canopy [BP]/scripts/src/rules/tntFuse"; +import { tntFuseRule } from "../../../../../Canopy[BP]/scripts/src/rules/tntFuse"; +import { world } from "@minecraft/server"; const tntEntity = { typeId: 'minecraft:tnt', @@ -13,50 +14,28 @@ const tntEntity = { }; let tntFuseDP = false; -vi.mock("@minecraft/server", () => ({ - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } - }, - runJob: vi.fn(), - currentTick: (Date.now() / 50), - runInterval: vi.fn((callback, interval) => { - const intervalId = setInterval(callback, interval * 50); - return { - clear: () => clearInterval(intervalId) - }; - }), - clearRun: vi.fn((runner) => { - runner.clear(); - }) - }, - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() - } +vi.mock('@minecraft/server', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + system: { + ...original.system, + currentTick: (Date.now() / 50), + runInterval: vi.fn((callback, interval) => { + const intervalId = setInterval(callback, interval * 50); + return { clear: () => clearInterval(intervalId) }; + }), + clearRun: vi.fn((runner) => { runner.clear(); }) }, - afterEvents: { - worldLoad: { - subscribe: vi.fn() - }, - entitySpawn: { - subscribe: vi.fn() - }, - entityLoad: { - subscribe: vi.fn() + world: { + ...original.world, + afterEvents: { + ...original.world.afterEvents, + entityLoad: { subscribe: vi.fn() } } - }, - setDynamicProperty: (identifier, ticks) => { tntFuseDP = ticks }, - getDynamicProperty: () => tntFuseDP - } -})); - -vi.mock("@minecraft/server-ui", () => ({ - ModalFormData: vi.fn() -})); + } + }; +}); describe('tntFuseRule', () => { afterEach(() => { @@ -82,7 +61,7 @@ describe('tntFuseRule', () => { }); it('should properly initialize the fuse ticks DP', () => { - tntFuseDP = void 0; + world.setDynamicProperty(tntFuseRule.getID(), void 0) expect(tntFuseRule.getGlobalFuseTicks()).toBe(80); }); @@ -92,4 +71,4 @@ describe('tntFuseRule', () => { tntFuseRule.onEntityLoad({ entity: tntEntity }); expect(startFuseMock).toHaveBeenCalledWith(tntEntity, 15); }); -}); \ No newline at end of file +}); diff --git a/__tests__/manifests.test.js b/__tests__/manifests.test.js index bd9d6001..ca91fa31 100644 --- a/__tests__/manifests.test.js +++ b/__tests__/manifests.test.js @@ -1,10 +1,10 @@ import { describe, it, expect, beforeAll } from "vitest"; import fs from "fs"; import path from 'path'; -import { PACK_VERSION, MC_VERSION } from "../Canopy [BP]/scripts/constants"; +import { PACK_VERSION, MC_VERSION } from "../Canopy[BP]/scripts/constants"; -const manifestPathBP = path.resolve('Canopy [BP]/manifest.json'); -const manifestPathRP = path.resolve('Canopy [RP]/manifest.json'); +const manifestPathBP = path.resolve('Canopy[BP]/manifest.json'); +const manifestPathRP = path.resolve('Canopy[RP]/manifest.json'); function getManifestObject(manifestPath) { const manifestContent = fs.readFileSync(manifestPath, 'utf-8'); diff --git a/config.json b/config.json index a4221e2c..d4c82014 100644 --- a/config.json +++ b/config.json @@ -3,12 +3,25 @@ "author": "ForestOfLight", "name": "Canopy", "packs": { - "behaviorPack": "./Canopy [BP]", - "resourcePack": "./Canopy [RP]" + "behaviorPack": "./Canopy[BP]", + "resourcePack": "./Canopy[RP]" }, "regolith": { "dataPath": "./data", - "filterDefinitions": {}, + "filterDefinitions": { + "bump_version": { + "runWith": "nodejs", + "script": "filters/bump_version/main.js" + }, + "update_mob_data": { + "runWith": "nodejs", + "script": "filters/update_mob_data/main.js" + }, + "package_mcaddon": { + "runWith": "nodejs", + "script": "filters/package_mcaddon/main.js" + } + }, "formatVersion": "1.4.0", "profiles": { "default": { @@ -30,6 +43,23 @@ "bpName": "project.name+'[BP]'" }, "filters": [] + }, + "release": { + "export": { + "target": "none" + }, + "filters": [ + { "filter": "update_mob_data" }, + { "filter": "package_mcaddon" } + ] + }, + "bump-version": { + "export": { + "target": "none" + }, + "filters": [ + { "filter": "bump_version" } + ] } } } diff --git a/docs/TRANSLATING.md b/docs/TRANSLATING.md new file mode 100644 index 00000000..e7e19e04 --- /dev/null +++ b/docs/TRANSLATING.md @@ -0,0 +1,104 @@ +# Contributing a Translation to Canopy + +Canopy currently supports: American English, German, Indonesian, Chinese, Welsh, and Japanese. New translations are always welcome! + +## Getting Started + +### 1. Fork and clone the repository + +1. Go to [github.com/ForestOfLight/Canopy](https://github.com/ForestOfLight/Canopy) and click **Fork** (top right). +2. Clone your fork (use the `dev` branch): + ```bash + git clone -b dev https://github.com//Canopy.git + cd Canopy + ``` +3. Create a new branch for your translation: + ```bash + git checkout -b translation/fr_FR + ``` + +## Adding or Updating a Translation + +### Updating an existing language + +Open the existing `.lang` file for your locale in `Canopy[RP]/texts/` (e.g., `de_DE.lang`) and update any strings that need changing. + +### Adding a new language + +1. Copy the English reference file and rename it to your locale code: + ```bash + cp "Canopy[RP]/texts/en_US.lang" "Canopy[RP]/texts/fr_FR.lang" + ``` + Valid locale codes are listed at [wiki.bedrock.dev/text/text-intro#vanilla-languages](https://wiki.bedrock.dev/text/text-intro#vanilla-languages). + +2. Add your locale code to `Canopy[RP]/texts/languages.json`: + ```json + [ + "en_US", + "de_DE", + "id_ID", + "zh_CN", + "cy_GB", + "ja_JP", + "fr_FR" + ] + ``` + +### Translating the strings + +Open your `.lang` file. Each line is a key-value pair: + +``` +commands.help=Displays help pages. +``` + +- **Translate the value only** (right side of `=`) — never change the key (left side) +- **Preserve format codes exactly** — `§a`, `§r`, `§l` are color/style codes; `%s`, `%1`, `%2` are placeholders that get filled in at runtime. Changing or removing them will break the output. + +Example — correct: +``` +commands.help=Zeigt Hilfeseiten an. +``` + +Example — incorrect (key changed): +``` +befehle.hilfe=Zeigt Hilfeseiten an. +``` + +## Testing Your Translation + +1. Place the Canopy resource pack in your Minecraft `development_resource_packs` folder. +2. Launch Minecraft and set your game language to the locale you translated. +3. Load a world with the Canopy pack applied. +4. Test strings in-game — check commands, messages, and UI text. + +**Tip:** If you make changes to the `.lang` file while Minecraft is running, you can reload without restarting using: + +``` +/reload all +``` + +This only works when the pack is in `development_resource_packs`. + +## Submitting a PR + +1. Stage only your translation files: + ```bash + git add "Canopy[RP]/texts/fr_FR.lang" + # If you added a new language, also stage languages.json: + git add "Canopy[RP]/texts/languages.json" + ``` +2. Commit: + ```bash + git commit -m "translation: add fr_FR" + ``` +3. Push to your fork: + ```bash + git push origin translation/fr_FR + ``` +4. Open a pull request on GitHub targeting the **`dev` branch**. + - Your PR should only contain changes to your `.lang` file (and `languages.json` if it's a new language). Any other changes belong in a separate PR. + +## Questions? + +Reach out on the [Canopy Discord](https://discord.gg/9KGche8fxm) — we're happy to help. diff --git a/docs/scripts/generate-wiki.js b/docs/scripts/generate-wiki.js new file mode 100644 index 00000000..a0ef5ad8 --- /dev/null +++ b/docs/scripts/generate-wiki.js @@ -0,0 +1,218 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const currPath = fileURLToPath(new URL('.', import.meta.url)); +const projectRoot = path.resolve(currPath, '../../'); + +function parseLangFile(langPath) { + const content = fs.readFileSync(langPath, 'utf8'); + const map = {}; + for (const line of content.split('\n')) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith('#')) continue; + const eqIdx = trimmed.indexOf('='); + if (eqIdx === -1) continue; + map[trimmed.slice(0, eqIdx).trim()] = trimmed.slice(eqIdx + 1).trim(); + } + return map; +} + +function resolveDescription(desc, lang) { + if (!desc) return ''; + if (typeof desc === 'string') return lang[desc] ?? desc; + if (desc.translate) return lang[desc.translate] ?? desc.translate; + if (desc.text) return desc.text; + return ''; +} + +const PARAM_TYPE_DISPLAY = { + Boolean: 'bool', + Enum: null, // replaced by enum values inline + Float: 'float', + Integer: 'int', + Location: 'x y z', + String: 'string', + EntitySelector: 'entity', + EntityType: 'entityType', + BlockType: 'blockType', +}; + +function buildParamDisplay(param, enums) { + if (param.type === 'Enum') { + const enumDef = enums?.find(e => e.name === param.name); + return enumDef ? enumDef.values.join('/') : param.name; + } + return PARAM_TYPE_DISPLAY[param.type] ?? param.name; +} + +function buildVanillaCommandBlock(cmd, lang) { + const cc = cmd.customCommand; + const cmdName = cmd.getName(); + const sub = cmd.getSubCommandWikiDescription(); + const isOp = cc.permissionLevel && cc.permissionLevel !== 'Any'; + const opSuffix = isOp ? ' Requires OP.' : ''; + + if (Object.keys(sub).length === 0) { + // Plain command — build single usage string + const parts = [`/${cmdName}`]; + for (const p of (cc.mandatoryParameters || [])) { + const display = buildParamDisplay(p, cc.enums); + const label = p.name.replace(/^[^:]+:/, ''); + parts.push(`<${label}: ${display}>`); + } + for (const p of (cc.optionalParameters || [])) { + const display = buildParamDisplay(p, cc.enums); + const label = p.name.replace(/^[^:]+:/, ''); + parts.push(`[${label}: ${display}]`); + } + const desc = cc.wikiDescription ?? resolveDescription(cc.description, lang); + return `**Usage: \`${parts.join(' ')}\`** \n${desc}${opSuffix}`; + } + + // Sub-command style — one block per enum value + const remainingMandatory = (cc.mandatoryParameters || []).slice(1); + const blocks = []; + for (const [enumVal, info] of Object.entries(sub)) { + const selectedOptional = (info.params || []).map(pName => + (cc.optionalParameters || []).find(p => + p.name === pName || p.name.endsWith(`:${pName}`) + ) + ).filter(Boolean); + const usageParts = [`/${cmdName}`, enumVal]; + for (const p of remainingMandatory) { + const display = buildParamDisplay(p, cc.enums); + const label = p.name.replace(/^[^:]+:/, ''); + usageParts.push(`<${label}: ${display}>`); + } + for (const p of selectedOptional) { + const display = buildParamDisplay(p, cc.enums); + const label = p.name.replace(/^[^:]+:/, ''); + usageParts.push(`[${label}: ${display}]`); + } + blocks.push(`**Usage: \`${usageParts.join(' ')}\`** \n${info.description}${opSuffix}`); + } + return blocks.join('\n\n'); +} + +function buildCommandBlock(cmd, lang) { + const isOp = cmd.isOpOnly(); + const opSuffix = isOp ? ' Requires OP.' : ''; + const entries = cmd.getHelpEntries(); + + if (entries.length === 0) { + const usage = cmd.getUsage(); + const desc = cmd.getWikiDescription() ?? resolveDescription(cmd.getDescription(), lang); + return `**Usage: \`${usage}\`** \n${desc}${opSuffix}`; + } + + return entries.map(e => { + const prefix = cmd.getUsage().startsWith('/') ? '/' : './'; + const usage = `${prefix}${e.usage}`; + const desc = e.wikiDescription ?? resolveDescription(e.description, lang); + return `**Usage: \`${usage}\`** \n${desc}${opSuffix}`; + }).join('\n\n'); +} + +function buildRuleEntry(rule, lang) { + const id = rule.getID(); + const desc = rule.getWikiDescription() + ?? lang[`rules.${id}`] + ?? lang[`rules.infoDisplay.${id}`] + ?? lang[`rules.infodisplay.${id}`]; + if (!desc) process.stderr.write(`[wiki-gen] No description found for rule: ${id}\n`); + const type = rule.getType(); + const defaultVal = rule.getDefaultValue(); + const suggested = rule.getSuggestedOptions(); + + let entry = `## ${id}\n`; + if (desc) entry += `${desc}\n\n`; + entry += `- Type: \`${type}\`\n`; + entry += `- Default value: \`${defaultVal}\`\n`; + if (suggested) entry += `- Suggested options: ${suggested.map(v => `\`${v}\``).join(', ')}\n`; + return entry; +} + +function generateRulesPage(rules, lang) { + const sorted = [...rules].sort((a, b) => a.getID().localeCompare(b.getID())); + const toc = sorted.map(r => `- [${r.getID()}](#${r.getID().toLowerCase()})`).join('\n'); + const entries = sorted.map(r => buildRuleEntry(r, lang)).join('\n'); + return `

\npack_icon\n

\n\n**Table of Contents:**\n${toc}\n\n---\n\n${entries}`; +} + +function injectCommandsPage(template, commandMap, lang) { + const usedKeys = new Set(); + let result = template.replace(/\{\{(\w+)\}\}/g, (match, key) => { + usedKeys.add(key); + const cmd = commandMap.get(key); + if (!cmd) { + process.stderr.write(`[wiki-gen] No command found for placeholder: {{${key}}}\n`); + return match; + } + return cmd.isVanilla + ? buildVanillaCommandBlock(cmd.instance, lang) + : buildCommandBlock(cmd.instance, lang); + }); + + const unlisted = [...commandMap.entries()] + .filter(([key]) => !usedKeys.has(key)) + .map(([, cmd]) => { + const block = cmd.isVanilla + ? buildVanillaCommandBlock(cmd.instance, lang) + : buildCommandBlock(cmd.instance, lang); + const name = cmd.instance.getName(); + return `### ${name}\n${block}`; + }); + + if (unlisted.length > 0) + result += `\n\n## Unlisted Commands\n\n${unlisted.join('\n\n')}`; + + + return result; +} + +export async function main(wikiPath) { + await import('../../Canopy[BP]/scripts/main.js'); + const lang = parseLangFile(path.join(projectRoot, 'Canopy[RP]/texts/en_US.lang')); + await generateRulesPages(wikiPath, lang); + await generateCommandsPage(wikiPath, lang); +} + +async function generateRulesPages(wikiPath, lang) { + const { Rules } = await import('../../Canopy[BP]/scripts/lib/canopy/rules/Rules.js'); + const { InfoDisplay } = await import('../../Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplay.js'); + new InfoDisplay({ id: 'wiki-gen-mock', setDynamicProperty: () => {}, getDynamicProperty: () => {} }); + + const allRules = Rules.getAll(); + const globalRules = allRules.filter(r => r.getCategory() === 'Rules'); + const infoDisplayRules = allRules.filter(r => r.getCategory() === 'InfoDisplay'); + + fs.writeFileSync(path.join(wikiPath, 'Global-Rules.md'), generateRulesPage(globalRules, lang), 'utf8'); + fs.writeFileSync(path.join(wikiPath, 'InfoDisplay-Rules.md'), generateRulesPage(infoDisplayRules, lang), 'utf8'); + console.log('✓ Generated Global-Rules.md and InfoDisplay-Rules.md'); +} + +async function generateCommandsPage(wikiPath, lang) { + const commandMap = await getCommandMap(); + const commandsTemplatePath = path.join(wikiPath, 'Commands.md'); + const template = fs.readFileSync(commandsTemplatePath, 'utf8'); + const injected = injectCommandsPage(template, commandMap, lang); + fs.writeFileSync(commandsTemplatePath, injected, 'utf8'); + console.log('✓ Injected Commands.md'); +} + +async function getCommandMap() { + const { VanillaCommands } = await import('../../Canopy[BP]/scripts/lib/canopy/commands/VanillaCommands.js'); + const { Commands } = await import('../../Canopy[BP]/scripts/lib/canopy/commands/Commands.js'); + + const commandMap = new Map(); + for (const cmd of Commands.getAll()) { + if (cmd.getExtension()) continue; + if (cmd.isHelpHidden()) continue; + commandMap.set(cmd.getName(), { instance: cmd, isVanilla: false }); + } + for (const cmd of VanillaCommands.getAll()) + commandMap.set(cmd.getName(), { instance: cmd, isVanilla: true }); + + return commandMap; +} \ No newline at end of file diff --git a/docs/scripts/generate-wiki.test.js b/docs/scripts/generate-wiki.test.js new file mode 100644 index 00000000..beaced9c --- /dev/null +++ b/docs/scripts/generate-wiki.test.js @@ -0,0 +1,17 @@ +import { describe, it } from 'vitest'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +describe('Wiki generator', () => { + it('generates wiki pages', { timeout: 60000 }, async () => { + const wikiPath = process.env.WIKI_PATH; + if (!wikiPath) + throw new Error('WIKI_PATH argument is required.\nUsage: npm run generate-wiki '); + + const currPath = fileURLToPath(new URL('.', import.meta.url)); + const resolvedWikiPath = path.resolve(currPath, wikiPath); + + const { main } = await import('./generate-wiki.js'); + await main(resolvedWikiPath); + }); +}); diff --git a/filters/bump_version/main.js b/filters/bump_version/main.js new file mode 100644 index 00000000..23ad2c49 --- /dev/null +++ b/filters/bump_version/main.js @@ -0,0 +1,117 @@ +import fs from 'fs'; +import path from 'path'; +import readline from 'readline'; +import { fileURLToPath } from 'url'; + +const dirname = path.dirname(fileURLToPath(import.meta.url)); +const projectRoot = path.resolve(dirname, '../..'); +const settings = JSON.parse(process.env.FILTER_SETTINGS || '{}'); + +function bumpVersion(versionStr, type) { + const parts = versionStr.split('.').map(Number); + if (type === 'major') { parts[0]++; parts[1] = 0; parts[2] = 0; } + else if (type === 'minor') { parts[1]++; parts[2] = 0; } + else { parts[2]++; } + return parts.join('.'); +} + +function versionToArray(versionStr) { + return versionStr.split('.').map(Number); +} + +function getCurrentVersion() { + const content = fs.readFileSync( + path.join(projectRoot, 'Canopy[BP]', 'scripts', 'constants.js'), 'utf8' + ); + const match = content.match(/PACK_VERSION\s*=\s*['"]([^'"]+)['"]/); + if (!match) throw new Error('Could not find PACK_VERSION in constants.js'); + return match[1]; +} + +function updateConstants(filePath, newVersion) { + let content = fs.readFileSync(filePath, 'utf8'); + content = content.replace( + /(PACK_VERSION\s*=\s*['"])[^'"]+(['"])/, + `$1${newVersion}$2` + ); + fs.writeFileSync(filePath, content, 'utf8'); +} + +function detectIndent(content) { + const match = content.match(/^[ \t]+/m); + return match ? match[0] : ' '; +} + +function updateManifest(filePath, newVersion) { + const content = fs.readFileSync(filePath, 'utf8'); + const indent = detectIndent(content); + const data = JSON.parse(content); + const versionArray = versionToArray(newVersion); + + data.header.name = data.header.name.replace(/v[\d.]+/, `v${newVersion}`); + data.header.version = versionArray; + + for (const dep of (data.dependencies || [])) { + if (Array.isArray(dep.version)) + dep.version = versionArray; + } + + fs.writeFileSync(filePath, JSON.stringify(data, null, indent) + '\n', 'utf8'); +} + +function promptBumpType(currentVersion) { + return new Promise((resolve, reject) => { + const patch = bumpVersion(currentVersion, 'patch'); + const minor = bumpVersion(currentVersion, 'minor'); + const major = bumpVersion(currentVersion, 'major'); + + const ttyInput = fs.createReadStream('/dev/tty'); + const ttyOutput = fs.createWriteStream('/dev/tty'); + const rl = readline.createInterface({ input: ttyInput, output: ttyOutput }); + + ttyOutput.write(`[bump-version] Current version: v${currentVersion}\n`); + ttyOutput.write(` 1) patch v${currentVersion} → v${patch}\n`); + ttyOutput.write(` 2) minor v${currentVersion} → v${minor}\n`); + ttyOutput.write(` 3) major v${currentVersion} → v${major}\n`); + + rl.question('Select bump type [1-3] (default: 1): ', (answer) => { + rl.close(); + ttyInput.destroy(); + const choice = answer.trim() || '1'; + if (choice === '2') resolve('minor'); + else if (choice === '3') resolve('major'); + else resolve('patch'); + }); + + ttyInput.on('error', reject); + }); +} + +async function main() { + const currentVersion = getCurrentVersion(); + + let bumpType; + try { + bumpType = await promptBumpType(currentVersion); + } catch { + bumpType = settings.bumpType || 'patch'; + } + + const newVersion = bumpVersion(currentVersion, bumpType); + console.log(`[bump-version] ${currentVersion} → ${newVersion}`); + + updateConstants(path.join(projectRoot, 'Canopy[BP]', 'scripts', 'constants.js'), newVersion); + updateManifest(path.join(projectRoot, 'Canopy[BP]', 'manifest.json'), newVersion); + updateManifest(path.join(projectRoot, 'Canopy[RP]', 'manifest.json'), newVersion); + + updateConstants('BP/scripts/constants.js', newVersion); + updateManifest('BP/manifest.json', newVersion); + updateManifest('RP/manifest.json', newVersion); + + console.log(`[bump-version] Updated to v${newVersion}`); +} + +main().catch(err => { + console.error('[bump-version] Error:', err.message); + process.exit(1); +}); diff --git a/filters/package_mcaddon/main.js b/filters/package_mcaddon/main.js new file mode 100644 index 00000000..17f09bf3 --- /dev/null +++ b/filters/package_mcaddon/main.js @@ -0,0 +1,43 @@ +import fs from 'fs'; +import path from 'path'; +import { execFileSync } from 'child_process'; +import { fileURLToPath } from 'url'; + +const dirname = path.dirname(fileURLToPath(import.meta.url)); +const projectRoot = path.resolve(dirname, '../..'); +const buildDir = path.join(projectRoot, 'build'); +const projectName = JSON.parse(fs.readFileSync(path.join(projectRoot, 'config.json'), 'utf8')).name; + +function getPackVersion() { + const content = fs.readFileSync(path.join(projectRoot, `${projectName}[BP]`, 'scripts', 'constants.js'), 'utf8'); + const match = content.match(/PACK_VERSION\s*=\s*['"]([^'"]+)['"]/); + return match ? match[1] : 'unknown'; +} + +function main() { + fs.mkdirSync(buildDir, { recursive: true }); + + const version = getPackVersion(); + const outputPath = path.join(buildDir, `${projectName}-v${version}.mcaddon`); + + if (fs.existsSync(outputPath)) + fs.unlinkSync(outputPath); + + fs.renameSync('BP', `${projectName}[BP]`); + fs.renameSync('RP', `${projectName}[RP]`); + + const licenseSrc = path.join(projectRoot, 'LICENSE'); + fs.copyFileSync(licenseSrc, path.join(`${projectName}[BP]`, 'LICENSE')); + fs.copyFileSync(licenseSrc, path.join(`${projectName}[RP]`, 'LICENSE')); + + console.log(`[package-mcaddon] Creating ${projectName}-v${version}.mcaddon...`); + execFileSync('zip', ['-r', outputPath, `${projectName}[BP]`, `${projectName}[RP]`], { stdio: 'inherit' }); + console.log(`[package-mcaddon] Saved to: ${outputPath}`); +} + +try { + main(); +} catch (err) { + console.error('[package-mcaddon] Error:', err.message); + process.exit(1); +} diff --git a/filters/update_mob_data/main.js b/filters/update_mob_data/main.js new file mode 100644 index 00000000..caaa0c78 --- /dev/null +++ b/filters/update_mob_data/main.js @@ -0,0 +1,121 @@ +import fs from 'fs'; + +const GITHUB_API = 'https://api.github.com/repos/Mojang/bedrock-samples'; +const RAW_BASE = 'https://raw.githubusercontent.com/Mojang/bedrock-samples'; + +function stripJsonComments(text) { + return text + .replace(/\/\*[\s\S]*?\*\//g, '') + .replace(/\/\/[^\n]*/g, ''); +} + +async function githubFetch(url) { + const res = await fetch(url, { + headers: { + 'Accept': 'application/vnd.github.v3+json', + 'User-Agent': 'regolith-update-mob-data-filter' + } + }); + if (!res.ok) { + const msg = await res.text(); + throw new Error(`GitHub API ${res.status} for ${url}: ${msg}`); + } + return res.json(); +} + +async function getLatestStableTag() { + const tags = await githubFetch(`${GITHUB_API}/tags?per_page=20`); + const stable = tags.find(t => !t.name.includes('preview')); + if (!stable) throw new Error('No stable tag found in first 20 tags'); + return stable.name; +} + +async function walkDir(dirPath, ref) { + const results = []; + const items = await githubFetch(`${GITHUB_API}/contents/${dirPath}?ref=${encodeURIComponent(ref)}`); + for (const item of items) { + if (item.type === 'file') + results.push(item); + else if (item.type === 'dir') + results.push(...(await walkDir(item.path, ref))); + } + return results; +} + +async function buildCategoryToMobMap(tag) { + const files = await walkDir('behavior_pack/spawn_rules', tag); + const map = {}; + + await Promise.all(files.map(async (file) => { + if (!file.name.endsWith('.json')) return; + const mobName = file.name.slice(0, -5); + const res = await fetch(`${RAW_BASE}/${tag}/behavior_pack/spawn_rules/${file.name}`); + const text = await res.text(); + const data = JSON.parse(stripJsonComments(text)); + const category = data?.['minecraft:spawn_rules']?.description?.population_control ?? 'none'; + if (!map[category]) map[category] = []; + map[category].push(mobName); + })); + + for (const cat of Object.keys(map)) map[cat].sort(); + return map; +} + +async function buildMeleeMobs(tag) { + const files = await walkDir('behavior_pack/entities', tag); + const meleeMobs = []; + + await Promise.all(files.map(async (file) => { + if (!file.name.endsWith('.json')) return; + const mobName = file.name.slice(0, -5); + const res = await fetch(`${RAW_BASE}/${tag}/behavior_pack/entities/${file.name}`); + const text = await res.text(); + if (text.includes('"minecraft:attack"')) + meleeMobs.push(mobName); + })); + + return meleeMobs.sort(); +} + +function formatMobMap(map) { + const entries = Object.entries(map).map(([cat, mobs]) => { + const mobList = mobs.map(m => ` '${m}'`).join(',\n'); + return ` '${cat}' : [\n${mobList}\n ]`; + }); + return `export const categoryToMobMap = {\n${entries.join(',\n')}\n}`; +} + +function formatMeleeMobs(mobs) { + const list = mobs.map(m => ` '${m}'`).join(',\n'); + return `export const meleeMobs = [\n${list}\n]`; +} + +async function main() { + const tag = await getLatestStableTag(); + console.log(`[update-mob-data] Using bedrock-samples tag: ${tag}`); + + const [categoryToMobMap, meleeMobs] = await Promise.all([ + buildCategoryToMobMap(tag), + buildMeleeMobs(tag) + ]); + + const dataPath = 'BP/scripts/include/data.js'; + let content = fs.readFileSync(dataPath, 'utf8'); + + content = content.replace( + /export const categoryToMobMap = \{[\s\S]*?\n\}/, + formatMobMap(categoryToMobMap) + ); + content = content.replace( + /export const meleeMobs = \[[\s\S]*?\n\]/, + formatMeleeMobs(meleeMobs) + ); + + fs.writeFileSync(dataPath, content, 'utf8'); + console.log(`[update-mob-data] Updated categoryToMobMap (${Object.values(categoryToMobMap).flat().length} mobs across ${Object.keys(categoryToMobMap).length} categories) and meleeMobs (${meleeMobs.length} mobs)`); +} + +main().catch(err => { + console.error('[update-mob-data] Error:', err.message); + process.exit(1); +}); diff --git a/package-lock.json b/package-lock.json index 209d769c..ba398c14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,14 +7,15 @@ "name": "canopy", "license": "MIT", "dependencies": { - "@minecraft/debug-utilities": "^1.0.0-beta.1.26.10-preview.20", - "@minecraft/server": "^2.6.0-beta.1.26.0-stable", - "@minecraft/server-gametest": "^1.0.0-beta.1.21.130-stable", - "@minecraft/server-ui": "^2.1.0-beta.1.21.130-stable" + "@minecraft/debug-utilities": "^1.0.0-beta.1.26.20-stable", + "@minecraft/server": "^2.8.0-beta.1.26.20-stable", + "@minecraft/server-gametest": "^1.0.0-beta.1.26.20-stable", + "@minecraft/server-ui": "^2.1.0-beta.1.26.20-stable" }, "devDependencies": { "@eslint/compat": "^1.2.5", "@eslint/js": "^9.19.0", + "@forestoflight/minecraft-vitest-mocks": "^1.0.5", "@vitest/coverage-v8": "^3.0.4", "axios": "^1.7.9", "eslint": "^9.19.0", @@ -492,10 +493,11 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", - "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, + "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" }, @@ -514,6 +516,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -548,34 +551,37 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", - "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.5", + "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", - "minimatch": "^3.1.2" + "minimatch": "^3.1.5" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -583,11 +589,25 @@ "node": "*" } }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/core": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", - "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" }, @@ -596,19 +616,20 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", - "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", "dev": true, + "license": "MIT", "dependencies": { - "ajv": "^6.12.4", + "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, "engines": { @@ -619,10 +640,11 @@ } }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -633,6 +655,7 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -641,10 +664,11 @@ } }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -657,6 +681,7 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -665,36 +690,51 @@ } }, "node_modules/@eslint/js": { - "version": "9.19.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.19.0.tgz", - "integrity": "sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, "node_modules/@eslint/object-schema": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", - "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", - "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.10.0", + "@eslint/core": "^0.17.0", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@forestoflight/minecraft-vitest-mocks": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@forestoflight/minecraft-vitest-mocks/-/minecraft-vitest-mocks-1.0.5.tgz", + "integrity": "sha512-jquY6DKa6y6uuwqoWKnj4bsgXQZPVBiPpZdNwq6HEetjcvh3Ch/AlRVCFJcrRkSzDZ68YvkZBXNPCGZCDnWbGQ==", + "dev": true, + "peerDependencies": { + "vitest": "*" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -744,10 +784,11 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", - "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=18.18" }, @@ -837,9 +878,9 @@ "peer": true }, "node_modules/@minecraft/debug-utilities": { - "version": "1.0.0-beta.1.26.10-preview.20", - "resolved": "https://registry.npmjs.org/@minecraft/debug-utilities/-/debug-utilities-1.0.0-beta.1.26.10-preview.20.tgz", - "integrity": "sha512-hPdSkkxFIKNdZKb/+hht1ZRtCYTLo0bJOijyy2M3Z+GMI3sem6J6dZOE4sDpy2hyuwq5+Su43rWi7s9jeqmajQ==", + "version": "1.0.0-beta.1.26.20-stable", + "resolved": "https://registry.npmjs.org/@minecraft/debug-utilities/-/debug-utilities-1.0.0-beta.1.26.20-stable.tgz", + "integrity": "sha512-Eke9f5q5D+G405ZdOJGk+0TOwKZlX9tWvRiGd4821TrRcldh4Irtda6Iqln8s1ZLDMU6Ytm/0JkC7ZxLXgAAdQ==", "license": "MIT", "peerDependencies": { "@minecraft/common": "^1.0.0", @@ -847,9 +888,9 @@ } }, "node_modules/@minecraft/server": { - "version": "2.6.0-beta.1.26.0-stable", - "resolved": "https://registry.npmjs.org/@minecraft/server/-/server-2.6.0-beta.1.26.0-stable.tgz", - "integrity": "sha512-I8GFHqE4LNnmI5FnCpCGjbURHbdzYJdZtJ9vqfjlJ8TkWKlrrJVQpIr6IDghG0jon1Zv0zMJUWprpo6wsedkVg==", + "version": "2.8.0-beta.1.26.20-stable", + "resolved": "https://registry.npmjs.org/@minecraft/server/-/server-2.8.0-beta.1.26.20-stable.tgz", + "integrity": "sha512-oxrzclVYyf3WDotWBQ5YauQ47CksnOhDdlm/jX1LPvWvNo6atMYCls6kWLE5CsQrMBwEfwb5pazFxXeUh/4ZMw==", "license": "MIT", "peerDependencies": { "@minecraft/common": "^1.2.0", @@ -857,21 +898,23 @@ } }, "node_modules/@minecraft/server-gametest": { - "version": "1.0.0-beta.1.21.130-stable", - "resolved": "https://registry.npmjs.org/@minecraft/server-gametest/-/server-gametest-1.0.0-beta.1.21.130-stable.tgz", - "integrity": "sha512-17kwE/Oesv/EGKGjd/QkrZX5gFjfK72p3kouAre8INO0VF1vZbur0N3A21XofQj7hOLtEGxfRbqYZYm0mvjuDQ==", + "version": "1.0.0-beta.1.26.20-stable", + "resolved": "https://registry.npmjs.org/@minecraft/server-gametest/-/server-gametest-1.0.0-beta.1.26.20-stable.tgz", + "integrity": "sha512-AIw2pK3YKdwkmpaaUbzzgzxllvRmHsjqNhMnNbAC9j5vGDdLW7y3X8sPavw57LGypj9wXffcBGWcMyYQdY2x1A==", + "license": "MIT", "peerDependencies": { "@minecraft/common": "^1.0.0", - "@minecraft/server": "^1.17.0 || ^2.0.0 || ^2.5.0-beta.1.21.130-stable" + "@minecraft/server": "^1.17.0 || ^2.0.0 || ^2.8.0-beta.1.26.20-stable" } }, "node_modules/@minecraft/server-ui": { - "version": "2.1.0-beta.1.21.130-stable", - "resolved": "https://registry.npmjs.org/@minecraft/server-ui/-/server-ui-2.1.0-beta.1.21.130-stable.tgz", - "integrity": "sha512-XMT5DLhHthjkj82uHHRq4XRJy6IWs1XwhLATLZ2ynNtPBgQp6srB3xudfOrYHUP1bO+uqvmAQAIApUqO05uEDg==", + "version": "2.1.0-beta.1.26.20-stable", + "resolved": "https://registry.npmjs.org/@minecraft/server-ui/-/server-ui-2.1.0-beta.1.26.20-stable.tgz", + "integrity": "sha512-7m7glKSEl39dDEtYi1sxaDqh9iMECs1NHn1g6RTJp1qbo1uNtV1a8xJdlJRajqDhQgvWMMV8Hv4f4d334pa5Lw==", + "license": "MIT", "peerDependencies": { "@minecraft/common": "^1.0.0", - "@minecraft/server": "^2.0.0 || ^2.5.0-beta.1.21.130-stable" + "@minecraft/server": "^2.0.0 || ^2.8.0-beta.1.26.20-stable" } }, "node_modules/@minecraft/vanilla-data": { @@ -892,276 +935,368 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.1.tgz", - "integrity": "sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", + "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.1.tgz", - "integrity": "sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", + "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.1.tgz", - "integrity": "sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", + "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.1.tgz", - "integrity": "sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", + "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.1.tgz", - "integrity": "sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", + "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.1.tgz", - "integrity": "sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", + "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.1.tgz", - "integrity": "sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", + "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.1.tgz", - "integrity": "sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", + "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.1.tgz", - "integrity": "sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", + "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.1.tgz", - "integrity": "sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", + "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.1.tgz", - "integrity": "sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==", + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", + "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", "cpu": [ "loong64" ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", + "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.1.tgz", - "integrity": "sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", + "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", + "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.1.tgz", - "integrity": "sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", + "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.1.tgz", - "integrity": "sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", + "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.1.tgz", - "integrity": "sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", + "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.1.tgz", - "integrity": "sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", + "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.1.tgz", - "integrity": "sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", + "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", + "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", + "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.1.tgz", - "integrity": "sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", + "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.1.tgz", - "integrity": "sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", + "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", + "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.1.tgz", - "integrity": "sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", + "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", - "dev": true + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@vitest/coverage-v8": { "version": "3.1.4", @@ -1302,10 +1437,11 @@ } }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -1318,15 +1454,17 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1366,7 +1504,8 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "dev": true, + "license": "Python-2.0" }, "node_modules/assertion-error": { "version": "2.0.1", @@ -1381,30 +1520,34 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/axios": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", - "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.2.tgz", + "integrity": "sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==", "dev": true, + "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" } }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -1418,11 +1561,26 @@ "node": ">=8" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -1506,6 +1664,7 @@ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, + "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -1517,7 +1676,8 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/cross-spawn": { "version": "7.0.6", @@ -1570,10 +1730,26 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.4.0" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -1586,12 +1762,61 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", "dev": true }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", @@ -1645,31 +1870,32 @@ } }, "node_modules/eslint": { - "version": "9.19.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.19.0.tgz", - "integrity": "sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, + "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.0", - "@eslint/core": "^0.10.0", - "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.19.0", - "@eslint/plugin-kit": "^0.2.5", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.1", + "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "ajv": "^6.12.4", + "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.2.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -1681,7 +1907,7 @@ "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -1704,10 +1930,11 @@ } }, "node_modules/eslint-scope": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", - "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -1720,10 +1947,11 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -1732,20 +1960,22 @@ } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -1754,14 +1984,15 @@ } }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1787,6 +2018,7 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -1834,13 +2066,15 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", @@ -1904,15 +2138,16 @@ } }, "node_modules/flatted": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", - "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", - "dev": true + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "dev": true, "funding": [ { @@ -1920,6 +2155,7 @@ "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -1946,13 +2182,16 @@ } }, "node_modules/form-data": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", - "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "dev": true, + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -1973,11 +2212,62 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, + "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -2017,6 +2307,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2026,6 +2329,48 @@ "node": ">=8" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -2037,15 +2382,17 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -2168,10 +2515,11 @@ } }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -2189,7 +2537,8 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -2287,11 +2636,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -2301,6 +2661,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -2309,12 +2670,13 @@ } }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, + "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -2420,6 +2782,7 @@ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -2483,10 +2846,11 @@ "dev": true }, "node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -2495,9 +2859,9 @@ } }, "node_modules/postcss": { - "version": "8.5.4", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz", - "integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==", + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", + "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", "dev": true, "funding": [ { @@ -2513,6 +2877,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -2532,16 +2897,21 @@ } }, "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -2551,17 +2921,19 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/rollup": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.1.tgz", - "integrity": "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", + "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", "dev": true, + "license": "MIT", "dependencies": { - "@types/estree": "1.0.7" + "@types/estree": "1.0.8" }, "bin": { "rollup": "dist/bin/rollup" @@ -2571,26 +2943,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.41.1", - "@rollup/rollup-android-arm64": "4.41.1", - "@rollup/rollup-darwin-arm64": "4.41.1", - "@rollup/rollup-darwin-x64": "4.41.1", - "@rollup/rollup-freebsd-arm64": "4.41.1", - "@rollup/rollup-freebsd-x64": "4.41.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.41.1", - "@rollup/rollup-linux-arm-musleabihf": "4.41.1", - "@rollup/rollup-linux-arm64-gnu": "4.41.1", - "@rollup/rollup-linux-arm64-musl": "4.41.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.41.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.41.1", - "@rollup/rollup-linux-riscv64-gnu": "4.41.1", - "@rollup/rollup-linux-riscv64-musl": "4.41.1", - "@rollup/rollup-linux-s390x-gnu": "4.41.1", - "@rollup/rollup-linux-x64-gnu": "4.41.1", - "@rollup/rollup-linux-x64-musl": "4.41.1", - "@rollup/rollup-win32-arm64-msvc": "4.41.1", - "@rollup/rollup-win32-ia32-msvc": "4.41.1", - "@rollup/rollup-win32-x64-msvc": "4.41.1", + "@rollup/rollup-android-arm-eabi": "4.60.2", + "@rollup/rollup-android-arm64": "4.60.2", + "@rollup/rollup-darwin-arm64": "4.60.2", + "@rollup/rollup-darwin-x64": "4.60.2", + "@rollup/rollup-freebsd-arm64": "4.60.2", + "@rollup/rollup-freebsd-x64": "4.60.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", + "@rollup/rollup-linux-arm-musleabihf": "4.60.2", + "@rollup/rollup-linux-arm64-gnu": "4.60.2", + "@rollup/rollup-linux-arm64-musl": "4.60.2", + "@rollup/rollup-linux-loong64-gnu": "4.60.2", + "@rollup/rollup-linux-loong64-musl": "4.60.2", + "@rollup/rollup-linux-ppc64-gnu": "4.60.2", + "@rollup/rollup-linux-ppc64-musl": "4.60.2", + "@rollup/rollup-linux-riscv64-gnu": "4.60.2", + "@rollup/rollup-linux-riscv64-musl": "4.60.2", + "@rollup/rollup-linux-s390x-gnu": "4.60.2", + "@rollup/rollup-linux-x64-gnu": "4.60.2", + "@rollup/rollup-linux-x64-musl": "4.60.2", + "@rollup/rollup-openbsd-x64": "4.60.2", + "@rollup/rollup-openharmony-arm64": "4.60.2", + "@rollup/rollup-win32-arm64-msvc": "4.60.2", + "@rollup/rollup-win32-ia32-msvc": "4.60.2", + "@rollup/rollup-win32-x64-gnu": "4.60.2", + "@rollup/rollup-win32-x64-msvc": "4.60.2", "fsevents": "~2.3.2" } }, @@ -2872,15 +3249,17 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } }, "node_modules/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", "dev": true, + "license": "MIT", "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", diff --git a/package.json b/package.json index e0a4f7ac..6d04a37a 100644 --- a/package.json +++ b/package.json @@ -10,18 +10,20 @@ }, "license": "MIT", "dependencies": { - "@minecraft/debug-utilities": "^1.0.0-beta.1.26.10-preview.20", - "@minecraft/server": "^2.6.0-beta.1.26.0-stable", - "@minecraft/server-gametest": "^1.0.0-beta.1.21.130-stable", - "@minecraft/server-ui": "^2.1.0-beta.1.21.130-stable" + "@minecraft/debug-utilities": "^1.0.0-beta.1.26.20-stable", + "@minecraft/server": "^2.8.0-beta.1.26.20-stable", + "@minecraft/server-gametest": "^1.0.0-beta.1.26.20-stable", + "@minecraft/server-ui": "^2.1.0-beta.1.26.20-stable" }, "scripts": { - "test": "vitest run --coverage", - "lint": "eslint . --fix" + "test": "vitest run --config vitest.config.js --coverage", + "lint": "eslint . --fix", + "generate-wiki": "sh -c 'WIKI_PATH=${WIKI_PATH:-$1} npx vitest run --config vitest.wiki.config.js' --" }, "devDependencies": { "@eslint/compat": "^1.2.5", "@eslint/js": "^9.19.0", + "@forestoflight/minecraft-vitest-mocks": "^1.0.5", "@vitest/coverage-v8": "^3.0.4", "axios": "^1.7.9", "eslint": "^9.19.0", diff --git a/vite.config.js b/vite.config.js index 829429fb..99f91d1f 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,22 +1,17 @@ import { defineConfig } from 'vite'; export default defineConfig({ - server: { - deps: { - external: ['@minecraft/server', '@minecraft/server-ui'], - } - }, resolve: { alias: { - '@minecraft/server': `__mocks__/@minecraft/server`, - '@minecraft/server-ui': `__mocks__/@minecraft/server-ui`, + '@minecraft/server': `${__dirname}/__mocks__/@minecraft/server`, + '@minecraft/server-ui': `${__dirname}/__mocks__/@minecraft/server-ui`, } }, test: { - testFiles: '**/__tests__/**/*.test.js', - files: '**/__tests__/**', + setupFiles: ['./vitest.setup.js'], env: { NODE_ENV: 'test' - } + }, + include: ['__tests__/**/*.test.js'] } }); \ No newline at end of file diff --git a/vitest.config.js b/vitest.config.js new file mode 100644 index 00000000..47eeb81f --- /dev/null +++ b/vitest.config.js @@ -0,0 +1,28 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + server: { + fs: { + strict: false + } + }, + resolve: { + alias: { + '@minecraft/server': `@forestoflight/minecraft-vitest-mocks/server`, + '@minecraft/server-ui': `@forestoflight/minecraft-vitest-mocks/server-ui`, + '@minecraft/debug-utilities': `@forestoflight/minecraft-vitest-mocks/debug-utilities` + } + }, + test: { + setupFiles: ['@forestoflight/minecraft-vitest-mocks/setup'], + server: { + deps: { + inline: ['@forestoflight/minecraft-vitest-mocks'] + } + }, + env: { + NODE_ENV: 'test' + }, + include: ['__tests__/**/*.test.js'] + } +}); diff --git a/vitest.wiki.config.js b/vitest.wiki.config.js new file mode 100644 index 00000000..9c5388c8 --- /dev/null +++ b/vitest.wiki.config.js @@ -0,0 +1,32 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + server: { + fs: { + strict: false + } + }, + resolve: { + alias: { + '@minecraft/server': `@forestoflight/minecraft-vitest-mocks/server`, + '@minecraft/server-ui': `@forestoflight/minecraft-vitest-mocks/server-ui`, + '@minecraft/debug-utilities': `@forestoflight/minecraft-vitest-mocks/debug-utilities`, + 'lib/canopy/Canopy': `${__dirname}/Canopy[BP]/scripts/lib/canopy/Canopy.js`, + 'src/commands/trackevent': `${__dirname}/Canopy[BP]/scripts/src/commands/trackevent.js`, + 'src/classes/Instaminable': `${__dirname}/Canopy[BP]/scripts/src/classes/Instaminable.js`, + 'src/rules/durabilityNotifier': `${__dirname}/Canopy[BP]/scripts/src/rules/durabilityNotifier.js`, + }, + }, + test: { + env: { + NODE_ENV: 'test' + }, + include: ['docs/scripts/generate-wiki.test.js'], + setupFiles: ['@forestoflight/minecraft-vitest-mocks/setup'], + server: { + deps: { + inline: ['@forestoflight/minecraft-vitest-mocks'] + } + }, + } +});