From f2bf3ae1c12379c85759ecdb29b67be13e149ba1 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Tue, 10 Mar 2026 11:37:17 -0700 Subject: [PATCH 01/64] bump to 26.20 --- Canopy [BP]/manifest.json | 2 +- package-lock.json | 18 +++++++++--------- package.json | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Canopy [BP]/manifest.json b/Canopy [BP]/manifest.json index fa5d94f..f07b80c 100644 --- a/Canopy [BP]/manifest.json +++ b/Canopy [BP]/manifest.json @@ -26,7 +26,7 @@ "dependencies": [ { "module_name": "@minecraft/server", - "version": "2.7.0-beta" + "version": "2.8.0-beta" }, { "module_name": "@minecraft/server-ui", diff --git a/package-lock.json b/package-lock.json index 209d769..82ea11c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,8 +7,8 @@ "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/debug-utilities": "^1.0.0-beta.1.26.20-preview.20", + "@minecraft/server": "^2.8.0-beta.1.26.20-preview.20", "@minecraft/server-gametest": "^1.0.0-beta.1.21.130-stable", "@minecraft/server-ui": "^2.1.0-beta.1.21.130-stable" }, @@ -837,9 +837,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-preview.20", + "resolved": "https://registry.npmjs.org/@minecraft/debug-utilities/-/debug-utilities-1.0.0-beta.1.26.20-preview.20.tgz", + "integrity": "sha512-neXYjfMaFTh9H52Ey5QsrjqAIOPUB5baPgX6TT+BP86vTnHP+zpxK3SL2u8Ppknysi56gWaSSykqunmedFuZag==", "license": "MIT", "peerDependencies": { "@minecraft/common": "^1.0.0", @@ -847,13 +847,13 @@ } }, "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-preview.20", + "resolved": "https://registry.npmjs.org/@minecraft/server/-/server-2.8.0-beta.1.26.20-preview.20.tgz", + "integrity": "sha512-3PknB6QJcV79O+apC+6RVJTawM7syQPL9Ie5DwUylJJ/ODXswYHHBWHs0JaoYbvdN6DY+a/WyxOomJUKmVS6Bg==", "license": "MIT", "peerDependencies": { "@minecraft/common": "^1.2.0", - "@minecraft/vanilla-data": ">=1.20.70" + "@minecraft/vanilla-data": ">=1.20.70 || 1.26.20-preview.20" } }, "node_modules/@minecraft/server-gametest": { diff --git a/package.json b/package.json index e0a4f7a..3fd10ff 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,8 @@ }, "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/debug-utilities": "^1.0.0-beta.1.26.20-preview.20", + "@minecraft/server": "^2.8.0-beta.1.26.20-preview.20", "@minecraft/server-gametest": "^1.0.0-beta.1.21.130-stable", "@minecraft/server-ui": "^2.1.0-beta.1.21.130-stable" }, From 9bafadfc73559fa9be28e66f82e3fa3dcb6a3091 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Tue, 10 Mar 2026 11:37:28 -0700 Subject: [PATCH 02/64] add alpha channel to all debugShape colors --- Canopy [BP]/scripts/src/classes/BiomeEdgeRenderer.js | 9 +++++---- Canopy [BP]/scripts/src/classes/ChunkBorderRender.js | 8 ++++---- Canopy [BP]/scripts/src/classes/HSSFinder.js | 2 +- Canopy [BP]/scripts/src/classes/HSSRenderer.js | 4 ++-- .../scripts/src/classes/debugdisplay/AttackBox.js | 2 +- .../scripts/src/classes/debugdisplay/CollisionBox.js | 2 +- Canopy [BP]/scripts/src/classes/debugdisplay/EyeLevel.js | 2 +- .../src/classes/debugdisplay/ViewDirectionVector.js | 2 +- 8 files changed, 16 insertions(+), 15 deletions(-) diff --git a/Canopy [BP]/scripts/src/classes/BiomeEdgeRenderer.js b/Canopy [BP]/scripts/src/classes/BiomeEdgeRenderer.js index f96ec80..8e42707 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; @@ -142,7 +142,7 @@ export class BiomeEdgeRenderer { 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); 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); diff --git a/Canopy [BP]/scripts/src/classes/ChunkBorderRender.js b/Canopy [BP]/scripts/src/classes/ChunkBorderRender.js index 56eb8fe..66e2f4d 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; diff --git a/Canopy [BP]/scripts/src/classes/HSSFinder.js b/Canopy [BP]/scripts/src/classes/HSSFinder.js index bccd882..56a5e7b 100644 --- a/Canopy [BP]/scripts/src/classes/HSSFinder.js +++ b/Canopy [BP]/scripts/src/classes/HSSFinder.js @@ -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 index 2c3c9a9..d9d6fc8 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/debugdisplay/AttackBox.js b/Canopy [BP]/scripts/src/classes/debugdisplay/AttackBox.js index 4e18a64..1f1afa2 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); } diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/CollisionBox.js b/Canopy [BP]/scripts/src/classes/debugdisplay/CollisionBox.js index cb945d4..9c32a92 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); } diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/EyeLevel.js b/Canopy [BP]/scripts/src/classes/debugdisplay/EyeLevel.js index 100fc0a..16849b7 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/ViewDirectionVector.js b/Canopy [BP]/scripts/src/classes/debugdisplay/ViewDirectionVector.js index 31ea668..d677d85 100644 --- a/Canopy [BP]/scripts/src/classes/debugdisplay/ViewDirectionVector.js +++ b/Canopy [BP]/scripts/src/classes/debugdisplay/ViewDirectionVector.js @@ -10,7 +10,7 @@ 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); } From 8acd3dfede88948db1abce93115154bae714ca38 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Tue, 10 Mar 2026 12:46:29 -0700 Subject: [PATCH 03/64] add renderSignalStrength InfoDisplay rule This needs some heavy refactoring in the InfoDisplay classes and there could probably be some inheritance/simplification in rendering --- .../src/classes/SignalStrengthRenderer.js | 62 ++++++++++++ .../src/rules/infodisplay/InfoDisplay.js | 6 +- .../rules/infodisplay/RenderSignalStrength.js | 94 +++++++++++++++++++ 3 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 Canopy [BP]/scripts/src/classes/SignalStrengthRenderer.js create mode 100644 Canopy [BP]/scripts/src/rules/infodisplay/RenderSignalStrength.js diff --git a/Canopy [BP]/scripts/src/classes/SignalStrengthRenderer.js b/Canopy [BP]/scripts/src/classes/SignalStrengthRenderer.js new file mode 100644 index 0000000..d050426 --- /dev/null +++ b/Canopy [BP]/scripts/src/classes/SignalStrengthRenderer.js @@ -0,0 +1,62 @@ +import { debugDrawer, DebugText } from "@minecraft/debug-utilities"; +import { system } from "@minecraft/server"; +import { Vector } from "../../lib/Vector"; + +export class SignalStrengthRenderer { + block; + visibleToPlayer; + textShape; + runner = void 0; + + constructor(block, visibleToPlayer) { + this.block = block; + 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.isAir) { + this.stopRender(); + return; + } + this.textShape.setText(String(this.block.getRedstonePower())); + } + + createTextShape() { + const dimensionlocation = Vector.from(this.block.center()).add(new Vector(0, -7.5/16, 0.1)); + dimensionlocation.dimension = this.block.dimension; + this.textShape = new DebugText(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.drawShape(this.textShape); + } + + drawShape(debugShape) { + if (this.visibleToPlayer) + debugShape.visibleTo = [this.visibleToPlayer]; + this.textShape = debugShape; + debugDrawer.addShape(debugShape); + } +} \ No newline at end of file diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplay.js b/Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplay.js index 7f0779e..5ce0574 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplay.js +++ b/Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplay.js @@ -28,6 +28,8 @@ import { Weather } from './Weather'; import { LiquidTarget } from './LiquidTarget'; import { LiquidStates } from './LiquidStates'; +import { RenderSignalStrength } from './RenderSignalStrength'; + const playerToInfoDisplayMap = {}; let currentTickWorldwideElementData = {}; @@ -66,7 +68,9 @@ class InfoDisplay { new BlockStates(player, 21), new PeekInventory(player, 22), new LiquidTarget(player, 23), - new LiquidStates(player, 24) + new LiquidStates(player, 24), + + new RenderSignalStrength(player) ]; playerToInfoDisplayMap[player.id] = this; } 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 0000000..47b64af --- /dev/null +++ b/Canopy [BP]/scripts/src/rules/infodisplay/RenderSignalStrength.js @@ -0,0 +1,94 @@ +import { BlockVolume, system } from '@minecraft/server'; +import { InfoDisplayElement } from './InfoDisplayElement'; +import { SignalStrengthRenderer } from '../../classes/SignalStrengthRenderer'; +import { Vector } from '../../../lib/Vector'; + +class RenderSignalStrength extends InfoDisplayElement{ + player; + playerId; + RENDER_DISTANCE = 8; + signalStrengthRenderers = new Map(); + runner = void 0; + + constructor(player) { + const ruleData = { + identifier: 'renderSignalStrength', + description: { translate: 'rules.infoDisplay.renderSignalStrength' }, + onEnableCallback: () => this.startRendering(), + onDisableCallback: () => this.stopRendering() + }; + super(ruleData, 0); + this.player = player; + this.playerId = player.id; + } + + startRendering() { + if (this.runner !== void 0) + this.stopRendering(); + this.signalStrengthRenderers = new Map(); + this.runner = system.runInterval(this.onTick.bind(this)); + } + + stopRendering() { + if (this.runner !== void 0) { + system.clearRun(this.runner); + this.runner = void 0; + } + if (this.signalStrengthRenderers.size > 0) { + for (const renderer of this.signalStrengthRenderers.values()) + renderer.destroy(); + this.signalStrengthRenderers.clear(); + } + } + + refresh() { + this.stopRendering(); + this.startRendering(); + } + + onTick() { + if (this.player) + this.renderForNearbyBlocks(); + else + this.stopRendering(); + } + + renderForNearbyBlocks() { + const blockLocationIterator = this.getNearbyBlockLocationIterator(); + let locationResult = blockLocationIterator.next(); + const blocks = []; + while (!locationResult.done) { + const block = this.player.dimension.getBlock(locationResult.value); + blocks.push(block); + const key = Vector.from(block.location).toString(); + if (!this.signalStrengthRenderers.has(key)) + this.signalStrengthRenderers.set(key, new SignalStrengthRenderer(block, this.player)); + locationResult = blockLocationIterator.next(); + } + for (const [key, renderer] of this.signalStrengthRenderers) { + if (!blocks.some(e => e.id === renderer.block.id)) { + renderer.destroy(); + this.signalStrengthRenderers.delete(key); + } + } + } + + getNearbyBlockLocationIterator() { + const min = Vector.from(this.player.location).subtract(new Vector(this.RENDER_DISTANCE, this.RENDER_DISTANCE, this.RENDER_DISTANCE)); + const max = Vector.from(this.player.location).add(new Vector(this.RENDER_DISTANCE, 2, this.RENDER_DISTANCE)); + const volume = new BlockVolume(min, max); + const blockVolume = this.player.dimension.getBlocks(volume, { includeTypes: ["minecraft:redstone_wire"] }, true); + return blockVolume.getBlockLocationIterator(); + } + + getFormattedDataOwnLine() { + this.onTick(); + return { text: '' }; + } + + getFormattedDataSharedLine() { + return this.getFormattedDataOwnLine(); + } +} + +export { RenderSignalStrength }; From 1ba0a9e05f58f35654451f2fd0ef50c831556872 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Wed, 11 Mar 2026 13:21:36 -0700 Subject: [PATCH 04/64] refactor InfoDisplayElements to support Shapes --- .vscode/launch.json | 3 +- .../src/classes/SignalStrengthRenderer.js | 24 +++--- .../scripts/src/rules/infodisplay/Biome.js | 4 +- .../src/rules/infodisplay/BlockStates.js | 4 +- .../src/rules/infodisplay/CardinalFacing.js | 4 +- .../src/rules/infodisplay/ChunkCoords.js | 4 +- .../scripts/src/rules/infodisplay/Coords.js | 4 +- .../src/rules/infodisplay/Dimension.js | 4 +- .../scripts/src/rules/infodisplay/Entities.js | 4 +- .../src/rules/infodisplay/EventTrackers.js | 4 +- .../scripts/src/rules/infodisplay/Facing.js | 4 +- .../rules/infodisplay/HopperCounterCounts.js | 4 +- .../src/rules/infodisplay/InfoDisplay.js | 51 +++++++---- .../rules/infodisplay/InfoDisplayElement.js | 12 +-- .../infodisplay/InfoDisplayShapeElement.js | 47 ++++++++++ .../infodisplay/InfoDisplayTextElement.js | 23 +++++ .../scripts/src/rules/infodisplay/Light.js | 4 +- .../src/rules/infodisplay/LiquidStates.js | 4 +- .../src/rules/infodisplay/LiquidTarget.js | 4 +- .../src/rules/infodisplay/MoonPhase.js | 4 +- .../src/rules/infodisplay/PeekInventory.js | 4 +- .../rules/infodisplay/RenderSignalStrength.js | 86 ++++++++----------- .../src/rules/infodisplay/SessionTime.js | 4 +- .../src/rules/infodisplay/SignalStrength.js | 4 +- .../src/rules/infodisplay/SimulationMap.js | 4 +- .../src/rules/infodisplay/SlimeChunk.js | 4 +- .../scripts/src/rules/infodisplay/Speed.js | 4 +- .../src/rules/infodisplay/Structures.js | 4 +- .../scripts/src/rules/infodisplay/TPS.js | 4 +- .../scripts/src/rules/infodisplay/Target.js | 4 +- .../src/rules/infodisplay/TimeOfDay.js | 4 +- .../scripts/src/rules/infodisplay/Velocity.js | 4 +- .../scripts/src/rules/infodisplay/Weather.js | 4 +- .../scripts/src/rules/infodisplay/WorldDay.js | 4 +- Canopy [RP]/texts/en_US.lang | 1 + .../src/rules/infodisplay/BlockStates.test.js | 6 +- .../src/rules/infodisplay/ChunkCoords.test.js | 6 +- .../src/rules/infodisplay/Dimension.test.js | 6 +- .../rules/infodisplay/PeekInventory.test.js | 6 +- .../src/rules/infodisplay/Speed.test.js | 6 +- .../src/rules/infodisplay/Structures.test.js | 6 +- .../src/rules/infodisplay/Weather.test.js | 6 +- .../BP/scripts/src/rules/tntFuse.test.js | 10 +++ 43 files changed, 240 insertions(+), 167 deletions(-) create mode 100644 Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplayShapeElement.js create mode 100644 Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplayTextElement.js diff --git a/.vscode/launch.json b/.vscode/launch.json index 8293d8e..a7f82d2 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,8 +8,7 @@ "mode": "listen", "targetModuleUuid": "3d753132-e3c9-4305-a995-eae30b486093", "localRoot": "${workspaceFolder}/Canopy [BP]/scripts", - "port": 19144, - "preLaunchTask": "regolith: watch" + "port": 19144 } ] } \ No newline at end of file diff --git a/Canopy [BP]/scripts/src/classes/SignalStrengthRenderer.js b/Canopy [BP]/scripts/src/classes/SignalStrengthRenderer.js index d050426..e29ab7b 100644 --- a/Canopy [BP]/scripts/src/classes/SignalStrengthRenderer.js +++ b/Canopy [BP]/scripts/src/classes/SignalStrengthRenderer.js @@ -1,15 +1,16 @@ -import { debugDrawer, DebugText } from "@minecraft/debug-utilities"; -import { system } from "@minecraft/server"; +import { system, TextPrimitive, world } from "@minecraft/server"; import { Vector } from "../../lib/Vector"; export class SignalStrengthRenderer { block; + dimension; visibleToPlayer; textShape; runner = void 0; - constructor(block, visibleToPlayer) { + constructor(block, dimension, visibleToPlayer) { this.block = block; + this.dimension = dimension; this.visibleToPlayer = visibleToPlayer; this.startRender(); } @@ -35,7 +36,7 @@ export class SignalStrengthRenderer { } onTick() { - if (!this.block?.isValid || this.block.isAir) { + if (!this.block?.isValid || this.block.typeId !== "minecraft:redstone_wire") { this.stopRender(); return; } @@ -43,20 +44,19 @@ export class SignalStrengthRenderer { } createTextShape() { - const dimensionlocation = Vector.from(this.block.center()).add(new Vector(0, -7.5/16, 0.1)); - dimensionlocation.dimension = this.block.dimension; - this.textShape = new DebugText(dimensionlocation, String(this.block.getRedstonePower())); + 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.drawShape(this.textShape); + this.drawShape(); } - drawShape(debugShape) { + drawShape() { if (this.visibleToPlayer) - debugShape.visibleTo = [this.visibleToPlayer]; - this.textShape = debugShape; - debugDrawer.addShape(debugShape); + this.textShape.visibleTo = [this.visibleToPlayer]; + world.primitiveShapesManager.addText(this.textShape); } } \ No newline at end of file diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Biome.js b/Canopy [BP]/scripts/src/rules/infodisplay/Biome.js index 62a2fb1..f655613 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/Biome.js +++ b/Canopy [BP]/scripts/src/rules/infodisplay/Biome.js @@ -1,6 +1,6 @@ -import { InfoDisplayElement } from './InfoDisplayElement.js'; +import { InfoDisplayTextElement } from './InfoDisplayTextElement.js'; -class Biome extends InfoDisplayElement { +class Biome extends InfoDisplayTextElement { player; constructor(player, displayLine) { diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/BlockStates.js b/Canopy [BP]/scripts/src/rules/infodisplay/BlockStates.js index 33ec7b6..41e7455 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/BlockStates.js +++ b/Canopy [BP]/scripts/src/rules/infodisplay/BlockStates.js @@ -1,8 +1,8 @@ -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) { diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/CardinalFacing.js b/Canopy [BP]/scripts/src/rules/infodisplay/CardinalFacing.js index 3332e06..886dcc2 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/CardinalFacing.js +++ b/Canopy [BP]/scripts/src/rules/infodisplay/CardinalFacing.js @@ -1,6 +1,6 @@ -import { InfoDisplayElement } from './InfoDisplayElement.js'; +import { InfoDisplayTextElement } from './InfoDisplayTextElement.js'; -class CardinalFacing extends InfoDisplayElement { +class CardinalFacing extends InfoDisplayTextElement { player; constructor(player, displayLine) { diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/ChunkCoords.js b/Canopy [BP]/scripts/src/rules/infodisplay/ChunkCoords.js index fd56c96..b062409 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/ChunkCoords.js +++ b/Canopy [BP]/scripts/src/rules/infodisplay/ChunkCoords.js @@ -1,6 +1,6 @@ -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' } }; super(ruleData, displayLine); diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Coords.js b/Canopy [BP]/scripts/src/rules/infodisplay/Coords.js index 0265263..73c6313 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/Coords.js +++ b/Canopy [BP]/scripts/src/rules/infodisplay/Coords.js @@ -1,6 +1,6 @@ -import { InfoDisplayElement } from './InfoDisplayElement.js'; +import { InfoDisplayTextElement } from './InfoDisplayTextElement.js'; -class Coords extends InfoDisplayElement { +class Coords extends InfoDisplayTextElement { player; constructor(player, displayLine) { diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Dimension.js b/Canopy [BP]/scripts/src/rules/infodisplay/Dimension.js index bc3a883..df71420 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/Dimension.js +++ b/Canopy [BP]/scripts/src/rules/infodisplay/Dimension.js @@ -1,7 +1,7 @@ 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' } }; super(ruleData, displayLine); diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Entities.js b/Canopy [BP]/scripts/src/rules/infodisplay/Entities.js index f9e2929..11aa493 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/Entities.js +++ b/Canopy [BP]/scripts/src/rules/infodisplay/Entities.js @@ -1,7 +1,7 @@ -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) { diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/EventTrackers.js b/Canopy [BP]/scripts/src/rules/infodisplay/EventTrackers.js index 6c930f7..ee638f7 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/EventTrackers.js +++ b/Canopy [BP]/scripts/src/rules/infodisplay/EventTrackers.js @@ -1,7 +1,7 @@ -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' } }; super(ruleData, displayLine, true); diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Facing.js b/Canopy [BP]/scripts/src/rules/infodisplay/Facing.js index 2057540..8811e81 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/Facing.js +++ b/Canopy [BP]/scripts/src/rules/infodisplay/Facing.js @@ -1,6 +1,6 @@ -import { InfoDisplayElement } from './InfoDisplayElement.js'; +import { InfoDisplayTextElement } from './InfoDisplayTextElement.js'; -class Facing extends InfoDisplayElement { +class Facing extends InfoDisplayTextElement { player; constructor(player, displayLine) { diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/HopperCounterCounts.js b/Canopy [BP]/scripts/src/rules/infodisplay/HopperCounterCounts.js index 0d28d20..d996a95 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/HopperCounterCounts.js +++ b/Canopy [BP]/scripts/src/rules/infodisplay/HopperCounterCounts.js @@ -1,8 +1,8 @@ -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' } }; super(ruleData, displayLine, true); diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplay.js b/Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplay.js index 5ce0574..92286cd 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'; @@ -30,14 +32,13 @@ import { LiquidStates } from './LiquidStates'; import { RenderSignalStrength } from './RenderSignalStrength'; -const playerToInfoDisplayMap = {}; -let currentTickWorldwideElementData = {}; - class InfoDisplay { player; elements = []; infoMessage = { rawtext: [] }; clearedPreviousMessage = false; + static playerToInfoDisplayMap = {}; + static currentTickWorldwideElementData = {}; constructor(player) { this.player = player; @@ -72,26 +73,33 @@ class InfoDisplay { new RenderSignalStrength(player) ]; - playerToInfoDisplayMap[player.id] = this; + InfoDisplay.playerToInfoDisplayMap[player.id] = this; } 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)) @@ -101,6 +109,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)); } @@ -158,16 +177,18 @@ class InfoDisplay { } 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]; }); diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplayElement.js b/Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplayElement.js index a2edb82..e08aa8d 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 0000000..82aba89 --- /dev/null +++ b/Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplayShapeElement.js @@ -0,0 +1,47 @@ +import { InfoDisplayElement } from './InfoDisplayElement'; + +class InfoDisplayShapeElement extends InfoDisplayElement { + identifier; + rule; + isWorldwide; + + 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."); + } + + setupCallbacks(ruleData) { + return ruleData; + } + + 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 0000000..051d16f --- /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 index 3628d37..fad736f 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/Light.js +++ b/Canopy [BP]/scripts/src/rules/infodisplay/Light.js @@ -1,6 +1,6 @@ -import { InfoDisplayElement } from './InfoDisplayElement.js'; +import { InfoDisplayTextElement } from './InfoDisplayTextElement.js'; -class Light extends InfoDisplayElement { +class Light extends InfoDisplayTextElement { player; constructor(player, displayLine) { diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/LiquidStates.js b/Canopy [BP]/scripts/src/rules/infodisplay/LiquidStates.js index 9f7f5a1..d180baf 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/LiquidStates.js +++ b/Canopy [BP]/scripts/src/rules/infodisplay/LiquidStates.js @@ -1,7 +1,7 @@ -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) { diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/LiquidTarget.js b/Canopy [BP]/scripts/src/rules/infodisplay/LiquidTarget.js index 949f20f..923b681 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/LiquidTarget.js +++ b/Canopy [BP]/scripts/src/rules/infodisplay/LiquidTarget.js @@ -1,7 +1,7 @@ -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' } }; super(ruleData, displayLine); diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/MoonPhase.js b/Canopy [BP]/scripts/src/rules/infodisplay/MoonPhase.js index f55aeb4..f1a76e5 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/MoonPhase.js +++ b/Canopy [BP]/scripts/src/rules/infodisplay/MoonPhase.js @@ -1,7 +1,7 @@ -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' } }; super(ruleData, displayLine, true); diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/PeekInventory.js b/Canopy [BP]/scripts/src/rules/infodisplay/PeekInventory.js index 3cbd451..5f1a56f 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/PeekInventory.js +++ b/Canopy [BP]/scripts/src/rules/infodisplay/PeekInventory.js @@ -1,9 +1,9 @@ -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) { diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/RenderSignalStrength.js b/Canopy [BP]/scripts/src/rules/infodisplay/RenderSignalStrength.js index 47b64af..f0175e4 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/RenderSignalStrength.js +++ b/Canopy [BP]/scripts/src/rules/infodisplay/RenderSignalStrength.js @@ -1,93 +1,75 @@ -import { BlockVolume, system } from '@minecraft/server'; -import { InfoDisplayElement } from './InfoDisplayElement'; +import { InfoDisplayShapeElement } from './InfoDisplayShapeElement'; +import { BlockVolume } from '@minecraft/server'; import { SignalStrengthRenderer } from '../../classes/SignalStrengthRenderer'; import { Vector } from '../../../lib/Vector'; -class RenderSignalStrength extends InfoDisplayElement{ +class RenderSignalStrength extends InfoDisplayShapeElement { player; playerId; - RENDER_DISTANCE = 8; - signalStrengthRenderers = new Map(); - runner = void 0; + RENDER_DISTANCE = 10; + signalStrengthRenderers = {}; constructor(player) { const ruleData = { identifier: 'renderSignalStrength', description: { translate: 'rules.infoDisplay.renderSignalStrength' }, - onEnableCallback: () => this.startRendering(), - onDisableCallback: () => this.stopRendering() + onEnableCallback: () => this.startRender(), + onDisableCallback: () => this.stopRender() }; super(ruleData, 0); this.player = player; this.playerId = player.id; } - startRendering() { - if (this.runner !== void 0) - this.stopRendering(); - this.signalStrengthRenderers = new Map(); - this.runner = system.runInterval(this.onTick.bind(this)); + startRender() { + this.signalStrengthRenderers = {}; } - stopRendering() { - if (this.runner !== void 0) { - system.clearRun(this.runner); - this.runner = void 0; + stopRender() { + for (const [key, renderer] of Object.entries(this.signalStrengthRenderers)) { + renderer.destroy(); + delete this.signalStrengthRenderers[key]; } - if (this.signalStrengthRenderers.size > 0) { - for (const renderer of this.signalStrengthRenderers.values()) - renderer.destroy(); - this.signalStrengthRenderers.clear(); - } - } - - refresh() { - this.stopRendering(); - this.startRendering(); + this.signalStrengthRenderers = {}; } onTick() { - if (this.player) - this.renderForNearbyBlocks(); - else - this.stopRendering(); + this.renderForNearbyBlocks(); } renderForNearbyBlocks() { - const blockLocationIterator = this.getNearbyBlockLocationIterator(); + 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(); - const blocks = []; while (!locationResult.done) { - const block = this.player.dimension.getBlock(locationResult.value); - blocks.push(block); - const key = Vector.from(block.location).toString(); - if (!this.signalStrengthRenderers.has(key)) - this.signalStrengthRenderers.set(key, new SignalStrengthRenderer(block, this.player)); + 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 this.signalStrengthRenderers) { - if (!blocks.some(e => e.id === renderer.block.id)) { + for (const [key, renderer] of Object.entries(this.signalStrengthRenderers)) { + if (!blockKeys.includes(key)) { renderer.destroy(); - this.signalStrengthRenderers.delete(key); + delete this.signalStrengthRenderers[key]; } } } - getNearbyBlockLocationIterator() { - const min = Vector.from(this.player.location).subtract(new Vector(this.RENDER_DISTANCE, this.RENDER_DISTANCE, this.RENDER_DISTANCE)); - const max = Vector.from(this.player.location).add(new Vector(this.RENDER_DISTANCE, 2, this.RENDER_DISTANCE)); + getNearbyBlockLocationIterator(dimension, location) { + const locationVector = Vector.from(location); + const min = locationVector.subtract(new Vector(this.RENDER_DISTANCE, this.RENDER_DISTANCE, this.RENDER_DISTANCE)); + const max = locationVector.add(new Vector(this.RENDER_DISTANCE, 2, this.RENDER_DISTANCE)); const volume = new BlockVolume(min, max); - const blockVolume = this.player.dimension.getBlocks(volume, { includeTypes: ["minecraft:redstone_wire"] }, true); + const blockVolume = dimension.getBlocks(volume, { includeTypes: ["minecraft:redstone_wire"] }, true); return blockVolume.getBlockLocationIterator(); } - getFormattedDataOwnLine() { - this.onTick(); - return { text: '' }; - } - - getFormattedDataSharedLine() { - return this.getFormattedDataOwnLine(); + getKey(block) { + return `<${block.x}, ${block.y}, ${block.z}>`; } } diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/SessionTime.js b/Canopy [BP]/scripts/src/rules/infodisplay/SessionTime.js index c3e46fc..6753cf3 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/SessionTime.js +++ b/Canopy [BP]/scripts/src/rules/infodisplay/SessionTime.js @@ -1,6 +1,6 @@ -import { InfoDisplayElement } from './InfoDisplayElement.js'; +import { InfoDisplayTextElement } from './InfoDisplayTextElement.js'; -class SessionTime extends InfoDisplayElement { +class SessionTime extends InfoDisplayTextElement { player; constructor(player, displayLine) { diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/SignalStrength.js b/Canopy [BP]/scripts/src/rules/infodisplay/SignalStrength.js index 8ebc112..65e0970 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/SignalStrength.js +++ b/Canopy [BP]/scripts/src/rules/infodisplay/SignalStrength.js @@ -1,7 +1,7 @@ -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) { diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/SimulationMap.js b/Canopy [BP]/scripts/src/rules/infodisplay/SimulationMap.js index 9fbfb82..35deb1c 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/SimulationMap.js +++ b/Canopy [BP]/scripts/src/rules/infodisplay/SimulationMap.js @@ -1,8 +1,8 @@ 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) { diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/SlimeChunk.js b/Canopy [BP]/scripts/src/rules/infodisplay/SlimeChunk.js index 083490b..82d2c13 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/SlimeChunk.js +++ b/Canopy [BP]/scripts/src/rules/infodisplay/SlimeChunk.js @@ -1,7 +1,7 @@ -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: '' }; diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Speed.js b/Canopy [BP]/scripts/src/rules/infodisplay/Speed.js index a37833e..f270336 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/Speed.js +++ b/Canopy [BP]/scripts/src/rules/infodisplay/Speed.js @@ -1,8 +1,8 @@ -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) { diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Structures.js b/Canopy [BP]/scripts/src/rules/infodisplay/Structures.js index e2f6e94..8584985 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/Structures.js +++ b/Canopy [BP]/scripts/src/rules/infodisplay/Structures.js @@ -1,6 +1,6 @@ -import { InfoDisplayElement } from "./InfoDisplayElement"; +import { InfoDisplayTextElement } from "./InfoDisplayTextElement"; -export class Structures extends InfoDisplayElement { +export class Structures extends InfoDisplayTextElement { player; constructor(player, displayLine) { diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/TPS.js b/Canopy [BP]/scripts/src/rules/infodisplay/TPS.js index 6d67b61..c7d266e 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/TPS.js +++ b/Canopy [BP]/scripts/src/rules/infodisplay/TPS.js @@ -1,8 +1,8 @@ -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' } }; super(ruleData, displayLine, true); diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Target.js b/Canopy [BP]/scripts/src/rules/infodisplay/Target.js index 866b09b..46013ea 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/Target.js +++ b/Canopy [BP]/scripts/src/rules/infodisplay/Target.js @@ -1,7 +1,7 @@ -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' } }; super(ruleData, displayLine); diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/TimeOfDay.js b/Canopy [BP]/scripts/src/rules/infodisplay/TimeOfDay.js index d63954f..e6b3644 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/TimeOfDay.js +++ b/Canopy [BP]/scripts/src/rules/infodisplay/TimeOfDay.js @@ -1,7 +1,7 @@ -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' } }; super(ruleData, displayLine, true); diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Velocity.js b/Canopy [BP]/scripts/src/rules/infodisplay/Velocity.js index bab0b83..b11e112 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/Velocity.js +++ b/Canopy [BP]/scripts/src/rules/infodisplay/Velocity.js @@ -1,7 +1,7 @@ -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) { diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Weather.js b/Canopy [BP]/scripts/src/rules/infodisplay/Weather.js index dbb1873..b0eb11c 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/Weather.js +++ b/Canopy [BP]/scripts/src/rules/infodisplay/Weather.js @@ -1,6 +1,6 @@ -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' } }; super(ruleData, displayLine); diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/WorldDay.js b/Canopy [BP]/scripts/src/rules/infodisplay/WorldDay.js index 67b6c13..7ea4b4b 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/WorldDay.js +++ b/Canopy [BP]/scripts/src/rules/infodisplay/WorldDay.js @@ -1,7 +1,7 @@ -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' } }; super(ruleData, displayLine, true); diff --git a/Canopy [RP]/texts/en_US.lang b/Canopy [RP]/texts/en_US.lang index e9fa083..fed5698 100644 --- a/Canopy [RP]/texts/en_US.lang +++ b/Canopy [RP]/texts/en_US.lang @@ -431,6 +431,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/__tests__/BP/scripts/src/rules/infodisplay/BlockStates.test.js b/__tests__/BP/scripts/src/rules/infodisplay/BlockStates.test.js index 7ecf62a..a7de9b4 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/BlockStates.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/BlockStates.test.js @@ -1,6 +1,6 @@ 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 { InfoDisplayTextElement } from '../../../../../../Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplayTextElement'; import { Rules } from '../../../../../../Canopy [BP]/scripts/lib/canopy/rules/Rules'; vi.mock('@minecraft/server', () => ({ @@ -59,8 +59,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', () => { diff --git a/__tests__/BP/scripts/src/rules/infodisplay/ChunkCoords.test.js b/__tests__/BP/scripts/src/rules/infodisplay/ChunkCoords.test.js index 6035b4d..2651adb 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/ChunkCoords.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/ChunkCoords.test.js @@ -1,6 +1,6 @@ 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 { InfoDisplayTextElement } from '../../../../../../Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplayTextElement'; import { Rules } from '../../../../../../Canopy [BP]/scripts/lib/canopy/rules/Rules'; vi.mock('@minecraft/server', () => ({ @@ -47,8 +47,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', () => { diff --git a/__tests__/BP/scripts/src/rules/infodisplay/Dimension.test.js b/__tests__/BP/scripts/src/rules/infodisplay/Dimension.test.js index 8e65d22..5eb59b8 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/Dimension.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/Dimension.test.js @@ -1,6 +1,6 @@ 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 { InfoDisplayTextElement } from '../../../../../../Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplayTextElement'; import { Rules } from '../../../../../../Canopy [BP]/scripts/lib/canopy/rules/Rules'; vi.mock('@minecraft/server', () => ({ @@ -46,8 +46,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', () => { diff --git a/__tests__/BP/scripts/src/rules/infodisplay/PeekInventory.test.js b/__tests__/BP/scripts/src/rules/infodisplay/PeekInventory.test.js index 5ad401b..a9a68ab 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/PeekInventory.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/PeekInventory.test.js @@ -1,6 +1,6 @@ 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 { InfoDisplayTextElement } from '../../../../../../Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplayTextElement'; import { Rules } from '../../../../../../Canopy [BP]/scripts/lib/canopy/rules/Rules'; vi.mock('@minecraft/server', () => ({ @@ -78,8 +78,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', () => { diff --git a/__tests__/BP/scripts/src/rules/infodisplay/Speed.test.js b/__tests__/BP/scripts/src/rules/infodisplay/Speed.test.js index 20fad6c..6a6c61e 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/Speed.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/Speed.test.js @@ -1,6 +1,6 @@ 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 { InfoDisplayTextElement } from '../../../../../../Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplayTextElement'; import { Rules } from '../../../../../../Canopy [BP]/scripts/lib/canopy/rules/Rules'; vi.mock('@minecraft/server', () => ({ @@ -44,8 +44,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', () => { diff --git a/__tests__/BP/scripts/src/rules/infodisplay/Structures.test.js b/__tests__/BP/scripts/src/rules/infodisplay/Structures.test.js index 473f807..62599d9 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/Structures.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/Structures.test.js @@ -1,6 +1,6 @@ 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 { InfoDisplayTextElement } from '../../../../../../Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplayTextElement'; import { Rules } from '../../../../../../Canopy [BP]/scripts/lib/canopy/rules/Rules'; vi.mock('@minecraft/server', () => ({ @@ -45,8 +45,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', () => { diff --git a/__tests__/BP/scripts/src/rules/infodisplay/Weather.test.js b/__tests__/BP/scripts/src/rules/infodisplay/Weather.test.js index 15de5bc..74ba1c6 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/Weather.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/Weather.test.js @@ -1,6 +1,6 @@ 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 { InfoDisplayTextElement } from '../../../../../../Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplayTextElement'; import { Rules } from '../../../../../../Canopy [BP]/scripts/lib/canopy/rules/Rules'; vi.mock('@minecraft/server', () => ({ @@ -45,8 +45,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', () => { diff --git a/__tests__/BP/scripts/src/rules/tntFuse.test.js b/__tests__/BP/scripts/src/rules/tntFuse.test.js index 331ff34..11b5ab1 100644 --- a/__tests__/BP/scripts/src/rules/tntFuse.test.js +++ b/__tests__/BP/scripts/src/rules/tntFuse.test.js @@ -44,6 +44,9 @@ vi.mock("@minecraft/server", () => ({ }, entitySpawn: { subscribe: vi.fn() + }, + entityLoad: { + subscribe: vi.fn() } }, setDynamicProperty: (identifier, ticks) => { tntFuseDP = ticks }, @@ -82,4 +85,11 @@ describe('tntFuseRule', () => { tntFuseDP = void 0; expect(tntFuseRule.getGlobalFuseTicks()).toBe(80); }); + + it('should restart the fuse when the entity loads', () => { + const startFuseMock = vi.spyOn(tntFuseRule, 'startFuse'); + tntFuseRule.setValue(15); + tntFuseRule.onEntityLoad({ entity: tntEntity }); + expect(startFuseMock).toHaveBeenCalledWith(tntEntity, 15); + }); }); \ No newline at end of file From 716d8442f73a355b2abb21a1b4287397b683f0cf Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Wed, 11 Mar 2026 13:48:21 -0700 Subject: [PATCH 05/64] fix rule object being inaccessible for InfoDisplayElements --- .../scripts/src/rules/infodisplay/InfoDisplayShapeElement.js | 1 - 1 file changed, 1 deletion(-) diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplayShapeElement.js b/Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplayShapeElement.js index 82aba89..3a177a6 100644 --- a/Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplayShapeElement.js +++ b/Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplayShapeElement.js @@ -2,7 +2,6 @@ import { InfoDisplayElement } from './InfoDisplayElement'; class InfoDisplayShapeElement extends InfoDisplayElement { identifier; - rule; isWorldwide; constructor(ruleData, isWorldwide = false) { From a34a29c2265bf21c670606dfbd051d8ce05a94d9 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Wed, 11 Mar 2026 14:03:42 -0700 Subject: [PATCH 06/64] move text-based elements from debug-utilities to server module --- .../src/classes/debugdisplay/DebugDisplayTextDrawer.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Canopy [BP]/scripts/src/classes/debugdisplay/DebugDisplayTextDrawer.js b/Canopy [BP]/scripts/src/classes/debugdisplay/DebugDisplayTextDrawer.js index 785b009..1696e87 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() { From 6da8ea973eabd2901a2e270b9a3a305d82297fcf Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Fri, 27 Mar 2026 15:00:42 -0700 Subject: [PATCH 07/64] improve curseforge upload automation --- .github/workflows/release.yml | 87 ++++++++++++++++++++++++++--------- 1 file changed, 65 insertions(+), 22 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index be42b78..c48435e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,6 +10,11 @@ on: jobs: publish: runs-on: ubuntu-latest + env: + PROJECT_NAME: Canopy + BP_SOURCE: "Canopy [BP]" + RP_SOURCE: "Canopy [RP]" + CF_PROJECT_ID: 1062078 steps: - uses: actions/checkout@v4 @@ -17,23 +22,23 @@ jobs: - name: Package mcaddon run: | VERSION=${{ github.event.release.tag_name }} - OUTPUT="Canopy-${VERSION}.mcaddon" + OUTPUT="${PROJECT_NAME}-${VERSION}.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]" + cp -r "$BP_SOURCE" "build/${BP_SOURCE}" + cp -r "$RP_SOURCE" "build/${RP_SOURCE}" + cp LICENSE "build/${BP_SOURCE}" + cp LICENSE "build/${RP_SOURCE}" cd build - zip -r "$OUTPUT" "Canopy[BP]" "Canopy[RP]" + zip -r "$OUTPUT" "${BP_SOURCE}" "${RP_SOURCE}" mv "$OUTPUT" .. cd .. - name: Upload asset to GitHub Release uses: softprops/action-gh-release@e798e6a1ede8d07b74ac4cdac6bdfa4cc1653907 with: - files: Canopy-${{ github.event.release.tag_name }}.mcaddon + files: ${{ env.PROJECT_NAME }}-${{ github.event.release.tag_name }}.mcaddon env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -42,33 +47,56 @@ jobs: RELEASE_BODY: ${{ github.event.release.body }} run: | VERSION=${{ github.event.release.tag_name }} + set -euo pipefail # Read the game version from manifest.json - MIN_ENGINE_VERSION=$(jq -r '.header.min_engine_version | @csv' "Canopy [BP]/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_JSON=$(curl -s -H "X-Api-Token: ${{ secrets.CF_API_TOKEN }}" \ + 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=$(echo "$CF_VERSIONS_JSON" | jq -c --arg prefix "$ENGINE_PREFIX" ' - [ .[] | select(.name | startswith($prefix)) | .id ] - ') - if [ -z "$GAME_VERSION_IDS" ]; then + 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" - ADDON_FILENAME=Canopy-$VERSION.mcaddon + PACK_FILENAME=${PROJECT_NAME}-$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" \ + --arg displayName "$PACK_FILENAME" \ --argjson gameVersions "$GAME_VERSION_IDS" \ '{ changelog: $changelog, @@ -77,20 +105,35 @@ jobs: releaseType: "release", gameVersions: $gameVersions }' > curseforge-metadata.json + METADATA_JSON=$(jq -c . curseforge-metadata.json) - # Upload the addon - RESPONSE=$(curl -s --fail-with-body -X POST \ + # Upload the behavior pack + 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 }}" \ - -F "metadata=@curseforge-metadata.json;type=application/json" \ - -F "file=@$ADDON_FILENAME" \ - https://minecraft-bedrock.curseforge.com/api/projects/1062078/upload-file + --form-string "metadata=$METADATA_JSON" \ + -F "file=@$PACK_FILENAME" \ + 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=$(echo "$RESPONSE" | jq -r '.id // empty') + 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:" - echo "$RESPONSE" + cat curseforge-upload-response.json exit 1 fi From c1b3aed9fad3c37becb0ea4753d2e0049290c6a9 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Fri, 27 Mar 2026 15:54:01 -0700 Subject: [PATCH 08/64] fix addon compilation step --- .github/workflows/release.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c48435e..c48183a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,15 +23,17 @@ jobs: run: | VERSION=${{ github.event.release.tag_name }} OUTPUT="${PROJECT_NAME}-${VERSION}.mcaddon" + BP_DIR="${PROJECT_NAME}[BP]" + RP_DIR="${PROJECT_NAME}[RP]" mkdir build - cp -r "$BP_SOURCE" "build/${BP_SOURCE}" - cp -r "$RP_SOURCE" "build/${RP_SOURCE}" - cp LICENSE "build/${BP_SOURCE}" - cp LICENSE "build/${RP_SOURCE}" + cp -r "$BP_SOURCE" "build/$BP_DIR" + cp -r "$RP_SOURCE" "build/$RP_DIR" + cp LICENSE "build/$BP_DIR" + cp LICENSE "build/$RP_DIR" cd build - zip -r "$OUTPUT" "${BP_SOURCE}" "${RP_SOURCE}" + zip -r "$OUTPUT" "$BP_DIR" "$RP_DIR" mv "$OUTPUT" .. cd .. From 2213e3835cb84f936ecb0eb7f459c9c3ac877502 Mon Sep 17 00:00:00 2001 From: ru-in-1 Date: Thu, 2 Apr 2026 17:47:55 +0900 Subject: [PATCH 09/64] update ja_JP.lang --- Canopy [RP]/texts/ja_JP.lang | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Canopy [RP]/texts/ja_JP.lang b/Canopy [RP]/texts/ja_JP.lang index 7e3e292..3a523b6 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 From 64918d665a724d4ba6b5f36e154121b5c3780d7d Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Wed, 15 Apr 2026 18:28:44 -0700 Subject: [PATCH 10/64] centralize vitest mocks into __mocks__ files Replace per-test-file vi.mock() boilerplate with comprehensive mock implementations in __mocks__/@minecraft/server.js and __mocks__/@minecraft/server-ui.js. All vi.fn() stubs (subscribe, runJob, run, runInterval, etc.) now live in one place and are applied automatically via the resolve.alias in vite.config.js. Adds vitest.setup.js (referenced by setupFiles) as a central place for future shared test setup. Co-Authored-By: Claude Sonnet 4.6 --- __mocks__/@minecraft/server-ui.js | 17 ++++--- __mocks__/@minecraft/server.js | 73 ++++++++++++++++++++++++++++--- vite.config.js | 12 ++--- vitest.setup.js | 4 ++ 4 files changed, 84 insertions(+), 22 deletions(-) create mode 100644 vitest.setup.js diff --git a/__mocks__/@minecraft/server-ui.js b/__mocks__/@minecraft/server-ui.js index 9437843..928e934 100644 --- a/__mocks__/@minecraft/server-ui.js +++ b/__mocks__/@minecraft/server-ui.js @@ -1,8 +1,13 @@ +import { vi } from 'vitest'; + export const FormCancelationReason = { - UserBusy: "UserBusy", - UserClosed: "UserClosed", -} + UserBusy: 'UserBusy', + UserClosed: 'UserClosed', +}; + export const uiManager = { - closeAllForms: () => {} -} -export const ActionFormData = {}; \ No newline at end of file + closeAllForms: vi.fn(), +}; + +export class ModalFormData {} +export class ActionFormData {} diff --git a/__mocks__/@minecraft/server.js b/__mocks__/@minecraft/server.js index 9c452a3..a45f9cf 100644 --- a/__mocks__/@minecraft/server.js +++ b/__mocks__/@minecraft/server.js @@ -1,11 +1,70 @@ -export const world = {}; -export const system = {}; -export const ItemStack = {}; +import { vi } from 'vitest'; + +export const world = { + beforeEvents: { + chatSend: { subscribe: vi.fn() }, + playerPlaceBlock: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + entityRemove: { subscribe: vi.fn() }, + playerLeave: { subscribe: vi.fn() }, + }, + afterEvents: { + worldLoad: { subscribe: vi.fn() }, + entitySpawn: { subscribe: vi.fn() }, + playerInventoryItemChange: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + pistonActivate: { subscribe: vi.fn() }, + }, + getDimension: vi.fn(), + getDynamicProperty: vi.fn(), + setDynamicProperty: vi.fn(), + structureManager: { + place: vi.fn(), + }, +}; + +export const system = { + currentTick: 0, + beforeEvents: { + startup: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + }, + afterEvents: { + scriptEventReceive: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + playerPlaceBlock: { subscribe: vi.fn() }, + }, + runJob: vi.fn(), + run: vi.fn(), + runInterval: vi.fn(), + runTimeout: vi.fn(), + clearRun: vi.fn(), +}; + +export const EntityComponentTypes = { + Inventory: 'inventory', +}; + +export const ItemComponentTypes = { + Durability: 'durability', + Enchantable: 'enchantable', +}; + +export const CustomCommandSource = { + Block: 'Block', + Entity: 'Entity', + Server: 'Server', +}; + +export const CustomCommandStatus = { + Failure: 'Failure', + Success: 'Success', +}; + 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 +export const TicksPerSecond = 20; +export const ItemStack = {}; +export class Block {} +export class Entity {} +export class Player { + sendMessage = vi.fn(); +} diff --git a/vite.config.js b/vite.config.js index 829429f..d44f64f 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,20 +1,14 @@ 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' } diff --git a/vitest.setup.js b/vitest.setup.js new file mode 100644 index 0000000..2ac8013 --- /dev/null +++ b/vitest.setup.js @@ -0,0 +1,4 @@ +// Global test setup. Add shared configuration here. +// Mock implementations live in __mocks__/@minecraft/server.js and __mocks__/@minecraft/server-ui.js. +// Test files that need different mock behavior (e.g. subscribe callbacks called immediately) +// can still override with vi.mock() locally. From 5db6a739036e7414cbe4fa35571a8efa30f70e32 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Wed, 15 Apr 2026 20:35:16 -0700 Subject: [PATCH 11/64] centralize @minecraft test mocks into __mocks__ files Moves all per-file vi.mock('@minecraft/server') and vi.mock('@minecraft/server-ui') blocks into centralized __mocks__/ files so mock definitions live in one place. Test files that need behavior different from the centralized mock use importOriginal to extend rather than replace the centralized mock. Co-Authored-By: Claude Sonnet 4.6 --- __mocks__/@minecraft/server-ui.js | 1 + __mocks__/@minecraft/server.js | 1 + __tests__/BP/scripts/include/data.test.js | 12 +- __tests__/BP/scripts/include/utils.test.js | 9 -- .../BP/scripts/lib/canopy/Canopy.test.js | 29 +--- .../BP/scripts/lib/canopy/Extension.test.js | 23 ---- .../BP/scripts/lib/canopy/Extensions.test.js | 25 +--- .../canopy/commands/ArgumentParser.test.js | 25 +--- .../lib/canopy/commands/Command.test.js | 23 ---- .../lib/canopy/commands/Commands.test.js | 44 +++--- .../canopy/commands/VanillaCommand.test.js | 58 +++----- .../lib/canopy/help/CommandHelpEntry.test.js | 25 +--- .../lib/canopy/help/CommandHelpPage.test.js | 23 ---- .../scripts/lib/canopy/help/HelpBook.test.js | 28 ---- .../help/InfoDisplayRuleHelpEntry.test.js | 23 ---- .../help/InfoDisplayRuleHelpPage.test.js | 24 ---- .../lib/canopy/help/RuleHelpPage.test.js | 24 ---- .../lib/canopy/rules/AbilityRule.test.js | 31 ----- .../lib/canopy/rules/BooleanRule.test.js | 44 ++---- .../lib/canopy/rules/GlobalRule.test.js | 36 ++--- .../lib/canopy/rules/InfoDisplayRule.test.js | 33 ++--- .../BP/scripts/lib/canopy/rules/Rules.test.js | 37 ++--- .../src/classes/EntityMovementLog.test.js | 70 ++++------ .../scripts/src/classes/EntityTntLog.test.js | 50 +++---- .../scripts/src/classes/InventoryUI.test.js | 50 +++---- .../BP/scripts/src/classes/Profiler.test.js | 42 +++--- .../BP/scripts/src/classes/TNTFuse.test.js | 62 ++++----- __tests__/BP/scripts/src/commands/log.test.js | 126 ++++++------------ .../BP/scripts/src/commands/velocity.test.js | 95 ++++--------- .../events/PlayerChangeSubChunkEvent.test.js | 36 ++--- .../src/events/PlayerStartSneakEvent.test.js | 45 +++---- .../events/SpawnEggSpawnEntityEvent.test.js | 52 ++++---- .../rules/allowBubbleColumnPlacement.test.js | 47 ++----- .../src/rules/allowPeekInventory.test.js | 85 ++++-------- .../creativeNetherWaterPlacement.test.js | 68 +++------- .../BP/scripts/src/rules/dupeTnt.test.js | 55 +++----- .../rules/echoShardsEnableShriekers.test.js | 67 +++------- .../src/rules/entitySeparation.test.js | 47 ++----- .../src/rules/infodisplay/BlockStates.test.js | 42 ++---- .../src/rules/infodisplay/ChunkCoords.test.js | 40 ++---- .../src/rules/infodisplay/Dimension.test.js | 40 ++---- .../rules/infodisplay/PeekInventory.test.js | 64 +++------ .../src/rules/infodisplay/Speed.test.js | 41 ++---- .../src/rules/infodisplay/Structures.test.js | 40 ++---- .../src/rules/infodisplay/Weather.test.js | 40 ++---- .../BP/scripts/src/rules/playerSit.test.js | 85 ++++-------- .../rules/spawnEggSpawnWithMinecart.test.js | 74 +++------- .../BP/scripts/src/rules/tntFuse.test.js | 64 +++------ 48 files changed, 581 insertions(+), 1524 deletions(-) diff --git a/__mocks__/@minecraft/server-ui.js b/__mocks__/@minecraft/server-ui.js index 928e934..fd0840e 100644 --- a/__mocks__/@minecraft/server-ui.js +++ b/__mocks__/@minecraft/server-ui.js @@ -1,3 +1,4 @@ +/* eslint-disable max-classes-per-file */ import { vi } from 'vitest'; export const FormCancelationReason = { diff --git a/__mocks__/@minecraft/server.js b/__mocks__/@minecraft/server.js index a45f9cf..21c6545 100644 --- a/__mocks__/@minecraft/server.js +++ b/__mocks__/@minecraft/server.js @@ -1,3 +1,4 @@ +/* eslint-disable max-classes-per-file */ import { vi } from 'vitest'; export const world = { diff --git a/__tests__/BP/scripts/include/data.test.js b/__tests__/BP/scripts/include/data.test.js index 7571b3e..8b17309 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 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 3d9f5a9..fd41d8c 100644 --- a/__tests__/BP/scripts/include/utils.test.js +++ b/__tests__/BP/scripts/include/utils.test.js @@ -5,15 +5,6 @@ import { getScriptEventSourceName, getScriptEventSourceObject, recolor, titleCase, formatColorStr } 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 fa3339f..e0d643c 100644 --- a/__tests__/BP/scripts/lib/canopy/Canopy.test.js +++ b/__tests__/BP/scripts/lib/canopy/Canopy.test.js @@ -1,33 +1,6 @@ -import { describe, it, expect, vi } from "vitest"; +import { describe, it, expect } 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() -})); - describe('Canopy module', () => { it('should export Commands', () => { expect(Canopy.Commands).toBeDefined(); diff --git a/__tests__/BP/scripts/lib/canopy/Extension.test.js b/__tests__/BP/scripts/lib/canopy/Extension.test.js index bc7d812..69353c4 100644 --- a/__tests__/BP/scripts/lib/canopy/Extension.test.js +++ b/__tests__/BP/scripts/lib/canopy/Extension.test.js @@ -3,29 +3,6 @@ import { Extension } from '../../../../../Canopy [BP]/scripts/lib/canopy/Extensi 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() - } -})); - describe('Extension', () => { const extensionData = { name: 'Test Extension', diff --git a/__tests__/BP/scripts/lib/canopy/Extensions.test.js b/__tests__/BP/scripts/lib/canopy/Extensions.test.js index 0531c73..ad0183a 100644 --- a/__tests__/BP/scripts/lib/canopy/Extensions.test.js +++ b/__tests__/BP/scripts/lib/canopy/Extensions.test.js @@ -1,30 +1,7 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; +import { describe, it, expect, 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() - } - } - }, - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } - }, - runJob: vi.fn() - } -})); - describe("Extensions", () => { beforeEach(() => { Extensions.clear(); diff --git a/__tests__/BP/scripts/lib/canopy/commands/ArgumentParser.test.js b/__tests__/BP/scripts/lib/canopy/commands/ArgumentParser.test.js index 2609870..dbd7a9c 100644 --- a/__tests__/BP/scripts/lib/canopy/commands/ArgumentParser.test.js +++ b/__tests__/BP/scripts/lib/canopy/commands/ArgumentParser.test.js @@ -1,29 +1,6 @@ -import { describe, it, expect, vi } from 'vitest'; +import { describe, it, expect } 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() - } -})); - describe('ArgumentParser', () => { describe('parseCommandString', () => { it('should throw an error for an empty command string', () => { diff --git a/__tests__/BP/scripts/lib/canopy/commands/Command.test.js b/__tests__/BP/scripts/lib/canopy/commands/Command.test.js index 905ad02..7217b46 100644 --- a/__tests__/BP/scripts/lib/canopy/commands/Command.test.js +++ b/__tests__/BP/scripts/lib/canopy/commands/Command.test.js @@ -6,29 +6,6 @@ import { Extension } from "../../../../../../Canopy [BP]/scripts/lib/canopy/Exte 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() - } -})); - describe("Command", () => { beforeEach(() => { Commands.clear(); diff --git a/__tests__/BP/scripts/lib/canopy/commands/Commands.test.js b/__tests__/BP/scripts/lib/canopy/commands/Commands.test.js index e89f8b0..24aa644 100644 --- a/__tests__/BP/scripts/lib/canopy/commands/Commands.test.js +++ b/__tests__/BP/scripts/lib/canopy/commands/Commands.test.js @@ -3,38 +3,24 @@ import { Commands } from "../../../../../../Canopy [BP]/scripts/lib/canopy/comma 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() +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', () => { beforeEach(() => { diff --git a/__tests__/BP/scripts/lib/canopy/commands/VanillaCommand.test.js b/__tests__/BP/scripts/lib/canopy/commands/VanillaCommand.test.js index 4ea2c26..d4603bd 100644 --- a/__tests__/BP/scripts/lib/canopy/commands/VanillaCommand.test.js +++ b/__tests__/BP/scripts/lib/canopy/commands/VanillaCommand.test.js @@ -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/help/CommandHelpEntry.test.js b/__tests__/BP/scripts/lib/canopy/help/CommandHelpEntry.test.js index b02eb09..317b17f 100644 --- a/__tests__/BP/scripts/lib/canopy/help/CommandHelpEntry.test.js +++ b/__tests__/BP/scripts/lib/canopy/help/CommandHelpEntry.test.js @@ -1,30 +1,7 @@ -import { describe, it, expect, vi } from "vitest"; +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"; -vi.mock('@minecraft/server', () => ({ - world: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() - } - }, - afterEvents: { - worldLoad: { - subscribe: vi.fn() - } - } - }, - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } - }, - runJob: vi.fn() - } -})); - describe('CommandHelpEntry', () => { const mockCommand = { getName: () => 'testCommand', diff --git a/__tests__/BP/scripts/lib/canopy/help/CommandHelpPage.test.js b/__tests__/BP/scripts/lib/canopy/help/CommandHelpPage.test.js index b19c9f9..b7f32a4 100644 --- a/__tests__/BP/scripts/lib/canopy/help/CommandHelpPage.test.js +++ b/__tests__/BP/scripts/lib/canopy/help/CommandHelpPage.test.js @@ -4,29 +4,6 @@ import { CommandHelpEntry } from "../../../../../../Canopy [BP]/scripts/lib/cano 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() - } -})); - describe('CommandHelpPage', () => { describe('constructor', () => { it('should create an instance with title and description', () => { diff --git a/__tests__/BP/scripts/lib/canopy/help/HelpBook.test.js b/__tests__/BP/scripts/lib/canopy/help/HelpBook.test.js index ec8d010..e67bd5a 100644 --- a/__tests__/BP/scripts/lib/canopy/help/HelpBook.test.js +++ b/__tests__/BP/scripts/lib/canopy/help/HelpBook.test.js @@ -7,34 +7,6 @@ import { CommandHelpPage } from "../../../../../../Canopy [BP]/scripts/lib/canop 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() -})); - // 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', () => ({ Rule: class Rule { diff --git a/__tests__/BP/scripts/lib/canopy/help/InfoDisplayRuleHelpEntry.test.js b/__tests__/BP/scripts/lib/canopy/help/InfoDisplayRuleHelpEntry.test.js index d9a9d50..4612d86 100644 --- a/__tests__/BP/scripts/lib/canopy/help/InfoDisplayRuleHelpEntry.test.js +++ b/__tests__/BP/scripts/lib/canopy/help/InfoDisplayRuleHelpEntry.test.js @@ -3,29 +3,6 @@ import { InfoDisplayRuleHelpEntry } from '../../../../../../Canopy [BP]/scripts/ 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() - } -})); - describe('InfoDisplayRuleHelpEntry', () => { let entry; beforeEach(() => { diff --git a/__tests__/BP/scripts/lib/canopy/help/InfoDisplayRuleHelpPage.test.js b/__tests__/BP/scripts/lib/canopy/help/InfoDisplayRuleHelpPage.test.js index b8522f3..c43cdcf 100644 --- a/__tests__/BP/scripts/lib/canopy/help/InfoDisplayRuleHelpPage.test.js +++ b/__tests__/BP/scripts/lib/canopy/help/InfoDisplayRuleHelpPage.test.js @@ -4,30 +4,6 @@ import { InfoDisplayRule } from '../../../../../../Canopy [BP]/scripts/lib/canop 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() - } -})); - // 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', () => ({ Rule: class Rule { diff --git a/__tests__/BP/scripts/lib/canopy/help/RuleHelpPage.test.js b/__tests__/BP/scripts/lib/canopy/help/RuleHelpPage.test.js index 40e3021..4ff092c 100644 --- a/__tests__/BP/scripts/lib/canopy/help/RuleHelpPage.test.js +++ b/__tests__/BP/scripts/lib/canopy/help/RuleHelpPage.test.js @@ -4,30 +4,6 @@ import { RuleHelpEntry } from "../../../../../../Canopy [BP]/scripts/lib/canopy/ 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() - } -})); - // 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', () => ({ Rule: class Rule { diff --git a/__tests__/BP/scripts/lib/canopy/rules/AbilityRule.test.js b/__tests__/BP/scripts/lib/canopy/rules/AbilityRule.test.js index c38183c..7945379 100644 --- a/__tests__/BP/scripts/lib/canopy/rules/AbilityRule.test.js +++ b/__tests__/BP/scripts/lib/canopy/rules/AbilityRule.test.js @@ -2,37 +2,6 @@ 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() - }, - playerInventoryItemChange: { - subscribe: vi.fn(), - unsubscribe: vi.fn() - } - } - }, - system: { - afterEvents: { - scriptEventReceive: { - subscribe: vi.fn() - } - }, - runJob: vi.fn() - } -})); - -vi.mock("@minecraft/server-ui", () => ({ - ModalFormData: vi.fn() -})); - const onPlayerEnableCallback = vi.fn(); const onPlayerDisableCallback = vi.fn(); diff --git a/__tests__/BP/scripts/lib/canopy/rules/BooleanRule.test.js b/__tests__/BP/scripts/lib/canopy/rules/BooleanRule.test.js index 29fb06b..4b1ef32 100644 --- a/__tests__/BP/scripts/lib/canopy/rules/BooleanRule.test.js +++ b/__tests__/BP/scripts/lib/canopy/rules/BooleanRule.test.js @@ -6,36 +6,20 @@ import { Extensions } from '../../../../../../Canopy [BP]/scripts/lib/canopy/Ext import { Extension } from '../../../../../../Canopy [BP]/scripts/lib/canopy/Extension.js'; import { RuleValueSet } from '../../../../../../Canopy [BP]/scripts/lib/canopy/extension.ipc.js'; -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({ diff --git a/__tests__/BP/scripts/lib/canopy/rules/GlobalRule.test.js b/__tests__/BP/scripts/lib/canopy/rules/GlobalRule.test.js index ebf04e6..2b742a6 100644 --- a/__tests__/BP/scripts/lib/canopy/rules/GlobalRule.test.js +++ b/__tests__/BP/scripts/lib/canopy/rules/GlobalRule.test.js @@ -2,31 +2,19 @@ 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"; -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 aca01ce..b78e156 100644 --- a/__tests__/BP/scripts/lib/canopy/rules/InfoDisplayRule.test.js +++ b/__tests__/BP/scripts/lib/canopy/rules/InfoDisplayRule.test.js @@ -3,30 +3,19 @@ import { Rules } from '../../../../../../Canopy [BP]/scripts/lib/canopy/rules/Ru 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(); - } +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; diff --git a/__tests__/BP/scripts/lib/canopy/rules/Rules.test.js b/__tests__/BP/scripts/lib/canopy/rules/Rules.test.js index f4a1d1d..e023c50 100644 --- a/__tests__/BP/scripts/lib/canopy/rules/Rules.test.js +++ b/__tests__/BP/scripts/lib/canopy/rules/Rules.test.js @@ -3,34 +3,19 @@ import { Rules } from "../../../../../../Canopy [BP]/scripts/lib/canopy/rules/Ru 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(); - } +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; diff --git a/__tests__/BP/scripts/src/classes/EntityMovementLog.test.js b/__tests__/BP/scripts/src/classes/EntityMovementLog.test.js index abea7ba..63be1ac 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"; -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 1d9d940..dc31cf3 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"; -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 7e1ed88..222dde3 100644 --- a/__tests__/BP/scripts/src/classes/InventoryUI.test.js +++ b/__tests__/BP/scripts/src/classes/InventoryUI.test.js @@ -2,40 +2,22 @@ import { InventoryUI } from "../../../../../Canopy [BP]/scripts/src/classes/Inve import { describe, it, expect, vi, afterEach } from "vitest"; 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 666604a..94a13ce 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() -})); +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 c7a53f0..633567f 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"; -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 836bba6..2aa6c52 100644 --- a/__tests__/BP/scripts/src/commands/log.test.js +++ b/__tests__/BP/scripts/src/commands/log.test.js @@ -3,96 +3,46 @@ import { logCommand } from "../../../../../Canopy [BP]/scripts/src/commands/log" import { Player } from "@minecraft/server"; 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 7f89dcf..6c4355d 100644 --- a/__tests__/BP/scripts/src/commands/velocity.test.js +++ b/__tests__/BP/scripts/src/commands/velocity.test.js @@ -3,79 +3,32 @@ import { velocityCommand } from "../../../../../Canopy [BP]/scripts/src/commands 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 ce21e93..fc0f9b6 100644 --- a/__tests__/BP/scripts/src/events/PlayerChangeSubChunkEvent.test.js +++ b/__tests__/BP/scripts/src/events/PlayerChangeSubChunkEvent.test.js @@ -4,23 +4,25 @@ 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 3e97dfb..37bb542 100644 --- a/__tests__/BP/scripts/src/events/PlayerStartSneakEvent.test.js +++ b/__tests__/BP/scripts/src/events/PlayerStartSneakEvent.test.js @@ -4,30 +4,27 @@ 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 2ce50a3..1469f9c 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 { 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 3e3b45a..8a2f307 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 { 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 7feaa19..f3661e1 100644 --- a/__tests__/BP/scripts/src/rules/allowPeekInventory.test.js +++ b/__tests__/BP/scripts/src/rules/allowPeekInventory.test.js @@ -2,65 +2,38 @@ import { allowPeekInventory } from "../../../../../Canopy [BP]/scripts/src/rules import { expect, it, describe, vi, afterEach } from "vitest"; 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 717d827..660f7b2 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 { 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 55e7901..67d35fe 100644 --- a/__tests__/BP/scripts/src/rules/dupeTnt.test.js +++ b/__tests__/BP/scripts/src/rules/dupeTnt.test.js @@ -3,42 +3,23 @@ import { spawnedEntitiesThisTick, handleTntDuplication } from '../../../../../Ca 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() - } +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', () => ({ BooleanRule: vi.fn(), @@ -47,10 +28,6 @@ vi.mock('../../../../../Canopy [BP]/scripts/lib/canopy/Canopy', () => ({ } })); -vi.mock("@minecraft/server-ui", () => ({ - ModalFormData: vi.fn() -})); - describe('dupeTnt Rule', () => { beforeEach(() => { spawnedEntitiesThisTick.length = 0; diff --git a/__tests__/BP/scripts/src/rules/echoShardsEnableShriekers.test.js b/__tests__/BP/scripts/src/rules/echoShardsEnableShriekers.test.js index f437774..0d46f99 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"; -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 fd75733..049bd01 100644 --- a/__tests__/BP/scripts/src/rules/entitySeparation.test.js +++ b/__tests__/BP/scripts/src/rules/entitySeparation.test.js @@ -2,42 +2,23 @@ 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"; -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 7ecf62a..53c27e8 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/BlockStates.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/BlockStates.test.js @@ -3,38 +3,20 @@ 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() +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(() => ({ diff --git a/__tests__/BP/scripts/src/rules/infodisplay/ChunkCoords.test.js b/__tests__/BP/scripts/src/rules/infodisplay/ChunkCoords.test.js index 6035b4d..404e4a7 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/ChunkCoords.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/ChunkCoords.test.js @@ -3,35 +3,19 @@ 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() +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: { diff --git a/__tests__/BP/scripts/src/rules/infodisplay/Dimension.test.js b/__tests__/BP/scripts/src/rules/infodisplay/Dimension.test.js index 8e65d22..4a8b956 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/Dimension.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/Dimension.test.js @@ -3,35 +3,19 @@ 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() +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: { diff --git a/__tests__/BP/scripts/src/rules/infodisplay/PeekInventory.test.js b/__tests__/BP/scripts/src/rules/infodisplay/PeekInventory.test.js index 5ad401b..37bffe4 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/PeekInventory.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/PeekInventory.test.js @@ -3,54 +3,26 @@ 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() +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(() => ({ diff --git a/__tests__/BP/scripts/src/rules/infodisplay/Speed.test.js b/__tests__/BP/scripts/src/rules/infodisplay/Speed.test.js index 20fad6c..4de974d 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/Speed.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/Speed.test.js @@ -3,36 +3,19 @@ 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() +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() - }, - TicksPerSecond: 20 -})); - -vi.mock("@minecraft/server-ui", () => ({ - ModalFormData: vi.fn() -})); + } + }; +}); const mockPlayer = { getVelocity: vi.fn(() => ({ x: 0, y: 0, z: 0 })), diff --git a/__tests__/BP/scripts/src/rules/infodisplay/Structures.test.js b/__tests__/BP/scripts/src/rules/infodisplay/Structures.test.js index 473f807..651f540 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/Structures.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/Structures.test.js @@ -3,35 +3,19 @@ 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() +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: { diff --git a/__tests__/BP/scripts/src/rules/infodisplay/Weather.test.js b/__tests__/BP/scripts/src/rules/infodisplay/Weather.test.js index 15de5bc..57bba7a 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/Weather.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/Weather.test.js @@ -3,35 +3,19 @@ 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() +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: { diff --git a/__tests__/BP/scripts/src/rules/playerSit.test.js b/__tests__/BP/scripts/src/rules/playerSit.test.js index 46b9029..a136f6c 100644 --- a/__tests__/BP/scripts/src/rules/playerSit.test.js +++ b/__tests__/BP/scripts/src/rules/playerSit.test.js @@ -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/spawnEggSpawnWithMinecart.test.js b/__tests__/BP/scripts/src/rules/spawnEggSpawnWithMinecart.test.js index 517eedc..5426e7d 100644 --- a/__tests__/BP/scripts/src/rules/spawnEggSpawnWithMinecart.test.js +++ b/__tests__/BP/scripts/src/rules/spawnEggSpawnWithMinecart.test.js @@ -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 11b5ab1..dfba06e 100644 --- a/__tests__/BP/scripts/src/rules/tntFuse.test.js +++ b/__tests__/BP/scripts/src/rules/tntFuse.test.js @@ -13,50 +13,30 @@ const tntEntity = { }; let tntFuseDP = false; -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: { - beforeEvents: { - chatSend: { - subscribe: vi.fn() - } - }, - afterEvents: { - worldLoad: { - subscribe: vi.fn() - }, - entitySpawn: { - subscribe: vi.fn() + world: { + ...original.world, + afterEvents: { + ...original.world.afterEvents, + entityLoad: { subscribe: vi.fn() } }, - entityLoad: { - subscribe: vi.fn() - } - }, - setDynamicProperty: (identifier, ticks) => { tntFuseDP = ticks }, - getDynamicProperty: () => tntFuseDP - } -})); - -vi.mock("@minecraft/server-ui", () => ({ - ModalFormData: vi.fn() -})); + setDynamicProperty: (identifier, ticks) => { tntFuseDP = ticks }, + getDynamicProperty: () => tntFuseDP + } + }; +}); describe('tntFuseRule', () => { afterEach(() => { From 3377274253d6724d42e7d35cd661ffafac1d37ad Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Wed, 15 Apr 2026 20:53:46 -0700 Subject: [PATCH 12/64] Fix linting errors --- __tests__/BP/scripts/lib/canopy/help/CommandHelpPage.test.js | 2 +- __tests__/BP/scripts/lib/canopy/help/HelpEntry.test.js | 1 - __tests__/BP/scripts/lib/canopy/help/HelpPage.test.js | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/__tests__/BP/scripts/lib/canopy/help/CommandHelpPage.test.js b/__tests__/BP/scripts/lib/canopy/help/CommandHelpPage.test.js index b7f32a4..0305ce0 100644 --- a/__tests__/BP/scripts/lib/canopy/help/CommandHelpPage.test.js +++ b/__tests__/BP/scripts/lib/canopy/help/CommandHelpPage.test.js @@ -1,4 +1,4 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; +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"; diff --git a/__tests__/BP/scripts/lib/canopy/help/HelpEntry.test.js b/__tests__/BP/scripts/lib/canopy/help/HelpEntry.test.js index 026a493..91e7b7b 100644 --- a/__tests__/BP/scripts/lib/canopy/help/HelpEntry.test.js +++ b/__tests__/BP/scripts/lib/canopy/help/HelpEntry.test.js @@ -1,4 +1,3 @@ -/* eslint-disable max-classes-per-file */ import { describe, it, expect } from "vitest"; import { HelpEntry } from "../../../../../../Canopy [BP]/scripts/lib/canopy/help/HelpEntry"; diff --git a/__tests__/BP/scripts/lib/canopy/help/HelpPage.test.js b/__tests__/BP/scripts/lib/canopy/help/HelpPage.test.js index 8e26f49..8d18c20 100644 --- a/__tests__/BP/scripts/lib/canopy/help/HelpPage.test.js +++ b/__tests__/BP/scripts/lib/canopy/help/HelpPage.test.js @@ -1,4 +1,3 @@ -/* eslint-disable max-classes-per-file */ import { describe, it, expect } from 'vitest'; import { HelpPage } from '../../../../../../Canopy [BP]/scripts/lib/canopy/help/HelpPage'; From 9589dc8b19a00349363f6de1e810125c60c9652a Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Thu, 16 Apr 2026 05:25:02 -0700 Subject: [PATCH 13/64] 100% test coverage for the canopy lib --- .../BP/scripts/lib/canopy/Extension.test.js | 85 +++++++++- .../BP/scripts/lib/canopy/Extensions.test.js | 35 ++++- .../canopy/commands/ArgumentParser.test.js | 9 ++ .../lib/canopy/commands/Commands.test.js | 53 ++++++- .../lib/canopy/help/RuleHelpEntry.test.js | 36 +++++ .../lib/canopy/rules/AbilityRule.test.js | 146 +++++++++++++++++- .../lib/canopy/rules/BooleanRule.test.js | 40 ++++- .../lib/canopy/rules/FloatRule.test.js | 88 +++++++++++ .../lib/canopy/rules/InfoDisplayRule.test.js | 15 ++ .../lib/canopy/rules/IntegerRule.test.js | 84 ++++++++++ .../BP/scripts/lib/canopy/rules/Rule.test.js | 83 ++++++++++ .../BP/scripts/lib/canopy/rules/Rules.test.js | 41 +++++ 12 files changed, 699 insertions(+), 16 deletions(-) create mode 100644 __tests__/BP/scripts/lib/canopy/rules/FloatRule.test.js create mode 100644 __tests__/BP/scripts/lib/canopy/rules/IntegerRule.test.js create mode 100644 __tests__/BP/scripts/lib/canopy/rules/Rule.test.js diff --git a/__tests__/BP/scripts/lib/canopy/Extension.test.js b/__tests__/BP/scripts/lib/canopy/Extension.test.js index 69353c4..eae3ac0 100644 --- a/__tests__/BP/scripts/lib/canopy/Extension.test.js +++ b/__tests__/BP/scripts/lib/canopy/Extension.test.js @@ -1,7 +1,10 @@ -import { describe, it, expect, vi, beforeAll, afterAll } from 'vitest'; +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 = { @@ -124,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 ad0183a..81b698f 100644 --- a/__tests__/BP/scripts/lib/canopy/Extensions.test.js +++ b/__tests__/BP/scripts/lib/canopy/Extensions.test.js @@ -1,6 +1,22 @@ -import { describe, it, expect, beforeEach } from "vitest"; +import { describe, it, expect, vi, 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(), + } + }; +}); + 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", () => { beforeEach(() => { @@ -78,7 +94,18 @@ describe("Extensions", () => { }); }); - describe.skip('setupExtensionRegistration()', () => { - // Gametest + describe('setupExtensionRegistration()', () => { + it('should register an extension and log when the IPC message is received', () => { + const registerCall = IPC.on.mock.calls.find(([channel]) => channel === 'canopyExtension:registerExtension'); + const callback = registerCall[2]; + + 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 dbd7a9c..ceda434 100644 --- a/__tests__/BP/scripts/lib/canopy/commands/ArgumentParser.test.js +++ b/__tests__/BP/scripts/lib/canopy/commands/ArgumentParser.test.js @@ -80,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/Commands.test.js b/__tests__/BP/scripts/lib/canopy/commands/Commands.test.js index 24aa644..75601f9 100644 --- a/__tests__/BP/scripts/lib/canopy/commands/Commands.test.js +++ b/__tests__/BP/scripts/lib/canopy/commands/Commands.test.js @@ -2,6 +2,7 @@ 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"; +import { world } from "@minecraft/server"; vi.mock('@minecraft/server', async (importOriginal) => { const original = await importOriginal(); @@ -197,6 +198,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', @@ -262,9 +277,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' } @@ -275,4 +290,36 @@ describe('Commands', () => { expect(command2.runCallback).toHaveBeenCalledWith(sender, { multiArg: '@player' }); }); }); + + describe('chatSend event handler', () => { + let chatCallback; + let chatSender; + + beforeEach(() => { + Commands.clear(); + Rules.clear(); + chatCallback = world.beforeEvents.chatSend.subscribe.mock.calls[0][0]; + chatSender = { sendMessage: vi.fn(), commandPermissionLevel: 1 }; + }); + + it('should cancel and execute the command when message starts with the prefix', async () => { + 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/help/RuleHelpEntry.test.js b/__tests__/BP/scripts/lib/canopy/help/RuleHelpEntry.test.js index 29344e4..641f4ff 100644 --- a/__tests__/BP/scripts/lib/canopy/help/RuleHelpEntry.test.js +++ b/__tests__/BP/scripts/lib/canopy/help/RuleHelpEntry.test.js @@ -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/rules/AbilityRule.test.js b/__tests__/BP/scripts/lib/canopy/rules/AbilityRule.test.js index 7945379..347de59 100644 --- a/__tests__/BP/scripts/lib/canopy/rules/AbilityRule.test.js +++ b/__tests__/BP/scripts/lib/canopy/rules/AbilityRule.test.js @@ -2,6 +2,30 @@ 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', async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + world: { + ...original.world, + beforeEvents: { + ...original.world.beforeEvents, + playerLeave: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + }, + afterEvents: { + ...original.world.afterEvents, + playerJoin: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + }, + getAllPlayers: vi.fn().mockReturnValue([]), + }, + system: { + ...original.system, + currentTick: 0, + run: vi.fn((cb) => cb()), + } + }; +}); + const onPlayerEnableCallback = vi.fn(); const onPlayerDisableCallback = vi.fn(); @@ -10,10 +34,13 @@ describe('AbilityRule', () => { const testRuleData = { category: 'Rules', identifier: 'testRule', + onEnableCallback: vi.fn(), + onDisableCallback: vi.fn(), }; beforeEach(() => { Rules.clear(); + vi.clearAllMocks(); abilityRule = new AbilityRule(testRuleData, { slotNumber: 1, onPlayerEnableCallback, onPlayerDisableCallback }); }); @@ -38,7 +65,124 @@ 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', async () => { + const { world } = await import('@minecraft/server'); + 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', async () => { + const { world } = await import('@minecraft/server'); + 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', async () => { + const { world } = await import('@minecraft/server'); + world.getAllPlayers.mockReturnValue([null]); + expect(() => abilityRule.refreshOnlinePlayers()).not.toThrow(); + }); + + it('should subscribe to events when onEnable is called', async () => { + const { world } = await import('@minecraft/server'); + 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', async () => { + const { world } = await import('@minecraft/server'); + 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', async () => { + 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 4b1ef32..a95fe78 100644 --- a/__tests__/BP/scripts/lib/canopy/rules/BooleanRule.test.js +++ b/__tests__/BP/scripts/lib/canopy/rules/BooleanRule.test.js @@ -5,6 +5,7 @@ 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', async (importOriginal) => { const original = await importOriginal(); @@ -153,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', () => { @@ -169,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 0000000..81ef926 --- /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/InfoDisplayRule.test.js b/__tests__/BP/scripts/lib/canopy/rules/InfoDisplayRule.test.js index b78e156..4985b4c 100644 --- a/__tests__/BP/scripts/lib/canopy/rules/InfoDisplayRule.test.js +++ b/__tests__/BP/scripts/lib/canopy/rules/InfoDisplayRule.test.js @@ -134,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 0000000..ef9c178 --- /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 0000000..aaaf4dc --- /dev/null +++ b/__tests__/BP/scripts/lib/canopy/rules/Rule.test.js @@ -0,0 +1,83 @@ +import { describe, it, expect, vi, 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'); + }); + }); +}); diff --git a/__tests__/BP/scripts/lib/canopy/rules/Rules.test.js b/__tests__/BP/scripts/lib/canopy/rules/Rules.test.js index e023c50..d7c2499 100644 --- a/__tests__/BP/scripts/lib/canopy/rules/Rules.test.js +++ b/__tests__/BP/scripts/lib/canopy/rules/Rules.test.js @@ -234,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({ From cd1e6f4c90ae5378e00e289ff377bf65d2d47389 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Thu, 16 Apr 2026 16:06:57 -0700 Subject: [PATCH 14/64] refactor Vector scaling - Vector.multiply -> Vector.scale - Vector.divide removed. Use Vector.scale(1/x) instead --- Canopy [BP]/scripts/lib/Vector.js | 15 +++++---------- .../scripts/src/classes/BiomeEdgeRenderer.js | 4 ++-- .../scripts/src/classes/ChunkBorderRender.js | 2 +- Canopy [BP]/scripts/src/classes/HSSFinder.js | 4 ++-- .../scripts/src/classes/StructureBoundsFinder.js | 2 +- .../scripts/src/classes/debugdisplay/AttackBox.js | 4 ++-- .../src/classes/debugdisplay/CollisionBox.js | 2 +- .../scripts/src/classes/debugdisplay/HitBox.js | 2 +- .../classes/debugdisplay/ViewDirectionVector.js | 2 +- .../src/events/PlayerChangeSubChunkEvent.js | 5 +++-- 10 files changed, 19 insertions(+), 23 deletions(-) diff --git a/Canopy [BP]/scripts/lib/Vector.js b/Canopy [BP]/scripts/lib/Vector.js index bc8d56d..92cddd8 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; @@ -62,8 +58,7 @@ 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); }, + multiply(num) { return Vector.scale(this, num); }, get length() { return Vector.magnitude(this); }, get normalized() { return Vector.normalize(this); }, x: 0, diff --git a/Canopy [BP]/scripts/src/classes/BiomeEdgeRenderer.js b/Canopy [BP]/scripts/src/classes/BiomeEdgeRenderer.js index fc056b4..bb534bf 100644 --- a/Canopy [BP]/scripts/src/classes/BiomeEdgeRenderer.js +++ b/Canopy [BP]/scripts/src/classes/BiomeEdgeRenderer.js @@ -138,7 +138,7 @@ 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; @@ -181,7 +181,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/ChunkBorderRender.js b/Canopy [BP]/scripts/src/classes/ChunkBorderRender.js index 56eb8fe..105ea51 100644 --- a/Canopy [BP]/scripts/src/classes/ChunkBorderRender.js +++ b/Canopy [BP]/scripts/src/classes/ChunkBorderRender.js @@ -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/HSSFinder.js b/Canopy [BP]/scripts/src/classes/HSSFinder.js index 39a0746..eb90c37 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) { diff --git a/Canopy [BP]/scripts/src/classes/StructureBoundsFinder.js b/Canopy [BP]/scripts/src/classes/StructureBoundsFinder.js index dbaf04e..f28bb1a 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/debugdisplay/AttackBox.js b/Canopy [BP]/scripts/src/classes/debugdisplay/AttackBox.js index 1c8fb1a..149de0b 100644 --- a/Canopy [BP]/scripts/src/classes/debugdisplay/AttackBox.js +++ b/Canopy [BP]/scripts/src/classes/debugdisplay/AttackBox.js @@ -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/CollisionBox.js b/Canopy [BP]/scripts/src/classes/debugdisplay/CollisionBox.js index 18b923e..72861d6 100644 --- a/Canopy [BP]/scripts/src/classes/debugdisplay/CollisionBox.js +++ b/Canopy [BP]/scripts/src/classes/debugdisplay/CollisionBox.js @@ -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/HitBox.js b/Canopy [BP]/scripts/src/classes/debugdisplay/HitBox.js index 59dc960..ef548ac 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/ViewDirectionVector.js b/Canopy [BP]/scripts/src/classes/debugdisplay/ViewDirectionVector.js index 31ea668..bb28a4c 100644 --- a/Canopy [BP]/scripts/src/classes/debugdisplay/ViewDirectionVector.js +++ b/Canopy [BP]/scripts/src/classes/debugdisplay/ViewDirectionVector.js @@ -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/events/PlayerChangeSubChunkEvent.js b/Canopy [BP]/scripts/src/events/PlayerChangeSubChunkEvent.js index 79e2d2c..0cdd605 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; From cb2fa7c2ac2748305051af206020ae513edfd2b2 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Thu, 16 Apr 2026 16:29:14 -0700 Subject: [PATCH 15/64] solve a couple linting errors --- __tests__/BP/scripts/lib/canopy/commands/Commands.test.js | 2 +- __tests__/BP/scripts/lib/canopy/rules/AbilityRule.test.js | 2 +- __tests__/BP/scripts/lib/canopy/rules/Rule.test.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/__tests__/BP/scripts/lib/canopy/commands/Commands.test.js b/__tests__/BP/scripts/lib/canopy/commands/Commands.test.js index 75601f9..c4df877 100644 --- a/__tests__/BP/scripts/lib/canopy/commands/Commands.test.js +++ b/__tests__/BP/scripts/lib/canopy/commands/Commands.test.js @@ -302,7 +302,7 @@ describe('Commands', () => { chatSender = { sendMessage: vi.fn(), commandPermissionLevel: 1 }; }); - it('should cancel and execute the command when message starts with the prefix', async () => { + it('should cancel and execute the command when message starts with the prefix', () => { const command = { getName: () => 'test', isOpOnly: () => false, diff --git a/__tests__/BP/scripts/lib/canopy/rules/AbilityRule.test.js b/__tests__/BP/scripts/lib/canopy/rules/AbilityRule.test.js index 347de59..f1ba931 100644 --- a/__tests__/BP/scripts/lib/canopy/rules/AbilityRule.test.js +++ b/__tests__/BP/scripts/lib/canopy/rules/AbilityRule.test.js @@ -167,7 +167,7 @@ describe('AbilityRule', () => { expect(abilityRule.playerJoinTick['player1']).toBe(0); }); - it('should disable the player on leave', async () => { + it('should disable the player on leave', () => { const player = { id: 'player1', onScreenDisplay: { setActionBar: vi.fn() } }; abilityRule.enableForPlayer(player); abilityRule.onPlayerLeave({ player }); diff --git a/__tests__/BP/scripts/lib/canopy/rules/Rule.test.js b/__tests__/BP/scripts/lib/canopy/rules/Rule.test.js index aaaf4dc..04097d4 100644 --- a/__tests__/BP/scripts/lib/canopy/rules/Rule.test.js +++ b/__tests__/BP/scripts/lib/canopy/rules/Rule.test.js @@ -1,4 +1,4 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; +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'; From cd181ae4055e82c33a557f865f735c9e9e37de7e Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Thu, 16 Apr 2026 17:00:00 -0700 Subject: [PATCH 16/64] Add regolith release with auto-pulled mob data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces three local regolith filters and two new profiles to automate the release workflow. Filters (filters/) update_mob_data — fetches the latest stable bedrock-samples tag from GitHub at build time and regenerates categoryToMobMap (from behavior_pack/spawn_rules/) and meleeMobs (from behavior_pack/entities/) in data.js bump_version — interactively prompts for major/minor/patch and updates PACK_VERSION in constants.js and header.name/header.version/dependency versions in both manifests, writing changes to both source files and the regolith temp copy package_mcaddon — renames the temp BP/RP dirs to Canopy[BP]/Canopy[RP], then zips them into build/Canopy-v{version}.mcaddon Regolith profiles (config.json) release — runs update_mob_data → package_mcaddon; exports to none since the filter writes the artifact directly to build/ bump-version — runs bump_version standalone; falls back to settings.bumpType when stdin is not a TTY VSCode tasks (.vscode/tasks.json) Added tasks for all profiles (run default, run preview, run release, bump version) and renamed the existing watch task to watch default for consistency. The bump version task opens a new panel and stays open so the readline prompt is accessible. --- .vscode/tasks.json | 43 ++++++++++-- config.json | 32 ++++++++- filters/bump_version/main.js | 117 ++++++++++++++++++++++++++++++ filters/package_mcaddon/main.js | 38 ++++++++++ filters/update_mob_data/main.js | 121 ++++++++++++++++++++++++++++++++ 5 files changed, 346 insertions(+), 5 deletions(-) create mode 100644 filters/bump_version/main.js create mode 100644 filters/package_mcaddon/main.js create mode 100644 filters/update_mob_data/main.js diff --git a/.vscode/tasks.json b/.vscode/tasks.json index a3649d4..a4a4eb2 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/config.json b/config.json index a4221e2..915b66c 100644 --- a/config.json +++ b/config.json @@ -8,7 +8,20 @@ }, "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/filters/bump_version/main.js b/filters/bump_version/main.js new file mode 100644 index 0000000..23b4652 --- /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 0000000..59c0a1f --- /dev/null +++ b/filters/package_mcaddon/main.js @@ -0,0 +1,38 @@ +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'); + +function getPackVersion() { + const content = fs.readFileSync(path.join(projectRoot, 'Canopy [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, `Canopy-v${version}.mcaddon`); + + if (fs.existsSync(outputPath)) + fs.unlinkSync(outputPath); + + fs.renameSync('BP', 'Canopy[BP]'); + fs.renameSync('RP', 'Canopy[RP]'); + + console.log(`[package-mcaddon] Creating Canopy-v${version}.mcaddon...`); + execFileSync('zip', ['-r', outputPath, 'Canopy[BP]', 'Canopy[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 0000000..caaa0c7 --- /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); +}); From c79836189c8a3d27d63ed65adca0ef61174c5f3f Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Thu, 16 Apr 2026 22:33:48 -0700 Subject: [PATCH 17/64] additional Vector.scale refactor --- Canopy [BP]/scripts/lib/Vector.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Canopy [BP]/scripts/lib/Vector.js b/Canopy [BP]/scripts/lib/Vector.js index 92cddd8..41ac2ec 100644 --- a/Canopy [BP]/scripts/lib/Vector.js +++ b/Canopy [BP]/scripts/lib/Vector.js @@ -58,7 +58,7 @@ 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.scale(this, num); }, + scale(num) { return Vector.scale(this, num); }, get length() { return Vector.magnitude(this); }, get normalized() { return Vector.normalize(this); }, x: 0, From 1a1f6f8464727cd44d0d1d5f780e7e52338e8c97 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Fri, 17 Apr 2026 18:11:25 -0700 Subject: [PATCH 18/64] Run CI on Ubuntu instead of Windows --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 21507b3..90e179a 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: From c0c81043fd444a99657110e315236a890d31cbdc Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Sun, 19 Apr 2026 15:22:53 -0700 Subject: [PATCH 19/64] Add LICENSE to packaged mcaddon --- filters/package_mcaddon/main.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/filters/package_mcaddon/main.js b/filters/package_mcaddon/main.js index 59c0a1f..11a5925 100644 --- a/filters/package_mcaddon/main.js +++ b/filters/package_mcaddon/main.js @@ -6,9 +6,10 @@ 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, 'Canopy [BP]', 'scripts', 'constants.js'), 'utf8'); + 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'; } @@ -17,16 +18,20 @@ function main() { fs.mkdirSync(buildDir, { recursive: true }); const version = getPackVersion(); - const outputPath = path.join(buildDir, `Canopy-v${version}.mcaddon`); + const outputPath = path.join(buildDir, `${projectName}-v${version}.mcaddon`); if (fs.existsSync(outputPath)) fs.unlinkSync(outputPath); - fs.renameSync('BP', 'Canopy[BP]'); - fs.renameSync('RP', 'Canopy[RP]'); + fs.renameSync('BP', `${projectName}[BP]`); + fs.renameSync('RP', `${projectName}[RP]`); - console.log(`[package-mcaddon] Creating Canopy-v${version}.mcaddon...`); - execFileSync('zip', ['-r', outputPath, 'Canopy[BP]', 'Canopy[RP]'], { stdio: 'inherit' }); + 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}`); } From 5fc9fe4b52cc85706327a5859f14557ba14ceefd Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Sun, 19 Apr 2026 15:23:21 -0700 Subject: [PATCH 20/64] Use package_mcaddon regolith filter for release --- .github/workflows/release.yml | 245 +++++++++++++++++++--------------- 1 file changed, 137 insertions(+), 108 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c48183a..afe6d57 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,134 +8,163 @@ on: types: [published] jobs: - publish: + build: runs-on: ubuntu-latest - env: - PROJECT_NAME: Canopy - BP_SOURCE: "Canopy [BP]" - RP_SOURCE: "Canopy [RP]" - CF_PROJECT_ID: 1062078 + 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="${PROJECT_NAME}-${VERSION}.mcaddon" - BP_DIR="${PROJECT_NAME}[BP]" - RP_DIR="${PROJECT_NAME}[RP]" - - mkdir build - cp -r "$BP_SOURCE" "build/$BP_DIR" - cp -r "$RP_SOURCE" "build/$RP_DIR" - cp LICENSE "build/$BP_DIR" - cp LICENSE "build/$RP_DIR" - - cd build - zip -r "$OUTPUT" "$BP_DIR" "$RP_DIR" - mv "$OUTPUT" .. - cd .. + 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 }} + + upload-github: + needs: build + runs-on: ubuntu-latest + + 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: ${{ env.PROJECT_NAME }}-${{ 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 }} - set -euo pipefail - - # 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" - - PACK_FILENAME=${PROJECT_NAME}-$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 "$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 behavior pack - UPLOAD_HTTP=$(curl -sS -X POST \ - -o curseforge-upload-response.json \ + 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 }}" \ - --form-string "metadata=$METADATA_JSON" \ - -F "file=@$PACK_FILENAME" \ - 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 + -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 + fi - if ! jq -e . curseforge-upload-response.json >/dev/null; then - echo "CurseForge upload response was not valid JSON" - cat curseforge-upload-response.json + # 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 - - 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:" + 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 + exit 1 + fi From b165e563c5360c98ea3e446981677b968f469f0b Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Fri, 24 Apr 2026 02:00:31 -0700 Subject: [PATCH 21/64] Rename Canopy [RP/BP] folders to have no space --- .vscode/launch.json | 2 +- .../entities/ender_pearl.json | 0 .../entities/minecart.json | 0 .../entities/rideable.json | 0 {Canopy [BP] => Canopy[BP]}/entities/tnt.json | 0 {Canopy [BP] => Canopy[BP]}/manifest.json | 0 {Canopy [BP] => Canopy[BP]}/pack_icon.png | Bin .../scripts/constants.js | 0 .../scripts/include/data.js | 0 .../scripts/include/utils.js | 0 .../scripts/lib/MCBE-IPC/ipc.d.ts | 0 .../scripts/lib/MCBE-IPC/ipc.js | 0 .../scripts/lib/SRCItemDatabase/DBManager.js | 0 .../scripts/lib/SRCItemDatabase/Database.d.ts | 0 .../scripts/lib/SRCItemDatabase/Database.js | 0 .../lib/SRCItemDatabase/ItemDatabase.d.ts | 0 .../scripts/lib/SRCItemDatabase/ItemDatabase.js | 0 .../scripts/lib/Vector.js | 0 .../scripts/lib/canopy/Canopy.js | 0 .../scripts/lib/canopy/Extension.js | 0 .../scripts/lib/canopy/Extensions.js | 0 .../lib/canopy/commands/ArgumentParser.js | 0 .../lib/canopy/commands/BlockCommandOrigin.js | 0 .../scripts/lib/canopy/commands/Command.js | 0 .../lib/canopy/commands/CommandOrigin.js | 0 .../scripts/lib/canopy/commands/Commands.js | 0 .../lib/canopy/commands/EntityCommandOrigin.js | 0 .../lib/canopy/commands/FeedbackMessageType.js | 0 .../lib/canopy/commands/PlayerCommandOrigin.js | 0 .../lib/canopy/commands/ServerCommandOrigin.js | 0 .../lib/canopy/commands/VanillaCommand.js | 0 .../scripts/lib/canopy/extension.ipc.js | 0 .../scripts/lib/canopy/help/CommandHelpEntry.js | 0 .../scripts/lib/canopy/help/CommandHelpPage.js | 0 .../scripts/lib/canopy/help/HelpBook.js | 0 .../scripts/lib/canopy/help/HelpEntry.js | 0 .../scripts/lib/canopy/help/HelpPage.js | 0 .../lib/canopy/help/InfoDisplayRuleHelpEntry.js | 0 .../lib/canopy/help/InfoDisplayRuleHelpPage.js | 0 .../scripts/lib/canopy/help/RuleHelpEntry.js | 0 .../scripts/lib/canopy/help/RuleHelpPage.js | 0 .../scripts/lib/canopy/rules/AbilityRule.js | 0 .../scripts/lib/canopy/rules/BooleanRule.js | 0 .../scripts/lib/canopy/rules/FloatRule.js | 0 .../scripts/lib/canopy/rules/GlobalRule.js | 0 .../scripts/lib/canopy/rules/InfoDisplayRule.js | 0 .../scripts/lib/canopy/rules/IntegerRule.js | 0 .../scripts/lib/canopy/rules/Rule.js | 0 .../scripts/lib/canopy/rules/Rules.js | 0 .../scripts/lib/chestui/constants.js | 0 .../scripts/lib/chestui/forms.d.ts | 0 .../scripts/lib/chestui/forms.js | 0 .../scripts/lib/chestui/typeIds.js | 0 {Canopy [BP] => Canopy[BP]}/scripts/main.js | 0 .../scripts/src/classes/BiomeEdgeFinder.js | 0 .../scripts/src/classes/BiomeEdgeRenderer.js | 0 .../scripts/src/classes/BlockRotator.js | 0 .../scripts/src/classes/ChunkBorderRender.js | 0 .../scripts/src/classes/CollisionBoxRenderer.js | 0 .../scripts/src/classes/CounterChannel.js | 0 .../scripts/src/classes/CounterChannels.js | 0 .../scripts/src/classes/DirectionState.js | 0 .../scripts/src/classes/EntityLifetimeRecord.js | 0 .../src/classes/EntityLifetimeRecords.js | 0 .../scripts/src/classes/EntityLog.js | 0 .../scripts/src/classes/EntityMovementLog.js | 0 .../scripts/src/classes/EntityTntLog.js | 0 .../scripts/src/classes/EventTracker.js | 0 .../scripts/src/classes/GeneratorChannel.js | 0 .../scripts/src/classes/GeneratorChannels.js | 0 .../scripts/src/classes/HSSFinder.js | 0 .../scripts/src/classes/HSSRenderer.js | 0 .../scripts/src/classes/HotbarManager.js | 0 .../scripts/src/classes/Instaminable.js | 0 .../scripts/src/classes/InventoryUI.js | 0 .../scripts/src/classes/ItemCounterChannel.js | 0 .../scripts/src/classes/ItemCounterChannels.js | 0 .../scripts/src/classes/ItemLifetimeRecord.js | 0 .../scripts/src/classes/Profiler.js | 0 .../scripts/src/classes/SpawnTracker.js | 0 .../src/classes/StructureBoundsFinder.js | 0 .../scripts/src/classes/TNTFuse.js | 0 .../scripts/src/classes/Warps.js | 0 .../scripts/src/classes/WorldLifetimeTracker.js | 0 .../scripts/src/classes/WorldSpawns.js | 0 .../scripts/src/classes/debugdisplay/Age.js | 0 .../src/classes/debugdisplay/AttackBox.js | 0 .../AttributeDebugDisplayElement.js | 0 .../debugdisplay/BooleanDebugDisplayElement.js | 0 .../scripts/src/classes/debugdisplay/Breath.js | 0 .../src/classes/debugdisplay/CollisionBox.js | 0 .../ComponentDebugDisplayElement.js | 0 .../src/classes/debugdisplay/Container.js | 0 .../src/classes/debugdisplay/DebugDisplay.js | 0 .../classes/debugdisplay/DebugDisplayElement.js | 0 .../debugdisplay/DebugDisplayShapeElement.js | 0 .../debugdisplay/DebugDisplayTextDrawer.js | 0 .../debugdisplay/DebugDisplayTextElement.js | 0 .../scripts/src/classes/debugdisplay/Effects.js | 0 .../src/classes/debugdisplay/Equipment.js | 0 .../src/classes/debugdisplay/Exhaustion.js | 0 .../src/classes/debugdisplay/EyeLevel.js | 0 .../src/classes/debugdisplay/Families.js | 0 .../src/classes/debugdisplay/FlySpeed.js | 0 .../src/classes/debugdisplay/Friction.js | 0 .../scripts/src/classes/debugdisplay/GrowUp.js | 0 .../src/classes/debugdisplay/HeadLocation.js | 0 .../scripts/src/classes/debugdisplay/Health.js | 0 .../scripts/src/classes/debugdisplay/HitBox.js | 0 .../scripts/src/classes/debugdisplay/Horse.js | 0 .../scripts/src/classes/debugdisplay/Hunger.js | 0 .../scripts/src/classes/debugdisplay/ID.js | 0 .../src/classes/debugdisplay/IsClimbing.js | 0 .../src/classes/debugdisplay/IsFalling.js | 0 .../classes/debugdisplay/IsIllagerCaptain.js | 0 .../src/classes/debugdisplay/IsInWater.js | 0 .../src/classes/debugdisplay/IsOnGround.js | 0 .../src/classes/debugdisplay/IsSleeping.js | 0 .../src/classes/debugdisplay/IsSneaking.js | 0 .../src/classes/debugdisplay/IsSprinting.js | 0 .../src/classes/debugdisplay/IsSwimming.js | 0 .../scripts/src/classes/debugdisplay/IsValid.js | 0 .../scripts/src/classes/debugdisplay/Item.js | 0 .../src/classes/debugdisplay/LavaMovement.js | 0 .../scripts/src/classes/debugdisplay/Leash.js | 0 .../src/classes/debugdisplay/Location.js | 0 .../src/classes/debugdisplay/Movement.js | 0 .../scripts/src/classes/debugdisplay/NameTag.js | 0 .../scripts/src/classes/debugdisplay/OnFire.js | 0 .../src/classes/debugdisplay/Projectile.js | 0 .../src/classes/debugdisplay/PushThrough.js | 0 .../scripts/src/classes/debugdisplay/Ride.js | 0 .../scripts/src/classes/debugdisplay/Riding.js | 0 .../src/classes/debugdisplay/Rotation.js | 0 .../src/classes/debugdisplay/Saturation.js | 0 .../scripts/src/classes/debugdisplay/Scale.js | 0 .../scripts/src/classes/debugdisplay/SkinId.js | 0 .../scripts/src/classes/debugdisplay/Speed.js | 0 .../src/classes/debugdisplay/Strength.js | 0 .../scripts/src/classes/debugdisplay/TNT.js | 0 .../scripts/src/classes/debugdisplay/Tame.js | 0 .../scripts/src/classes/debugdisplay/Target.js | 0 .../scripts/src/classes/debugdisplay/TypeID.js | 0 .../classes/debugdisplay/UnderwaterMovement.js | 0 .../scripts/src/classes/debugdisplay/Variant.js | 0 .../debugdisplay/VectorDebugDisplayElement.js | 0 .../src/classes/debugdisplay/Velocity.js | 0 .../src/classes/debugdisplay/ViewDirection.js | 0 .../classes/debugdisplay/ViewDirectionVector.js | 0 .../src/classes/debugdisplay/WantsJockey.js | 0 .../classes/errors/GeneratedStructureError.js | 0 .../scripts/src/commands/CommandEnums.js | 0 .../scripts/src/commands/biomeedges.js | 0 .../scripts/src/commands/butcher.js | 0 .../scripts/src/commands/camera.js | 0 .../scripts/src/commands/canopy.js | 0 .../scripts/src/commands/changedimension.js | 0 .../scripts/src/commands/claimprojectiles.js | 0 .../scripts/src/commands/cleanup.js | 0 .../scripts/src/commands/counter.js | 0 .../scripts/src/commands/data.js | 0 .../scripts/src/commands/debugentity.js | 0 .../scripts/src/commands/distance.js | 0 .../scripts/src/commands/entitydensity.js | 0 .../scripts/src/commands/gamemode.js | 0 .../scripts/src/commands/generator.js | 0 .../scripts/src/commands/health.js | 0 .../scripts/src/commands/help.js | 0 .../scripts/src/commands/hss.js | 0 .../scripts/src/commands/info.js | 0 .../scripts/src/commands/jump.js | 0 .../scripts/src/commands/lifetimequery.js | 0 .../scripts/src/commands/lifetimequeryitem.js | 0 .../scripts/src/commands/lifetimetracking.js | 0 .../scripts/src/commands/log.js | 0 .../scripts/src/commands/loop.js | 0 .../scripts/src/commands/peek.js | 0 .../scripts/src/commands/pos.js | 0 .../scripts/src/commands/retest.js | 0 .../src/commands/scriptevents/counter.js | 0 .../src/commands/scriptevents/generator.js | 0 .../scripts/src/commands/scriptevents/spawn.js | 0 .../scripts/src/commands/scriptevents/tick.js | 0 .../scripts/src/commands/simmap.js | 0 .../scripts/src/commands/sit.js | 0 .../scripts/src/commands/spawn.js | 0 .../scripts/src/commands/tick.js | 0 .../scripts/src/commands/trackevent.js | 0 .../scripts/src/commands/velocity.js | 0 .../scripts/src/commands/warp.js | 0 .../scripts/src/events/EffectRemoveEvent.js | 0 .../scripts/src/events/Event.js | 0 .../src/events/PlayerChangeSubChunkEvent.js | 0 .../scripts/src/events/PlayerStartSneakEvent.js | 0 .../scripts/src/events/PlayerTameEntityEvent.js | 0 .../src/events/SpawnEggSpawnEntityEvent.js | 0 .../scripts/src/onReload.js | 0 .../scripts/src/onStart.js | 0 .../src/rules/allowBubbleColumnPlacement.js | 0 .../scripts/src/rules/allowPeekInventory.js | 0 .../scripts/src/rules/armorStandRespawning.js | 0 .../scripts/src/rules/autoItemPickup.js | 0 .../src/rules/cauldronConcreteConversion.js | 0 .../scripts/src/rules/chunkBorders.js | 0 .../scripts/src/rules/collisionBoxes.js | 0 .../src/rules/creativeHotbarSwitching.js | 0 .../scripts/src/rules/creativeInstantTame.js | 0 .../src/rules/creativeNetherWaterPlacement.js | 0 .../scripts/src/rules/creativeNoTileDrops.js | 0 .../scripts/src/rules/creativeOneHitKill.js | 0 .../scripts/src/rules/dupeTnt.js | 0 .../scripts/src/rules/durabilityNotifier.js | 0 .../scripts/src/rules/durabilitySwap.js | 0 .../src/rules/echoShardsEnableShriekers.js | 0 .../scripts/src/rules/enderPearlChunkLoading.js | 0 .../scripts/src/rules/entityInstantDeath.js | 0 .../scripts/src/rules/entitySeparation.js | 0 .../src/rules/explosionChainReactionOnly.js | 0 .../scripts/src/rules/explosionNoBlockDamage.js | 0 .../scripts/src/rules/explosionOff.js | 0 .../scripts/src/rules/flippinArrows.js | 0 .../scripts/src/rules/infodisplay/Biome.js | 0 .../src/rules/infodisplay/BlockStates.js | 0 .../src/rules/infodisplay/CardinalFacing.js | 0 .../src/rules/infodisplay/ChunkCoords.js | 0 .../scripts/src/rules/infodisplay/Coords.js | 0 .../scripts/src/rules/infodisplay/Dimension.js | 0 .../scripts/src/rules/infodisplay/Entities.js | 0 .../src/rules/infodisplay/EventTrackers.js | 0 .../scripts/src/rules/infodisplay/Facing.js | 0 .../rules/infodisplay/HopperCounterCounts.js | 0 .../src/rules/infodisplay/InfoDisplay.js | 0 .../src/rules/infodisplay/InfoDisplayElement.js | 0 .../scripts/src/rules/infodisplay/Light.js | 0 .../src/rules/infodisplay/LiquidStates.js | 0 .../src/rules/infodisplay/LiquidTarget.js | 0 .../scripts/src/rules/infodisplay/MoonPhase.js | 0 .../src/rules/infodisplay/PeekInventory.js | 0 .../src/rules/infodisplay/SessionTime.js | 0 .../src/rules/infodisplay/SignalStrength.js | 0 .../src/rules/infodisplay/SimulationMap.js | 0 .../scripts/src/rules/infodisplay/SlimeChunk.js | 0 .../scripts/src/rules/infodisplay/Speed.js | 0 .../scripts/src/rules/infodisplay/Structures.js | 0 .../scripts/src/rules/infodisplay/TPS.js | 0 .../scripts/src/rules/infodisplay/Target.js | 0 .../scripts/src/rules/infodisplay/TimeOfDay.js | 0 .../scripts/src/rules/infodisplay/Velocity.js | 0 .../scripts/src/rules/infodisplay/Weather.js | 0 .../scripts/src/rules/infodisplay/WorldDay.js | 0 .../scripts/src/rules/instaminableDeepslate.js | 0 .../scripts/src/rules/instaminableEndstone.js | 0 .../scripts/src/rules/minecartChunkLoading.js | 0 .../scripts/src/rules/noWelcomeMessage.js | 0 .../scripts/src/rules/pistonBedrockBreaking.js | 0 .../scripts/src/rules/playerSit.js | 0 .../scripts/src/rules/potionBoostedBreeding.js | 0 .../scripts/src/rules/quickFillContainer.js | 0 .../scripts/src/rules/refillHand.js | 0 .../src/rules/renewableElytraDropChance.js | 0 .../scripts/src/rules/renewableSponge.js | 0 .../src/rules/serverSideCollisionBoxes.js | 0 .../src/rules/spawnEggSpawnWithMinecart.js | 0 .../scripts/src/rules/tntFuse.js | 0 .../scripts/src/rules/tntPrimeMomentum.js | 0 .../structures/bubble_column.mcstructure | Bin {Canopy [RP] => Canopy[RP]}/manifest.json | 0 {Canopy [RP] => Canopy[RP]}/pack_icon.png | Bin .../particles/fortress_hss_marker.particle.json | 0 .../particles/monument_hss_marker.particle.json | 0 .../pillager_outpost_hss_marker.particle.json | 0 .../swamp_hut_hss_marker.particle.json | 0 {Canopy [RP] => Canopy[RP]}/texts/cy_GB.lang | 0 {Canopy [RP] => Canopy[RP]}/texts/de_DE.lang | 0 {Canopy [RP] => Canopy[RP]}/texts/en_US.lang | 0 {Canopy [RP] => Canopy[RP]}/texts/id_ID.lang | 0 {Canopy [RP] => Canopy[RP]}/texts/ja_JP.lang | 0 .../texts/languages.json | 0 {Canopy [RP] => Canopy[RP]}/texts/zh_CN.lang | 0 .../textures/particle/fortress_hss_marker.png | Bin .../particle/ocean_monument_hss_marker.png | Bin .../textures/particle/outpost_hss_marker.png | Bin .../textures/particle/witch_hut_hss_marker.png | Bin .../textures/ui/d_b.json | 0 {Canopy [RP] => Canopy[RP]}/textures/ui/d_b.png | Bin .../textures/ui/d_g.json | 0 {Canopy [RP] => Canopy[RP]}/textures/ui/d_g.png | Bin .../textures/ui/item_background.json | 0 .../textures/ui/item_background.png | Bin .../ui/_global_variables.json | 0 {Canopy [RP] => Canopy[RP]}/ui/_ui_defs.json | 0 .../ui/chest_inventory_system.json | 0 .../ui/chest_server_form.json | 0 .../ui/furnace_server_form.json | 0 {Canopy [RP] => Canopy[RP]}/ui/hud_screen.json | 0 {Canopy [RP] => Canopy[RP]}/ui/server_form.json | 0 __tests__/BP/scripts/include/data.test.js | 4 ++-- __tests__/BP/scripts/include/utils.test.js | 2 +- __tests__/BP/scripts/lib/canopy/Canopy.test.js | 2 +- .../BP/scripts/lib/canopy/Extension.test.js | 12 ++++++------ .../BP/scripts/lib/canopy/Extensions.test.js | 8 ++++---- .../lib/canopy/commands/ArgumentParser.test.js | 2 +- .../canopy/commands/BlockCommandOrigin.test.js | 2 +- .../scripts/lib/canopy/commands/Command.test.js | 12 ++++++------ .../lib/canopy/commands/CommandOrigin.test.js | 4 ++-- .../lib/canopy/commands/Commands.test.js | 6 +++--- .../canopy/commands/EntityCommandOrigin.test.js | 2 +- .../canopy/commands/PlayerCommandOrigin.test.js | 4 ++-- .../canopy/commands/ServerCommandOrigin.test.js | 2 +- .../lib/canopy/commands/VanillaCommand.test.js | 16 ++++++++-------- .../lib/canopy/help/CommandHelpEntry.test.js | 4 ++-- .../lib/canopy/help/CommandHelpPage.test.js | 8 ++++---- .../BP/scripts/lib/canopy/help/HelpBook.test.js | 16 ++++++++-------- .../scripts/lib/canopy/help/HelpEntry.test.js | 2 +- .../BP/scripts/lib/canopy/help/HelpPage.test.js | 2 +- .../help/InfoDisplayRuleHelpEntry.test.js | 6 +++--- .../canopy/help/InfoDisplayRuleHelpPage.test.js | 10 +++++----- .../lib/canopy/help/RuleHelpEntry.test.js | 2 +- .../lib/canopy/help/RuleHelpPage.test.js | 10 +++++----- .../lib/canopy/rules/AbilityRule.test.js | 4 ++-- .../lib/canopy/rules/BooleanRule.test.js | 12 ++++++------ .../scripts/lib/canopy/rules/FloatRule.test.js | 4 ++-- .../scripts/lib/canopy/rules/GlobalRule.test.js | 4 ++-- .../lib/canopy/rules/InfoDisplayRule.test.js | 6 +++--- .../lib/canopy/rules/IntegerRule.test.js | 4 ++-- .../BP/scripts/lib/canopy/rules/Rule.test.js | 8 ++++---- .../BP/scripts/lib/canopy/rules/Rules.test.js | 6 +++--- .../src/classes/EntityMovementLog.test.js | 2 +- .../BP/scripts/src/classes/EntityTntLog.test.js | 2 +- .../BP/scripts/src/classes/InventoryUI.test.js | 4 ++-- .../BP/scripts/src/classes/Profiler.test.js | 2 +- .../BP/scripts/src/classes/TNTFuse.test.js | 2 +- __tests__/BP/scripts/src/commands/log.test.js | 4 ++-- .../BP/scripts/src/commands/velocity.test.js | 6 +++--- .../events/PlayerChangeSubChunkEvent.test.js | 2 +- .../src/events/PlayerStartSneakEvent.test.js | 2 +- .../src/events/SpawnEggSpawnEntityEvent.test.js | 2 +- .../rules/allowBubbleColumnPlacement.test.js | 2 +- .../src/rules/allowPeekInventory.test.js | 4 ++-- .../rules/creativeNetherWaterPlacement.test.js | 2 +- __tests__/BP/scripts/src/rules/dupeTnt.test.js | 6 +++--- .../src/rules/echoShardsEnableShriekers.test.js | 2 +- .../scripts/src/rules/entitySeparation.test.js | 4 ++-- .../src/rules/infodisplay/BlockStates.test.js | 6 +++--- .../src/rules/infodisplay/ChunkCoords.test.js | 6 +++--- .../src/rules/infodisplay/Dimension.test.js | 6 +++--- .../src/rules/infodisplay/PeekInventory.test.js | 6 +++--- .../scripts/src/rules/infodisplay/Speed.test.js | 6 +++--- .../src/rules/infodisplay/Structures.test.js | 6 +++--- .../src/rules/infodisplay/Weather.test.js | 6 +++--- .../BP/scripts/src/rules/playerSit.test.js | 4 ++-- .../src/rules/spawnEggSpawnWithMinecart.test.js | 2 +- __tests__/BP/scripts/src/rules/tntFuse.test.js | 2 +- __tests__/manifests.test.js | 10 +++++----- config.json | 4 ++-- filters/bump_version/main.js | 8 ++++---- 356 files changed, 154 insertions(+), 154 deletions(-) rename {Canopy [BP] => Canopy[BP]}/entities/ender_pearl.json (100%) rename {Canopy [BP] => Canopy[BP]}/entities/minecart.json (100%) rename {Canopy [BP] => Canopy[BP]}/entities/rideable.json (100%) rename {Canopy [BP] => Canopy[BP]}/entities/tnt.json (100%) rename {Canopy [BP] => Canopy[BP]}/manifest.json (100%) rename {Canopy [BP] => Canopy[BP]}/pack_icon.png (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/constants.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/include/data.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/include/utils.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/MCBE-IPC/ipc.d.ts (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/MCBE-IPC/ipc.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/SRCItemDatabase/DBManager.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/SRCItemDatabase/Database.d.ts (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/SRCItemDatabase/Database.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/SRCItemDatabase/ItemDatabase.d.ts (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/SRCItemDatabase/ItemDatabase.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/Vector.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/canopy/Canopy.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/canopy/Extension.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/canopy/Extensions.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/canopy/commands/ArgumentParser.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/canopy/commands/BlockCommandOrigin.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/canopy/commands/Command.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/canopy/commands/CommandOrigin.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/canopy/commands/Commands.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/canopy/commands/EntityCommandOrigin.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/canopy/commands/FeedbackMessageType.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/canopy/commands/PlayerCommandOrigin.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/canopy/commands/ServerCommandOrigin.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/canopy/commands/VanillaCommand.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/canopy/extension.ipc.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/canopy/help/CommandHelpEntry.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/canopy/help/CommandHelpPage.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/canopy/help/HelpBook.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/canopy/help/HelpEntry.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/canopy/help/HelpPage.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/canopy/help/InfoDisplayRuleHelpEntry.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/canopy/help/InfoDisplayRuleHelpPage.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/canopy/help/RuleHelpEntry.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/canopy/help/RuleHelpPage.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/canopy/rules/AbilityRule.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/canopy/rules/BooleanRule.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/canopy/rules/FloatRule.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/canopy/rules/GlobalRule.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/canopy/rules/InfoDisplayRule.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/canopy/rules/IntegerRule.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/canopy/rules/Rule.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/canopy/rules/Rules.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/chestui/constants.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/chestui/forms.d.ts (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/chestui/forms.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/lib/chestui/typeIds.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/main.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/BiomeEdgeFinder.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/BiomeEdgeRenderer.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/BlockRotator.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/ChunkBorderRender.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/CollisionBoxRenderer.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/CounterChannel.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/CounterChannels.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/DirectionState.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/EntityLifetimeRecord.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/EntityLifetimeRecords.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/EntityLog.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/EntityMovementLog.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/EntityTntLog.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/EventTracker.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/GeneratorChannel.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/GeneratorChannels.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/HSSFinder.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/HSSRenderer.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/HotbarManager.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/Instaminable.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/InventoryUI.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/ItemCounterChannel.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/ItemCounterChannels.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/ItemLifetimeRecord.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/Profiler.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/SpawnTracker.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/StructureBoundsFinder.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/TNTFuse.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/Warps.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/WorldLifetimeTracker.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/WorldSpawns.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/Age.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/AttackBox.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/AttributeDebugDisplayElement.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/BooleanDebugDisplayElement.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/Breath.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/CollisionBox.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/ComponentDebugDisplayElement.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/Container.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/DebugDisplay.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/DebugDisplayElement.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/DebugDisplayShapeElement.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/DebugDisplayTextDrawer.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/DebugDisplayTextElement.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/Effects.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/Equipment.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/Exhaustion.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/EyeLevel.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/Families.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/FlySpeed.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/Friction.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/GrowUp.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/HeadLocation.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/Health.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/HitBox.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/Horse.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/Hunger.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/ID.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/IsClimbing.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/IsFalling.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/IsIllagerCaptain.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/IsInWater.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/IsOnGround.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/IsSleeping.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/IsSneaking.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/IsSprinting.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/IsSwimming.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/IsValid.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/Item.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/LavaMovement.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/Leash.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/Location.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/Movement.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/NameTag.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/OnFire.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/Projectile.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/PushThrough.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/Ride.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/Riding.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/Rotation.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/Saturation.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/Scale.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/SkinId.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/Speed.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/Strength.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/TNT.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/Tame.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/Target.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/TypeID.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/UnderwaterMovement.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/Variant.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/VectorDebugDisplayElement.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/Velocity.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/ViewDirection.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/ViewDirectionVector.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/debugdisplay/WantsJockey.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/classes/errors/GeneratedStructureError.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/CommandEnums.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/biomeedges.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/butcher.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/camera.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/canopy.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/changedimension.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/claimprojectiles.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/cleanup.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/counter.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/data.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/debugentity.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/distance.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/entitydensity.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/gamemode.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/generator.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/health.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/help.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/hss.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/info.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/jump.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/lifetimequery.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/lifetimequeryitem.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/lifetimetracking.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/log.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/loop.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/peek.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/pos.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/retest.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/scriptevents/counter.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/scriptevents/generator.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/scriptevents/spawn.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/scriptevents/tick.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/simmap.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/sit.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/spawn.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/tick.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/trackevent.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/velocity.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/commands/warp.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/events/EffectRemoveEvent.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/events/Event.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/events/PlayerChangeSubChunkEvent.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/events/PlayerStartSneakEvent.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/events/PlayerTameEntityEvent.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/events/SpawnEggSpawnEntityEvent.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/onReload.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/onStart.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/allowBubbleColumnPlacement.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/allowPeekInventory.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/armorStandRespawning.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/autoItemPickup.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/cauldronConcreteConversion.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/chunkBorders.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/collisionBoxes.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/creativeHotbarSwitching.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/creativeInstantTame.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/creativeNetherWaterPlacement.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/creativeNoTileDrops.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/creativeOneHitKill.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/dupeTnt.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/durabilityNotifier.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/durabilitySwap.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/echoShardsEnableShriekers.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/enderPearlChunkLoading.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/entityInstantDeath.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/entitySeparation.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/explosionChainReactionOnly.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/explosionNoBlockDamage.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/explosionOff.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/flippinArrows.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/infodisplay/Biome.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/infodisplay/BlockStates.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/infodisplay/CardinalFacing.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/infodisplay/ChunkCoords.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/infodisplay/Coords.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/infodisplay/Dimension.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/infodisplay/Entities.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/infodisplay/EventTrackers.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/infodisplay/Facing.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/infodisplay/HopperCounterCounts.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/infodisplay/InfoDisplay.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/infodisplay/InfoDisplayElement.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/infodisplay/Light.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/infodisplay/LiquidStates.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/infodisplay/LiquidTarget.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/infodisplay/MoonPhase.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/infodisplay/PeekInventory.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/infodisplay/SessionTime.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/infodisplay/SignalStrength.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/infodisplay/SimulationMap.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/infodisplay/SlimeChunk.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/infodisplay/Speed.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/infodisplay/Structures.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/infodisplay/TPS.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/infodisplay/Target.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/infodisplay/TimeOfDay.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/infodisplay/Velocity.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/infodisplay/Weather.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/infodisplay/WorldDay.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/instaminableDeepslate.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/instaminableEndstone.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/minecartChunkLoading.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/noWelcomeMessage.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/pistonBedrockBreaking.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/playerSit.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/potionBoostedBreeding.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/quickFillContainer.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/refillHand.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/renewableElytraDropChance.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/renewableSponge.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/serverSideCollisionBoxes.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/spawnEggSpawnWithMinecart.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/tntFuse.js (100%) rename {Canopy [BP] => Canopy[BP]}/scripts/src/rules/tntPrimeMomentum.js (100%) rename {Canopy [BP] => Canopy[BP]}/structures/bubble_column.mcstructure (100%) rename {Canopy [RP] => Canopy[RP]}/manifest.json (100%) rename {Canopy [RP] => Canopy[RP]}/pack_icon.png (100%) rename {Canopy [RP] => Canopy[RP]}/particles/fortress_hss_marker.particle.json (100%) rename {Canopy [RP] => Canopy[RP]}/particles/monument_hss_marker.particle.json (100%) rename {Canopy [RP] => Canopy[RP]}/particles/pillager_outpost_hss_marker.particle.json (100%) rename {Canopy [RP] => Canopy[RP]}/particles/swamp_hut_hss_marker.particle.json (100%) rename {Canopy [RP] => Canopy[RP]}/texts/cy_GB.lang (100%) rename {Canopy [RP] => Canopy[RP]}/texts/de_DE.lang (100%) rename {Canopy [RP] => Canopy[RP]}/texts/en_US.lang (100%) rename {Canopy [RP] => Canopy[RP]}/texts/id_ID.lang (100%) rename {Canopy [RP] => Canopy[RP]}/texts/ja_JP.lang (100%) rename {Canopy [RP] => Canopy[RP]}/texts/languages.json (100%) rename {Canopy [RP] => Canopy[RP]}/texts/zh_CN.lang (100%) rename {Canopy [RP] => Canopy[RP]}/textures/particle/fortress_hss_marker.png (100%) rename {Canopy [RP] => Canopy[RP]}/textures/particle/ocean_monument_hss_marker.png (100%) rename {Canopy [RP] => Canopy[RP]}/textures/particle/outpost_hss_marker.png (100%) rename {Canopy [RP] => Canopy[RP]}/textures/particle/witch_hut_hss_marker.png (100%) rename {Canopy [RP] => Canopy[RP]}/textures/ui/d_b.json (100%) rename {Canopy [RP] => Canopy[RP]}/textures/ui/d_b.png (100%) rename {Canopy [RP] => Canopy[RP]}/textures/ui/d_g.json (100%) rename {Canopy [RP] => Canopy[RP]}/textures/ui/d_g.png (100%) rename {Canopy [RP] => Canopy[RP]}/textures/ui/item_background.json (100%) rename {Canopy [RP] => Canopy[RP]}/textures/ui/item_background.png (100%) rename {Canopy [RP] => Canopy[RP]}/ui/_global_variables.json (100%) rename {Canopy [RP] => Canopy[RP]}/ui/_ui_defs.json (100%) rename {Canopy [RP] => Canopy[RP]}/ui/chest_inventory_system.json (100%) rename {Canopy [RP] => Canopy[RP]}/ui/chest_server_form.json (100%) rename {Canopy [RP] => Canopy[RP]}/ui/furnace_server_form.json (100%) rename {Canopy [RP] => Canopy[RP]}/ui/hud_screen.json (100%) rename {Canopy [RP] => Canopy[RP]}/ui/server_form.json (100%) diff --git a/.vscode/launch.json b/.vscode/launch.json index a7f82d2..46b27c0 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,7 +7,7 @@ "name": "Debug with Minecraft", "mode": "listen", "targetModuleUuid": "3d753132-e3c9-4305-a995-eae30b486093", - "localRoot": "${workspaceFolder}/Canopy [BP]/scripts", + "localRoot": "${workspaceFolder}/Canopy[BP]/scripts", "port": 19144 } ] 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 100% rename from Canopy [BP]/manifest.json rename to Canopy[BP]/manifest.json 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 similarity index 100% rename from Canopy [BP]/scripts/constants.js rename to Canopy[BP]/scripts/constants.js 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 100% rename from Canopy [BP]/scripts/lib/Vector.js rename to Canopy[BP]/scripts/lib/Vector.js diff --git a/Canopy [BP]/scripts/lib/canopy/Canopy.js b/Canopy[BP]/scripts/lib/canopy/Canopy.js similarity index 100% rename from Canopy [BP]/scripts/lib/canopy/Canopy.js rename to Canopy[BP]/scripts/lib/canopy/Canopy.js 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 100% rename from Canopy [BP]/scripts/lib/canopy/commands/Command.js rename to Canopy[BP]/scripts/lib/canopy/commands/Command.js 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 100% rename from Canopy [BP]/scripts/lib/canopy/commands/VanillaCommand.js rename to Canopy[BP]/scripts/lib/canopy/commands/VanillaCommand.js 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 100% rename from Canopy [BP]/scripts/lib/canopy/rules/BooleanRule.js rename to Canopy[BP]/scripts/lib/canopy/rules/BooleanRule.js diff --git a/Canopy [BP]/scripts/lib/canopy/rules/FloatRule.js b/Canopy[BP]/scripts/lib/canopy/rules/FloatRule.js similarity index 100% rename from Canopy [BP]/scripts/lib/canopy/rules/FloatRule.js rename to Canopy[BP]/scripts/lib/canopy/rules/FloatRule.js 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 100% rename from Canopy [BP]/scripts/lib/canopy/rules/IntegerRule.js rename to Canopy[BP]/scripts/lib/canopy/rules/IntegerRule.js diff --git a/Canopy [BP]/scripts/lib/canopy/rules/Rule.js b/Canopy[BP]/scripts/lib/canopy/rules/Rule.js similarity index 100% rename from Canopy [BP]/scripts/lib/canopy/rules/Rule.js rename to Canopy[BP]/scripts/lib/canopy/rules/Rule.js diff --git a/Canopy [BP]/scripts/lib/canopy/rules/Rules.js b/Canopy[BP]/scripts/lib/canopy/rules/Rules.js similarity index 100% rename from Canopy [BP]/scripts/lib/canopy/rules/Rules.js rename to Canopy[BP]/scripts/lib/canopy/rules/Rules.js 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 100% rename from Canopy [BP]/scripts/main.js rename to Canopy[BP]/scripts/main.js 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 100% rename from Canopy [BP]/scripts/src/classes/BiomeEdgeRenderer.js rename to Canopy[BP]/scripts/src/classes/BiomeEdgeRenderer.js 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 100% rename from Canopy [BP]/scripts/src/classes/ChunkBorderRender.js rename to Canopy[BP]/scripts/src/classes/ChunkBorderRender.js 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/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 100% rename from Canopy [BP]/scripts/src/classes/HSSFinder.js rename to Canopy[BP]/scripts/src/classes/HSSFinder.js diff --git a/Canopy [BP]/scripts/src/classes/HSSRenderer.js b/Canopy[BP]/scripts/src/classes/HSSRenderer.js similarity index 100% rename from Canopy [BP]/scripts/src/classes/HSSRenderer.js rename to Canopy[BP]/scripts/src/classes/HSSRenderer.js 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/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 100% rename from Canopy [BP]/scripts/src/classes/StructureBoundsFinder.js rename to Canopy[BP]/scripts/src/classes/StructureBoundsFinder.js 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 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/AttackBox.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/AttackBox.js 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 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/CollisionBox.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/CollisionBox.js 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 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/DebugDisplayTextDrawer.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/DebugDisplayTextDrawer.js 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 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/EyeLevel.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/EyeLevel.js 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 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/HitBox.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/HitBox.js 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 100% rename from Canopy [BP]/scripts/src/classes/debugdisplay/ViewDirectionVector.js rename to Canopy[BP]/scripts/src/classes/debugdisplay/ViewDirectionVector.js 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 100% rename from Canopy [BP]/scripts/src/commands/biomeedges.js rename to Canopy[BP]/scripts/src/commands/biomeedges.js diff --git a/Canopy [BP]/scripts/src/commands/butcher.js b/Canopy[BP]/scripts/src/commands/butcher.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/butcher.js rename to Canopy[BP]/scripts/src/commands/butcher.js diff --git a/Canopy [BP]/scripts/src/commands/camera.js b/Canopy[BP]/scripts/src/commands/camera.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/camera.js rename to Canopy[BP]/scripts/src/commands/camera.js diff --git a/Canopy [BP]/scripts/src/commands/canopy.js b/Canopy[BP]/scripts/src/commands/canopy.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/canopy.js rename to Canopy[BP]/scripts/src/commands/canopy.js diff --git a/Canopy [BP]/scripts/src/commands/changedimension.js b/Canopy[BP]/scripts/src/commands/changedimension.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/changedimension.js rename to Canopy[BP]/scripts/src/commands/changedimension.js diff --git a/Canopy [BP]/scripts/src/commands/claimprojectiles.js b/Canopy[BP]/scripts/src/commands/claimprojectiles.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/claimprojectiles.js rename to Canopy[BP]/scripts/src/commands/claimprojectiles.js diff --git a/Canopy [BP]/scripts/src/commands/cleanup.js b/Canopy[BP]/scripts/src/commands/cleanup.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/cleanup.js rename to Canopy[BP]/scripts/src/commands/cleanup.js diff --git a/Canopy [BP]/scripts/src/commands/counter.js b/Canopy[BP]/scripts/src/commands/counter.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/counter.js rename to Canopy[BP]/scripts/src/commands/counter.js diff --git a/Canopy [BP]/scripts/src/commands/data.js b/Canopy[BP]/scripts/src/commands/data.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/data.js rename to Canopy[BP]/scripts/src/commands/data.js diff --git a/Canopy [BP]/scripts/src/commands/debugentity.js b/Canopy[BP]/scripts/src/commands/debugentity.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/debugentity.js rename to Canopy[BP]/scripts/src/commands/debugentity.js diff --git a/Canopy [BP]/scripts/src/commands/distance.js b/Canopy[BP]/scripts/src/commands/distance.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/distance.js rename to Canopy[BP]/scripts/src/commands/distance.js diff --git a/Canopy [BP]/scripts/src/commands/entitydensity.js b/Canopy[BP]/scripts/src/commands/entitydensity.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/entitydensity.js rename to Canopy[BP]/scripts/src/commands/entitydensity.js 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 100% rename from Canopy [BP]/scripts/src/commands/generator.js rename to Canopy[BP]/scripts/src/commands/generator.js diff --git a/Canopy [BP]/scripts/src/commands/health.js b/Canopy[BP]/scripts/src/commands/health.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/health.js rename to Canopy[BP]/scripts/src/commands/health.js diff --git a/Canopy [BP]/scripts/src/commands/help.js b/Canopy[BP]/scripts/src/commands/help.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/help.js rename to Canopy[BP]/scripts/src/commands/help.js diff --git a/Canopy [BP]/scripts/src/commands/hss.js b/Canopy[BP]/scripts/src/commands/hss.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/hss.js rename to Canopy[BP]/scripts/src/commands/hss.js diff --git a/Canopy [BP]/scripts/src/commands/info.js b/Canopy[BP]/scripts/src/commands/info.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/info.js rename to Canopy[BP]/scripts/src/commands/info.js diff --git a/Canopy [BP]/scripts/src/commands/jump.js b/Canopy[BP]/scripts/src/commands/jump.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/jump.js rename to Canopy[BP]/scripts/src/commands/jump.js diff --git a/Canopy [BP]/scripts/src/commands/lifetimequery.js b/Canopy[BP]/scripts/src/commands/lifetimequery.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/lifetimequery.js rename to Canopy[BP]/scripts/src/commands/lifetimequery.js diff --git a/Canopy [BP]/scripts/src/commands/lifetimequeryitem.js b/Canopy[BP]/scripts/src/commands/lifetimequeryitem.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/lifetimequeryitem.js rename to Canopy[BP]/scripts/src/commands/lifetimequeryitem.js diff --git a/Canopy [BP]/scripts/src/commands/lifetimetracking.js b/Canopy[BP]/scripts/src/commands/lifetimetracking.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/lifetimetracking.js rename to Canopy[BP]/scripts/src/commands/lifetimetracking.js diff --git a/Canopy [BP]/scripts/src/commands/log.js b/Canopy[BP]/scripts/src/commands/log.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/log.js rename to Canopy[BP]/scripts/src/commands/log.js diff --git a/Canopy [BP]/scripts/src/commands/loop.js b/Canopy[BP]/scripts/src/commands/loop.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/loop.js rename to Canopy[BP]/scripts/src/commands/loop.js diff --git a/Canopy [BP]/scripts/src/commands/peek.js b/Canopy[BP]/scripts/src/commands/peek.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/peek.js rename to Canopy[BP]/scripts/src/commands/peek.js diff --git a/Canopy [BP]/scripts/src/commands/pos.js b/Canopy[BP]/scripts/src/commands/pos.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/pos.js rename to Canopy[BP]/scripts/src/commands/pos.js diff --git a/Canopy [BP]/scripts/src/commands/retest.js b/Canopy[BP]/scripts/src/commands/retest.js similarity index 100% rename from Canopy [BP]/scripts/src/commands/retest.js rename to Canopy[BP]/scripts/src/commands/retest.js 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 100% rename from Canopy [BP]/scripts/src/commands/spawn.js rename to Canopy[BP]/scripts/src/commands/spawn.js 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 100% rename from Canopy [BP]/scripts/src/commands/trackevent.js rename to Canopy[BP]/scripts/src/commands/trackevent.js 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 100% rename from Canopy [BP]/scripts/src/commands/warp.js rename to Canopy[BP]/scripts/src/commands/warp.js 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 100% rename from Canopy [BP]/scripts/src/events/PlayerChangeSubChunkEvent.js rename to Canopy[BP]/scripts/src/events/PlayerChangeSubChunkEvent.js 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 100% rename from Canopy [BP]/scripts/src/rules/allowBubbleColumnPlacement.js rename to Canopy[BP]/scripts/src/rules/allowBubbleColumnPlacement.js diff --git a/Canopy [BP]/scripts/src/rules/allowPeekInventory.js b/Canopy[BP]/scripts/src/rules/allowPeekInventory.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/allowPeekInventory.js rename to Canopy[BP]/scripts/src/rules/allowPeekInventory.js diff --git a/Canopy [BP]/scripts/src/rules/armorStandRespawning.js b/Canopy[BP]/scripts/src/rules/armorStandRespawning.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/armorStandRespawning.js rename to Canopy[BP]/scripts/src/rules/armorStandRespawning.js diff --git a/Canopy [BP]/scripts/src/rules/autoItemPickup.js b/Canopy[BP]/scripts/src/rules/autoItemPickup.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/autoItemPickup.js rename to Canopy[BP]/scripts/src/rules/autoItemPickup.js diff --git a/Canopy [BP]/scripts/src/rules/cauldronConcreteConversion.js b/Canopy[BP]/scripts/src/rules/cauldronConcreteConversion.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/cauldronConcreteConversion.js rename to Canopy[BP]/scripts/src/rules/cauldronConcreteConversion.js diff --git a/Canopy [BP]/scripts/src/rules/chunkBorders.js b/Canopy[BP]/scripts/src/rules/chunkBorders.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/chunkBorders.js rename to Canopy[BP]/scripts/src/rules/chunkBorders.js diff --git a/Canopy [BP]/scripts/src/rules/collisionBoxes.js b/Canopy[BP]/scripts/src/rules/collisionBoxes.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/collisionBoxes.js rename to Canopy[BP]/scripts/src/rules/collisionBoxes.js diff --git a/Canopy [BP]/scripts/src/rules/creativeHotbarSwitching.js b/Canopy[BP]/scripts/src/rules/creativeHotbarSwitching.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/creativeHotbarSwitching.js rename to Canopy[BP]/scripts/src/rules/creativeHotbarSwitching.js diff --git a/Canopy [BP]/scripts/src/rules/creativeInstantTame.js b/Canopy[BP]/scripts/src/rules/creativeInstantTame.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/creativeInstantTame.js rename to Canopy[BP]/scripts/src/rules/creativeInstantTame.js diff --git a/Canopy [BP]/scripts/src/rules/creativeNetherWaterPlacement.js b/Canopy[BP]/scripts/src/rules/creativeNetherWaterPlacement.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/creativeNetherWaterPlacement.js rename to Canopy[BP]/scripts/src/rules/creativeNetherWaterPlacement.js diff --git a/Canopy [BP]/scripts/src/rules/creativeNoTileDrops.js b/Canopy[BP]/scripts/src/rules/creativeNoTileDrops.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/creativeNoTileDrops.js rename to Canopy[BP]/scripts/src/rules/creativeNoTileDrops.js diff --git a/Canopy [BP]/scripts/src/rules/creativeOneHitKill.js b/Canopy[BP]/scripts/src/rules/creativeOneHitKill.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/creativeOneHitKill.js rename to Canopy[BP]/scripts/src/rules/creativeOneHitKill.js diff --git a/Canopy [BP]/scripts/src/rules/dupeTnt.js b/Canopy[BP]/scripts/src/rules/dupeTnt.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/dupeTnt.js rename to Canopy[BP]/scripts/src/rules/dupeTnt.js diff --git a/Canopy [BP]/scripts/src/rules/durabilityNotifier.js b/Canopy[BP]/scripts/src/rules/durabilityNotifier.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/durabilityNotifier.js rename to Canopy[BP]/scripts/src/rules/durabilityNotifier.js diff --git a/Canopy [BP]/scripts/src/rules/durabilitySwap.js b/Canopy[BP]/scripts/src/rules/durabilitySwap.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/durabilitySwap.js rename to Canopy[BP]/scripts/src/rules/durabilitySwap.js diff --git a/Canopy [BP]/scripts/src/rules/echoShardsEnableShriekers.js b/Canopy[BP]/scripts/src/rules/echoShardsEnableShriekers.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/echoShardsEnableShriekers.js rename to Canopy[BP]/scripts/src/rules/echoShardsEnableShriekers.js diff --git a/Canopy [BP]/scripts/src/rules/enderPearlChunkLoading.js b/Canopy[BP]/scripts/src/rules/enderPearlChunkLoading.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/enderPearlChunkLoading.js rename to Canopy[BP]/scripts/src/rules/enderPearlChunkLoading.js diff --git a/Canopy [BP]/scripts/src/rules/entityInstantDeath.js b/Canopy[BP]/scripts/src/rules/entityInstantDeath.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/entityInstantDeath.js rename to Canopy[BP]/scripts/src/rules/entityInstantDeath.js diff --git a/Canopy [BP]/scripts/src/rules/entitySeparation.js b/Canopy[BP]/scripts/src/rules/entitySeparation.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/entitySeparation.js rename to Canopy[BP]/scripts/src/rules/entitySeparation.js diff --git a/Canopy [BP]/scripts/src/rules/explosionChainReactionOnly.js b/Canopy[BP]/scripts/src/rules/explosionChainReactionOnly.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/explosionChainReactionOnly.js rename to Canopy[BP]/scripts/src/rules/explosionChainReactionOnly.js diff --git a/Canopy [BP]/scripts/src/rules/explosionNoBlockDamage.js b/Canopy[BP]/scripts/src/rules/explosionNoBlockDamage.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/explosionNoBlockDamage.js rename to Canopy[BP]/scripts/src/rules/explosionNoBlockDamage.js diff --git a/Canopy [BP]/scripts/src/rules/explosionOff.js b/Canopy[BP]/scripts/src/rules/explosionOff.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/explosionOff.js rename to Canopy[BP]/scripts/src/rules/explosionOff.js diff --git a/Canopy [BP]/scripts/src/rules/flippinArrows.js b/Canopy[BP]/scripts/src/rules/flippinArrows.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/flippinArrows.js rename to Canopy[BP]/scripts/src/rules/flippinArrows.js diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Biome.js b/Canopy[BP]/scripts/src/rules/infodisplay/Biome.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/infodisplay/Biome.js rename to Canopy[BP]/scripts/src/rules/infodisplay/Biome.js diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/BlockStates.js b/Canopy[BP]/scripts/src/rules/infodisplay/BlockStates.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/infodisplay/BlockStates.js rename to Canopy[BP]/scripts/src/rules/infodisplay/BlockStates.js diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/CardinalFacing.js b/Canopy[BP]/scripts/src/rules/infodisplay/CardinalFacing.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/infodisplay/CardinalFacing.js rename to Canopy[BP]/scripts/src/rules/infodisplay/CardinalFacing.js diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/ChunkCoords.js b/Canopy[BP]/scripts/src/rules/infodisplay/ChunkCoords.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/infodisplay/ChunkCoords.js rename to Canopy[BP]/scripts/src/rules/infodisplay/ChunkCoords.js diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Coords.js b/Canopy[BP]/scripts/src/rules/infodisplay/Coords.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/infodisplay/Coords.js rename to Canopy[BP]/scripts/src/rules/infodisplay/Coords.js diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Dimension.js b/Canopy[BP]/scripts/src/rules/infodisplay/Dimension.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/infodisplay/Dimension.js rename to Canopy[BP]/scripts/src/rules/infodisplay/Dimension.js diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Entities.js b/Canopy[BP]/scripts/src/rules/infodisplay/Entities.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/infodisplay/Entities.js rename to Canopy[BP]/scripts/src/rules/infodisplay/Entities.js diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/EventTrackers.js b/Canopy[BP]/scripts/src/rules/infodisplay/EventTrackers.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/infodisplay/EventTrackers.js rename to Canopy[BP]/scripts/src/rules/infodisplay/EventTrackers.js diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Facing.js b/Canopy[BP]/scripts/src/rules/infodisplay/Facing.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/infodisplay/Facing.js rename to Canopy[BP]/scripts/src/rules/infodisplay/Facing.js diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/HopperCounterCounts.js b/Canopy[BP]/scripts/src/rules/infodisplay/HopperCounterCounts.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/infodisplay/HopperCounterCounts.js rename to Canopy[BP]/scripts/src/rules/infodisplay/HopperCounterCounts.js diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplay.js b/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplay.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplay.js rename to Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplay.js diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplayElement.js b/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayElement.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/infodisplay/InfoDisplayElement.js rename to Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayElement.js diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Light.js b/Canopy[BP]/scripts/src/rules/infodisplay/Light.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/infodisplay/Light.js rename to Canopy[BP]/scripts/src/rules/infodisplay/Light.js diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/LiquidStates.js b/Canopy[BP]/scripts/src/rules/infodisplay/LiquidStates.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/infodisplay/LiquidStates.js rename to Canopy[BP]/scripts/src/rules/infodisplay/LiquidStates.js diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/LiquidTarget.js b/Canopy[BP]/scripts/src/rules/infodisplay/LiquidTarget.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/infodisplay/LiquidTarget.js rename to Canopy[BP]/scripts/src/rules/infodisplay/LiquidTarget.js diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/MoonPhase.js b/Canopy[BP]/scripts/src/rules/infodisplay/MoonPhase.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/infodisplay/MoonPhase.js rename to Canopy[BP]/scripts/src/rules/infodisplay/MoonPhase.js diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/PeekInventory.js b/Canopy[BP]/scripts/src/rules/infodisplay/PeekInventory.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/infodisplay/PeekInventory.js rename to Canopy[BP]/scripts/src/rules/infodisplay/PeekInventory.js diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/SessionTime.js b/Canopy[BP]/scripts/src/rules/infodisplay/SessionTime.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/infodisplay/SessionTime.js rename to Canopy[BP]/scripts/src/rules/infodisplay/SessionTime.js diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/SignalStrength.js b/Canopy[BP]/scripts/src/rules/infodisplay/SignalStrength.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/infodisplay/SignalStrength.js rename to Canopy[BP]/scripts/src/rules/infodisplay/SignalStrength.js diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/SimulationMap.js b/Canopy[BP]/scripts/src/rules/infodisplay/SimulationMap.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/infodisplay/SimulationMap.js rename to Canopy[BP]/scripts/src/rules/infodisplay/SimulationMap.js diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/SlimeChunk.js b/Canopy[BP]/scripts/src/rules/infodisplay/SlimeChunk.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/infodisplay/SlimeChunk.js rename to Canopy[BP]/scripts/src/rules/infodisplay/SlimeChunk.js diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Speed.js b/Canopy[BP]/scripts/src/rules/infodisplay/Speed.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/infodisplay/Speed.js rename to Canopy[BP]/scripts/src/rules/infodisplay/Speed.js diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Structures.js b/Canopy[BP]/scripts/src/rules/infodisplay/Structures.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/infodisplay/Structures.js rename to Canopy[BP]/scripts/src/rules/infodisplay/Structures.js diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/TPS.js b/Canopy[BP]/scripts/src/rules/infodisplay/TPS.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/infodisplay/TPS.js rename to Canopy[BP]/scripts/src/rules/infodisplay/TPS.js diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Target.js b/Canopy[BP]/scripts/src/rules/infodisplay/Target.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/infodisplay/Target.js rename to Canopy[BP]/scripts/src/rules/infodisplay/Target.js diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/TimeOfDay.js b/Canopy[BP]/scripts/src/rules/infodisplay/TimeOfDay.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/infodisplay/TimeOfDay.js rename to Canopy[BP]/scripts/src/rules/infodisplay/TimeOfDay.js diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Velocity.js b/Canopy[BP]/scripts/src/rules/infodisplay/Velocity.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/infodisplay/Velocity.js rename to Canopy[BP]/scripts/src/rules/infodisplay/Velocity.js diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/Weather.js b/Canopy[BP]/scripts/src/rules/infodisplay/Weather.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/infodisplay/Weather.js rename to Canopy[BP]/scripts/src/rules/infodisplay/Weather.js diff --git a/Canopy [BP]/scripts/src/rules/infodisplay/WorldDay.js b/Canopy[BP]/scripts/src/rules/infodisplay/WorldDay.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/infodisplay/WorldDay.js rename to Canopy[BP]/scripts/src/rules/infodisplay/WorldDay.js diff --git a/Canopy [BP]/scripts/src/rules/instaminableDeepslate.js b/Canopy[BP]/scripts/src/rules/instaminableDeepslate.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/instaminableDeepslate.js rename to Canopy[BP]/scripts/src/rules/instaminableDeepslate.js diff --git a/Canopy [BP]/scripts/src/rules/instaminableEndstone.js b/Canopy[BP]/scripts/src/rules/instaminableEndstone.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/instaminableEndstone.js rename to Canopy[BP]/scripts/src/rules/instaminableEndstone.js diff --git a/Canopy [BP]/scripts/src/rules/minecartChunkLoading.js b/Canopy[BP]/scripts/src/rules/minecartChunkLoading.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/minecartChunkLoading.js rename to Canopy[BP]/scripts/src/rules/minecartChunkLoading.js 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 100% rename from Canopy [BP]/scripts/src/rules/pistonBedrockBreaking.js rename to Canopy[BP]/scripts/src/rules/pistonBedrockBreaking.js diff --git a/Canopy [BP]/scripts/src/rules/playerSit.js b/Canopy[BP]/scripts/src/rules/playerSit.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/playerSit.js rename to Canopy[BP]/scripts/src/rules/playerSit.js diff --git a/Canopy [BP]/scripts/src/rules/potionBoostedBreeding.js b/Canopy[BP]/scripts/src/rules/potionBoostedBreeding.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/potionBoostedBreeding.js rename to Canopy[BP]/scripts/src/rules/potionBoostedBreeding.js diff --git a/Canopy [BP]/scripts/src/rules/quickFillContainer.js b/Canopy[BP]/scripts/src/rules/quickFillContainer.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/quickFillContainer.js rename to Canopy[BP]/scripts/src/rules/quickFillContainer.js diff --git a/Canopy [BP]/scripts/src/rules/refillHand.js b/Canopy[BP]/scripts/src/rules/refillHand.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/refillHand.js rename to Canopy[BP]/scripts/src/rules/refillHand.js diff --git a/Canopy [BP]/scripts/src/rules/renewableElytraDropChance.js b/Canopy[BP]/scripts/src/rules/renewableElytraDropChance.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/renewableElytraDropChance.js rename to Canopy[BP]/scripts/src/rules/renewableElytraDropChance.js diff --git a/Canopy [BP]/scripts/src/rules/renewableSponge.js b/Canopy[BP]/scripts/src/rules/renewableSponge.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/renewableSponge.js rename to Canopy[BP]/scripts/src/rules/renewableSponge.js diff --git a/Canopy [BP]/scripts/src/rules/serverSideCollisionBoxes.js b/Canopy[BP]/scripts/src/rules/serverSideCollisionBoxes.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/serverSideCollisionBoxes.js rename to Canopy[BP]/scripts/src/rules/serverSideCollisionBoxes.js diff --git a/Canopy [BP]/scripts/src/rules/spawnEggSpawnWithMinecart.js b/Canopy[BP]/scripts/src/rules/spawnEggSpawnWithMinecart.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/spawnEggSpawnWithMinecart.js rename to Canopy[BP]/scripts/src/rules/spawnEggSpawnWithMinecart.js diff --git a/Canopy [BP]/scripts/src/rules/tntFuse.js b/Canopy[BP]/scripts/src/rules/tntFuse.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/tntFuse.js rename to Canopy[BP]/scripts/src/rules/tntFuse.js diff --git a/Canopy [BP]/scripts/src/rules/tntPrimeMomentum.js b/Canopy[BP]/scripts/src/rules/tntPrimeMomentum.js similarity index 100% rename from Canopy [BP]/scripts/src/rules/tntPrimeMomentum.js rename to Canopy[BP]/scripts/src/rules/tntPrimeMomentum.js 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 100% rename from Canopy [RP]/manifest.json rename to Canopy[RP]/manifest.json 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 100% rename from Canopy [RP]/texts/en_US.lang rename to Canopy[RP]/texts/en_US.lang 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 100% rename from Canopy [RP]/texts/ja_JP.lang rename to Canopy[RP]/texts/ja_JP.lang 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/__tests__/BP/scripts/include/data.test.js b/__tests__/BP/scripts/include/data.test.js index 8b17309..da30daa 100644 --- a/__tests__/BP/scripts/include/data.test.js +++ b/__tests__/BP/scripts/include/data.test.js @@ -1,7 +1,7 @@ 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'; const bedrockSamplesRawUrl = `https://raw.githubusercontent.com/Mojang/bedrock-samples/refs/tags/v${MC_VERSION}/`; diff --git a/__tests__/BP/scripts/include/utils.test.js b/__tests__/BP/scripts/include/utils.test.js index fd41d8c..41b982c 100644 --- a/__tests__/BP/scripts/include/utils.test.js +++ b/__tests__/BP/scripts/include/utils.test.js @@ -3,7 +3,7 @@ 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'; describe('calcDistance()', () => { diff --git a/__tests__/BP/scripts/lib/canopy/Canopy.test.js b/__tests__/BP/scripts/lib/canopy/Canopy.test.js index e0d643c..0426579 100644 --- a/__tests__/BP/scripts/lib/canopy/Canopy.test.js +++ b/__tests__/BP/scripts/lib/canopy/Canopy.test.js @@ -1,5 +1,5 @@ import { describe, it, expect } from "vitest"; -import * as Canopy from "../../../../../Canopy [BP]/scripts/lib/canopy/Canopy"; +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 eae3ac0..f0f22fe 100644 --- a/__tests__/BP/scripts/lib/canopy/Extension.test.js +++ b/__tests__/BP/scripts/lib/canopy/Extension.test.js @@ -1,10 +1,10 @@ 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'; +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 = { diff --git a/__tests__/BP/scripts/lib/canopy/Extensions.test.js b/__tests__/BP/scripts/lib/canopy/Extensions.test.js index 81b698f..7a49ea5 100644 --- a/__tests__/BP/scripts/lib/canopy/Extensions.test.js +++ b/__tests__/BP/scripts/lib/canopy/Extensions.test.js @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; -vi.mock('../../../../../Canopy [BP]/scripts/lib/MCBE-IPC/ipc.js', async (importOriginal) => { +vi.mock('../../../../../Canopy[BP]/scripts/lib/MCBE-IPC/ipc.js', async (importOriginal) => { const actual = await importOriginal(); return { ...actual, @@ -14,9 +14,9 @@ vi.mock('../../../../../Canopy [BP]/scripts/lib/MCBE-IPC/ipc.js', async (importO }; }); -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"; +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", () => { beforeEach(() => { diff --git a/__tests__/BP/scripts/lib/canopy/commands/ArgumentParser.test.js b/__tests__/BP/scripts/lib/canopy/commands/ArgumentParser.test.js index ceda434..5598876 100644 --- a/__tests__/BP/scripts/lib/canopy/commands/ArgumentParser.test.js +++ b/__tests__/BP/scripts/lib/canopy/commands/ArgumentParser.test.js @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { ArgumentParser } from '../../../../../../Canopy [BP]/scripts/lib/canopy/commands/ArgumentParser.js'; +import { ArgumentParser } from '../../../../../../Canopy[BP]/scripts/lib/canopy/commands/ArgumentParser.js'; describe('ArgumentParser', () => { describe('parseCommandString', () => { diff --git a/__tests__/BP/scripts/lib/canopy/commands/BlockCommandOrigin.test.js b/__tests__/BP/scripts/lib/canopy/commands/BlockCommandOrigin.test.js index dfb7516..4d35bfd 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 7217b46..a847717 100644 --- a/__tests__/BP/scripts/lib/canopy/commands/Command.test.js +++ b/__tests__/BP/scripts/lib/canopy/commands/Command.test.js @@ -1,10 +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"; +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 43ade31..9e51b12 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 c4df877..432ac22 100644 --- a/__tests__/BP/scripts/lib/canopy/commands/Commands.test.js +++ b/__tests__/BP/scripts/lib/canopy/commands/Commands.test.js @@ -1,7 +1,7 @@ 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"; +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) => { diff --git a/__tests__/BP/scripts/lib/canopy/commands/EntityCommandOrigin.test.js b/__tests__/BP/scripts/lib/canopy/commands/EntityCommandOrigin.test.js index 81241db..10eeebf 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 73ad48c..4e566ae 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 f96880d..cdc8094 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 d4603bd..26da660 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 = { diff --git a/__tests__/BP/scripts/lib/canopy/help/CommandHelpEntry.test.js b/__tests__/BP/scripts/lib/canopy/help/CommandHelpEntry.test.js index 317b17f..cb4ea44 100644 --- a/__tests__/BP/scripts/lib/canopy/help/CommandHelpEntry.test.js +++ b/__tests__/BP/scripts/lib/canopy/help/CommandHelpEntry.test.js @@ -1,6 +1,6 @@ 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"; +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 0305ce0..9449880 100644 --- a/__tests__/BP/scripts/lib/canopy/help/CommandHelpPage.test.js +++ b/__tests__/BP/scripts/lib/canopy/help/CommandHelpPage.test.js @@ -1,8 +1,8 @@ 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"; +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 e67bd5a..5af6185 100644 --- a/__tests__/BP/scripts/lib/canopy/help/HelpBook.test.js +++ b/__tests__/BP/scripts/lib/canopy/help/HelpBook.test.js @@ -1,14 +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"; +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 91e7b7b..9c02674 100644 --- a/__tests__/BP/scripts/lib/canopy/help/HelpEntry.test.js +++ b/__tests__/BP/scripts/lib/canopy/help/HelpEntry.test.js @@ -1,5 +1,5 @@ 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 8d18c20..de45dfc 100644 --- a/__tests__/BP/scripts/lib/canopy/help/HelpPage.test.js +++ b/__tests__/BP/scripts/lib/canopy/help/HelpPage.test.js @@ -1,5 +1,5 @@ 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 4612d86..182d7d8 100644 --- a/__tests__/BP/scripts/lib/canopy/help/InfoDisplayRuleHelpEntry.test.js +++ b/__tests__/BP/scripts/lib/canopy/help/InfoDisplayRuleHelpEntry.test.js @@ -1,7 +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'; +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 c43cdcf..561e4da 100644 --- a/__tests__/BP/scripts/lib/canopy/help/InfoDisplayRuleHelpPage.test.js +++ b/__tests__/BP/scripts/lib/canopy/help/InfoDisplayRuleHelpPage.test.js @@ -1,11 +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'; +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 641f4ff..b2b10f0 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', () => { diff --git a/__tests__/BP/scripts/lib/canopy/help/RuleHelpPage.test.js b/__tests__/BP/scripts/lib/canopy/help/RuleHelpPage.test.js index 4ff092c..7baee2e 100644 --- a/__tests__/BP/scripts/lib/canopy/help/RuleHelpPage.test.js +++ b/__tests__/BP/scripts/lib/canopy/help/RuleHelpPage.test.js @@ -1,11 +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"; +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 f1ba931..2364f16 100644 --- a/__tests__/BP/scripts/lib/canopy/rules/AbilityRule.test.js +++ b/__tests__/BP/scripts/lib/canopy/rules/AbilityRule.test.js @@ -1,6 +1,6 @@ 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"; +import { AbilityRule } from "../../../../../../Canopy[BP]/scripts/lib/canopy/rules/AbilityRule"; +import { Rules } from "../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rules"; vi.mock('@minecraft/server', async (importOriginal) => { const original = await importOriginal(); diff --git a/__tests__/BP/scripts/lib/canopy/rules/BooleanRule.test.js b/__tests__/BP/scripts/lib/canopy/rules/BooleanRule.test.js index a95fe78..3f4488a 100644 --- a/__tests__/BP/scripts/lib/canopy/rules/BooleanRule.test.js +++ b/__tests__/BP/scripts/lib/canopy/rules/BooleanRule.test.js @@ -1,10 +1,10 @@ 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', async (importOriginal) => { diff --git a/__tests__/BP/scripts/lib/canopy/rules/FloatRule.test.js b/__tests__/BP/scripts/lib/canopy/rules/FloatRule.test.js index 81ef926..0b05f6f 100644 --- a/__tests__/BP/scripts/lib/canopy/rules/FloatRule.test.js +++ b/__tests__/BP/scripts/lib/canopy/rules/FloatRule.test.js @@ -1,6 +1,6 @@ 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'; +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; diff --git a/__tests__/BP/scripts/lib/canopy/rules/GlobalRule.test.js b/__tests__/BP/scripts/lib/canopy/rules/GlobalRule.test.js index 2b742a6..95fd09d 100644 --- a/__tests__/BP/scripts/lib/canopy/rules/GlobalRule.test.js +++ b/__tests__/BP/scripts/lib/canopy/rules/GlobalRule.test.js @@ -1,6 +1,6 @@ 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', async (importOriginal) => { const original = await importOriginal(); diff --git a/__tests__/BP/scripts/lib/canopy/rules/InfoDisplayRule.test.js b/__tests__/BP/scripts/lib/canopy/rules/InfoDisplayRule.test.js index 4985b4c..a46f09e 100644 --- a/__tests__/BP/scripts/lib/canopy/rules/InfoDisplayRule.test.js +++ b/__tests__/BP/scripts/lib/canopy/rules/InfoDisplayRule.test.js @@ -1,7 +1,7 @@ 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'; +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(); diff --git a/__tests__/BP/scripts/lib/canopy/rules/IntegerRule.test.js b/__tests__/BP/scripts/lib/canopy/rules/IntegerRule.test.js index ef9c178..3a4d00b 100644 --- a/__tests__/BP/scripts/lib/canopy/rules/IntegerRule.test.js +++ b/__tests__/BP/scripts/lib/canopy/rules/IntegerRule.test.js @@ -1,6 +1,6 @@ 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'; +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; diff --git a/__tests__/BP/scripts/lib/canopy/rules/Rule.test.js b/__tests__/BP/scripts/lib/canopy/rules/Rule.test.js index 04097d4..1cd92b1 100644 --- a/__tests__/BP/scripts/lib/canopy/rules/Rule.test.js +++ b/__tests__/BP/scripts/lib/canopy/rules/Rule.test.js @@ -1,8 +1,8 @@ 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'; +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 {} diff --git a/__tests__/BP/scripts/lib/canopy/rules/Rules.test.js b/__tests__/BP/scripts/lib/canopy/rules/Rules.test.js index d7c2499..5641dbe 100644 --- a/__tests__/BP/scripts/lib/canopy/rules/Rules.test.js +++ b/__tests__/BP/scripts/lib/canopy/rules/Rules.test.js @@ -1,7 +1,7 @@ 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"; +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(); diff --git a/__tests__/BP/scripts/src/classes/EntityMovementLog.test.js b/__tests__/BP/scripts/src/classes/EntityMovementLog.test.js index 63be1ac..259da85 100644 --- a/__tests__/BP/scripts/src/classes/EntityMovementLog.test.js +++ b/__tests__/BP/scripts/src/classes/EntityMovementLog.test.js @@ -1,5 +1,5 @@ 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', async (importOriginal) => { const original = await importOriginal(); diff --git a/__tests__/BP/scripts/src/classes/EntityTntLog.test.js b/__tests__/BP/scripts/src/classes/EntityTntLog.test.js index dc31cf3..adf4405 100644 --- a/__tests__/BP/scripts/src/classes/EntityTntLog.test.js +++ b/__tests__/BP/scripts/src/classes/EntityTntLog.test.js @@ -1,5 +1,5 @@ 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', async (importOriginal) => { const original = await importOriginal(); diff --git a/__tests__/BP/scripts/src/classes/InventoryUI.test.js b/__tests__/BP/scripts/src/classes/InventoryUI.test.js index 222dde3..b989d93 100644 --- a/__tests__/BP/scripts/src/classes/InventoryUI.test.js +++ b/__tests__/BP/scripts/src/classes/InventoryUI.test.js @@ -1,6 +1,6 @@ -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', async (importOriginal) => { const original = await importOriginal(); diff --git a/__tests__/BP/scripts/src/classes/Profiler.test.js b/__tests__/BP/scripts/src/classes/Profiler.test.js index 94a13ce..276169d 100644 --- a/__tests__/BP/scripts/src/classes/Profiler.test.js +++ b/__tests__/BP/scripts/src/classes/Profiler.test.js @@ -1,5 +1,5 @@ import { describe, it, expect, vi } from "vitest"; -import { Profiler } from "../../../../../Canopy [BP]/scripts/src/classes/Profiler"; +import { Profiler } from "../../../../../Canopy[BP]/scripts/src/classes/Profiler"; vi.mock('@minecraft/server', async (importOriginal) => { const original = await importOriginal(); diff --git a/__tests__/BP/scripts/src/classes/TNTFuse.test.js b/__tests__/BP/scripts/src/classes/TNTFuse.test.js index 633567f..c14f1d6 100644 --- a/__tests__/BP/scripts/src/classes/TNTFuse.test.js +++ b/__tests__/BP/scripts/src/classes/TNTFuse.test.js @@ -1,5 +1,5 @@ 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', async (importOriginal) => { const original = await importOriginal(); diff --git a/__tests__/BP/scripts/src/commands/log.test.js b/__tests__/BP/scripts/src/commands/log.test.js index 2aa6c52..8684dde 100644 --- a/__tests__/BP/scripts/src/commands/log.test.js +++ b/__tests__/BP/scripts/src/commands/log.test.js @@ -1,7 +1,7 @@ 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', async (importOriginal) => { const original = await importOriginal(); diff --git a/__tests__/BP/scripts/src/commands/velocity.test.js b/__tests__/BP/scripts/src/commands/velocity.test.js index 6c4355d..b628d36 100644 --- a/__tests__/BP/scripts/src/commands/velocity.test.js +++ b/__tests__/BP/scripts/src/commands/velocity.test.js @@ -1,7 +1,7 @@ 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', async (importOriginal) => { const original = await importOriginal(); diff --git a/__tests__/BP/scripts/src/events/PlayerChangeSubChunkEvent.test.js b/__tests__/BP/scripts/src/events/PlayerChangeSubChunkEvent.test.js index fc0f9b6..9a157ea 100644 --- a/__tests__/BP/scripts/src/events/PlayerChangeSubChunkEvent.test.js +++ b/__tests__/BP/scripts/src/events/PlayerChangeSubChunkEvent.test.js @@ -1,4 +1,4 @@ -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' }; diff --git a/__tests__/BP/scripts/src/events/PlayerStartSneakEvent.test.js b/__tests__/BP/scripts/src/events/PlayerStartSneakEvent.test.js index 37bb542..ad0d90c 100644 --- a/__tests__/BP/scripts/src/events/PlayerStartSneakEvent.test.js +++ b/__tests__/BP/scripts/src/events/PlayerStartSneakEvent.test.js @@ -1,4 +1,4 @@ -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 })) }; diff --git a/__tests__/BP/scripts/src/events/SpawnEggSpawnEntityEvent.test.js b/__tests__/BP/scripts/src/events/SpawnEggSpawnEntityEvent.test.js index 1469f9c..912e63c 100644 --- a/__tests__/BP/scripts/src/events/SpawnEggSpawnEntityEvent.test.js +++ b/__tests__/BP/scripts/src/events/SpawnEggSpawnEntityEvent.test.js @@ -1,4 +1,4 @@ -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', async (importOriginal) => { diff --git a/__tests__/BP/scripts/src/rules/allowBubbleColumnPlacement.test.js b/__tests__/BP/scripts/src/rules/allowBubbleColumnPlacement.test.js index 8a2f307..7371fc5 100644 --- a/__tests__/BP/scripts/src/rules/allowBubbleColumnPlacement.test.js +++ b/__tests__/BP/scripts/src/rules/allowBubbleColumnPlacement.test.js @@ -1,4 +1,4 @@ -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", async (importOriginal) => { diff --git a/__tests__/BP/scripts/src/rules/allowPeekInventory.test.js b/__tests__/BP/scripts/src/rules/allowPeekInventory.test.js index f3661e1..88ae831 100644 --- a/__tests__/BP/scripts/src/rules/allowPeekInventory.test.js +++ b/__tests__/BP/scripts/src/rules/allowPeekInventory.test.js @@ -1,6 +1,6 @@ -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', async (importOriginal) => { const original = await importOriginal(); diff --git a/__tests__/BP/scripts/src/rules/creativeNetherWaterPlacement.test.js b/__tests__/BP/scripts/src/rules/creativeNetherWaterPlacement.test.js index 660f7b2..a234721 100644 --- a/__tests__/BP/scripts/src/rules/creativeNetherWaterPlacement.test.js +++ b/__tests__/BP/scripts/src/rules/creativeNetherWaterPlacement.test.js @@ -1,4 +1,4 @@ -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', async (importOriginal) => { diff --git a/__tests__/BP/scripts/src/rules/dupeTnt.test.js b/__tests__/BP/scripts/src/rules/dupeTnt.test.js index 67d35fe..dd2b7e0 100644 --- a/__tests__/BP/scripts/src/rules/dupeTnt.test.js +++ b/__tests__/BP/scripts/src/rules/dupeTnt.test.js @@ -1,7 +1,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { spawnedEntitiesThisTick, handleTntDuplication } from '../../../../../Canopy [BP]/scripts/src/rules/dupeTnt'; +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'; +import { BooleanRule, Rules } from '../../../../../Canopy[BP]/scripts/lib/canopy/Canopy'; vi.mock('@minecraft/server', async (importOriginal) => { const original = await importOriginal(); @@ -21,7 +21,7 @@ vi.mock('@minecraft/server', async (importOriginal) => { }; }); -vi.mock('../../../../../Canopy [BP]/scripts/lib/canopy/Canopy', () => ({ +vi.mock('../../../../../Canopy[BP]/scripts/lib/canopy/Canopy', () => ({ BooleanRule: vi.fn(), Rules: { getNativeValue: vi.fn() diff --git a/__tests__/BP/scripts/src/rules/echoShardsEnableShriekers.test.js b/__tests__/BP/scripts/src/rules/echoShardsEnableShriekers.test.js index 0d46f99..6076640 100644 --- a/__tests__/BP/scripts/src/rules/echoShardsEnableShriekers.test.js +++ b/__tests__/BP/scripts/src/rules/echoShardsEnableShriekers.test.js @@ -1,5 +1,5 @@ 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', async (importOriginal) => { const original = await importOriginal(); diff --git a/__tests__/BP/scripts/src/rules/entitySeparation.test.js b/__tests__/BP/scripts/src/rules/entitySeparation.test.js index 049bd01..4f47c2b 100644 --- a/__tests__/BP/scripts/src/rules/entitySeparation.test.js +++ b/__tests__/BP/scripts/src/rules/entitySeparation.test.js @@ -1,6 +1,6 @@ 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', async (importOriginal) => { const original = await importOriginal(); diff --git a/__tests__/BP/scripts/src/rules/infodisplay/BlockStates.test.js b/__tests__/BP/scripts/src/rules/infodisplay/BlockStates.test.js index 53c27e8..5b6452d 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/BlockStates.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/BlockStates.test.js @@ -1,7 +1,7 @@ -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 { InfoDisplayElement } from '../../../../../../Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayElement'; +import { Rules } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rules'; vi.mock('@minecraft/server', async (importOriginal) => { const original = await importOriginal(); diff --git a/__tests__/BP/scripts/src/rules/infodisplay/ChunkCoords.test.js b/__tests__/BP/scripts/src/rules/infodisplay/ChunkCoords.test.js index 404e4a7..6b56195 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/ChunkCoords.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/ChunkCoords.test.js @@ -1,7 +1,7 @@ -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 { InfoDisplayElement } from '../../../../../../Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayElement'; +import { Rules } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rules'; vi.mock('@minecraft/server', async (importOriginal) => { const original = await importOriginal(); diff --git a/__tests__/BP/scripts/src/rules/infodisplay/Dimension.test.js b/__tests__/BP/scripts/src/rules/infodisplay/Dimension.test.js index 4a8b956..fdcde91 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/Dimension.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/Dimension.test.js @@ -1,7 +1,7 @@ -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'; +import { InfoDisplayElement } from '../../../../../../Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayElement'; +import { Rules } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rules'; vi.mock('@minecraft/server', async (importOriginal) => { const original = await importOriginal(); diff --git a/__tests__/BP/scripts/src/rules/infodisplay/PeekInventory.test.js b/__tests__/BP/scripts/src/rules/infodisplay/PeekInventory.test.js index 37bffe4..4cc146f 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/PeekInventory.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/PeekInventory.test.js @@ -1,7 +1,7 @@ -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 { InfoDisplayElement } from '../../../../../../Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayElement'; +import { Rules } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rules'; vi.mock('@minecraft/server', async (importOriginal) => { const original = await importOriginal(); diff --git a/__tests__/BP/scripts/src/rules/infodisplay/Speed.test.js b/__tests__/BP/scripts/src/rules/infodisplay/Speed.test.js index 4de974d..a72f17d 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/Speed.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/Speed.test.js @@ -1,7 +1,7 @@ -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'; +import { InfoDisplayElement } from '../../../../../../Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayElement'; +import { Rules } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rules'; vi.mock('@minecraft/server', async (importOriginal) => { const original = await importOriginal(); diff --git a/__tests__/BP/scripts/src/rules/infodisplay/Structures.test.js b/__tests__/BP/scripts/src/rules/infodisplay/Structures.test.js index 651f540..c9723ae 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/Structures.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/Structures.test.js @@ -1,7 +1,7 @@ -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'; +import { InfoDisplayElement } from '../../../../../../Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayElement'; +import { Rules } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rules'; vi.mock('@minecraft/server', async (importOriginal) => { const original = await importOriginal(); diff --git a/__tests__/BP/scripts/src/rules/infodisplay/Weather.test.js b/__tests__/BP/scripts/src/rules/infodisplay/Weather.test.js index 57bba7a..3a155e1 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/Weather.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/Weather.test.js @@ -1,7 +1,7 @@ -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 { InfoDisplayElement } from '../../../../../../Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayElement'; +import { Rules } from '../../../../../../Canopy[BP]/scripts/lib/canopy/rules/Rules'; vi.mock('@minecraft/server', async (importOriginal) => { const original = await importOriginal(); diff --git a/__tests__/BP/scripts/src/rules/playerSit.test.js b/__tests__/BP/scripts/src/rules/playerSit.test.js index a136f6c..e8b2d48 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', diff --git a/__tests__/BP/scripts/src/rules/spawnEggSpawnWithMinecart.test.js b/__tests__/BP/scripts/src/rules/spawnEggSpawnWithMinecart.test.js index 5426e7d..46fe599 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 = { diff --git a/__tests__/BP/scripts/src/rules/tntFuse.test.js b/__tests__/BP/scripts/src/rules/tntFuse.test.js index dfba06e..65709e4 100644 --- a/__tests__/BP/scripts/src/rules/tntFuse.test.js +++ b/__tests__/BP/scripts/src/rules/tntFuse.test.js @@ -1,5 +1,5 @@ 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"; const tntEntity = { typeId: 'minecraft:tnt', diff --git a/__tests__/manifests.test.js b/__tests__/manifests.test.js index bd9d600..fd71047 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'); @@ -30,7 +30,7 @@ describe('Manifests', () => { }); it('should include version number in its name', () => { - expect(manifestContentBP.header.name).toBe(`Canopy [BP] v${getCanopyManifestVersion()}`); + expect(manifestContentBP.header.name).toBe(`Canopy[BP] v${getCanopyManifestVersion()}`); }); it('should match constants version number', () => { @@ -45,7 +45,7 @@ describe('Manifests', () => { describe('RP', () => { it('RP name should include version number', () => { const manifestContentRP = getManifestObject(manifestPathRP); - expect(manifestContentRP.header.name).toBe(`Canopy [RP] v${getCanopyManifestVersion()}`); + expect(manifestContentRP.header.name).toBe(`Canopy[RP] v${getCanopyManifestVersion()}`); }); }); }); \ No newline at end of file diff --git a/config.json b/config.json index 915b66c..d4c8201 100644 --- a/config.json +++ b/config.json @@ -3,8 +3,8 @@ "author": "ForestOfLight", "name": "Canopy", "packs": { - "behaviorPack": "./Canopy [BP]", - "resourcePack": "./Canopy [RP]" + "behaviorPack": "./Canopy[BP]", + "resourcePack": "./Canopy[RP]" }, "regolith": { "dataPath": "./data", diff --git a/filters/bump_version/main.js b/filters/bump_version/main.js index 23b4652..23ad2c4 100644 --- a/filters/bump_version/main.js +++ b/filters/bump_version/main.js @@ -21,7 +21,7 @@ function versionToArray(versionStr) { function getCurrentVersion() { const content = fs.readFileSync( - path.join(projectRoot, 'Canopy [BP]', 'scripts', 'constants.js'), 'utf8' + 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'); @@ -100,9 +100,9 @@ async function main() { 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(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); From 5e9adf6f23c52b889170a95dc441dcf6ea4b7033 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Fri, 24 Apr 2026 03:14:39 -0700 Subject: [PATCH 22/64] chore: ignore Claude Superpowers documentation --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 18494b5..daa9548 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ node_modules/ coverage/ /build -/.regolith \ No newline at end of file +/.regolith +/docs/superpowers/ \ No newline at end of file From 20ad7ce8cf67783cb4c22e6e7509248a83004be0 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Fri, 24 Apr 2026 03:15:08 -0700 Subject: [PATCH 23/64] docs: add CONTRIBUTING.md --- CONTRIBUTING.md | 63 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..1b4632d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,63 @@ +# Contributing to Canopy + +Thanks for your interest in contributing! There are several ways to help: + +- [Reporting Bugs](#reporting-bugs) +- [Contributing Code](#contributing-code) +- [Contributing Translations](docs/TRANSLATING.md) + +--- + +## 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. From 33845375de9c5f7384efaf5e81ab6015489b4629 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Fri, 24 Apr 2026 03:15:42 -0700 Subject: [PATCH 24/64] docs: add TRANSLATING.md --- docs/TRANSLATING.md | 114 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 docs/TRANSLATING.md diff --git a/docs/TRANSLATING.md b/docs/TRANSLATING.md new file mode 100644 index 0000000..886dc10 --- /dev/null +++ b/docs/TRANSLATING.md @@ -0,0 +1,114 @@ +# 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: + ```bash + git clone 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. From 5f2114c38d5f102b2d101c3376d673ed9df8757c Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Fri, 24 Apr 2026 03:17:12 -0700 Subject: [PATCH 25/64] docs: trim README contributing section, point to CONTRIBUTING.md --- README.md | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/README.md b/README.md index b96d3ae..511acbe 100644 --- a/README.md +++ b/README.md @@ -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 From 478429e6527573831abb8e050daa90f215e39d4d Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Fri, 24 Apr 2026 04:05:01 -0700 Subject: [PATCH 26/64] test: update pack name test after pack folder rename --- __tests__/manifests.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/__tests__/manifests.test.js b/__tests__/manifests.test.js index fd71047..ca91fa3 100644 --- a/__tests__/manifests.test.js +++ b/__tests__/manifests.test.js @@ -30,7 +30,7 @@ describe('Manifests', () => { }); it('should include version number in its name', () => { - expect(manifestContentBP.header.name).toBe(`Canopy[BP] v${getCanopyManifestVersion()}`); + expect(manifestContentBP.header.name).toBe(`Canopy [BP] v${getCanopyManifestVersion()}`); }); it('should match constants version number', () => { @@ -45,7 +45,7 @@ describe('Manifests', () => { describe('RP', () => { it('RP name should include version number', () => { const manifestContentRP = getManifestObject(manifestPathRP); - expect(manifestContentRP.header.name).toBe(`Canopy[RP] v${getCanopyManifestVersion()}`); + expect(manifestContentRP.header.name).toBe(`Canopy [RP] v${getCanopyManifestVersion()}`); }); }); }); \ No newline at end of file From c4dab9117a66a66cd24fbcff7961f8eeb45038a2 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Fri, 24 Apr 2026 04:06:23 -0700 Subject: [PATCH 27/64] docs: clean up after claude --- CONTRIBUTING.md | 17 ++++++++--------- docs/TRANSLATING.md | 4 ++-- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1b4632d..b46ae77 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,18 +2,19 @@ Thanks for your interest in contributing! There are several ways to help: -- [Reporting Bugs](#reporting-bugs) -- [Contributing Code](#contributing-code) -- [Contributing Translations](docs/TRANSLATING.md) - ---- +- [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 @@ -56,8 +57,6 @@ All tests must pass before submitting. - 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/docs/TRANSLATING.md b/docs/TRANSLATING.md index 886dc10..3c9f8f8 100644 --- a/docs/TRANSLATING.md +++ b/docs/TRANSLATING.md @@ -9,9 +9,9 @@ Canopy currently supports: American English, German, Indonesian, Chinese, Welsh, ### 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: +2. Clone your fork (use the `dev` branch): ```bash - git clone https://github.com//Canopy.git + git clone -b dev https://github.com//Canopy.git cd Canopy ``` 3. Create a new branch for your translation: From 2692b21637e9fd79c0e776c3e50fe7f0b86366e8 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Fri, 24 Apr 2026 04:06:32 -0700 Subject: [PATCH 28/64] docs: same --- docs/TRANSLATING.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/docs/TRANSLATING.md b/docs/TRANSLATING.md index 3c9f8f8..e7e19e0 100644 --- a/docs/TRANSLATING.md +++ b/docs/TRANSLATING.md @@ -2,8 +2,6 @@ Canopy currently supports: American English, German, Indonesian, Chinese, Welsh, and Japanese. New translations are always welcome! ---- - ## Getting Started ### 1. Fork and clone the repository @@ -19,8 +17,6 @@ Canopy currently supports: American English, German, Indonesian, Chinese, Welsh, git checkout -b translation/fr_FR ``` ---- - ## Adding or Updating a Translation ### Updating an existing language @@ -69,8 +65,6 @@ 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. @@ -86,8 +80,6 @@ befehle.hilfe=Zeigt Hilfeseiten an. This only works when the pack is in `development_resource_packs`. ---- - ## Submitting a PR 1. Stage only your translation files: @@ -107,8 +99,6 @@ This only works when the pack is in `development_resource_packs`. 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. From 26117e61b4a059e7fac2766b7958c711f8354dad Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Fri, 24 Apr 2026 05:13:47 -0700 Subject: [PATCH 29/64] chore: add CustomCommandParamType and CommandPermissionLevel to server mock --- __mocks__/@minecraft/server.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/__mocks__/@minecraft/server.js b/__mocks__/@minecraft/server.js index 21c6545..92cb21f 100644 --- a/__mocks__/@minecraft/server.js +++ b/__mocks__/@minecraft/server.js @@ -58,6 +58,26 @@ export const CustomCommandStatus = { Success: 'Success', }; +export const CustomCommandParamType = { + Boolean: 'Boolean', + Enum: 'Enum', + Float: 'Float', + Integer: 'Integer', + Location: 'Location', + String: 'String', + EntitySelector: 'EntitySelector', + EntityType: 'EntityType', + BlockType: 'BlockType', +}; + +export const CommandPermissionLevel = { + Any: 'Any', + GameDirectors: 'GameDirectors', + Admin: 'Admin', + Owner: 'Owner', + Internal: 'Internal', +}; + export const DimensionTypes = {}; export const ScriptEventSource = {}; export const InputButton = {}; From 24590638f691878ddabb11386363a18459feeb4a Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Fri, 24 Apr 2026 05:17:29 -0700 Subject: [PATCH 30/64] feat: add wikiDescription and suggestedOptions fields to Rule base class --- .../scripts/lib/canopy/rules/BooleanRule.js | 3 +- Canopy[BP]/scripts/lib/canopy/rules/Rule.js | 17 +++++++++-- .../BP/scripts/lib/canopy/rules/Rule.test.js | 29 +++++++++++++++++++ 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/Canopy[BP]/scripts/lib/canopy/rules/BooleanRule.js b/Canopy[BP]/scripts/lib/canopy/rules/BooleanRule.js index 5384ad0..291edf4 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/Rule.js b/Canopy[BP]/scripts/lib/canopy/rules/Rule.js index b4a2d55..75edfaf 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/__tests__/BP/scripts/lib/canopy/rules/Rule.test.js b/__tests__/BP/scripts/lib/canopy/rules/Rule.test.js index 1cd92b1..a446aeb 100644 --- a/__tests__/BP/scripts/lib/canopy/rules/Rule.test.js +++ b/__tests__/BP/scripts/lib/canopy/rules/Rule.test.js @@ -80,4 +80,33 @@ describe('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(); + }); + }); }); From 705eb86d32015136a8de76656fd8ba5a30b1c2b5 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Fri, 24 Apr 2026 05:21:48 -0700 Subject: [PATCH 31/64] feat: add VanillaCommands registry and wikiDescription metadata to VanillaCommand --- Canopy[BP]/scripts/lib/canopy/Canopy.js | 5 +- .../lib/canopy/commands/VanillaCommand.js | 10 ++++ .../lib/canopy/commands/VanillaCommands.js | 17 +++++++ .../canopy/commands/VanillaCommands.test.js | 51 +++++++++++++++++++ 4 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 Canopy[BP]/scripts/lib/canopy/commands/VanillaCommands.js create mode 100644 __tests__/BP/scripts/lib/canopy/commands/VanillaCommands.test.js diff --git a/Canopy[BP]/scripts/lib/canopy/Canopy.js b/Canopy[BP]/scripts/lib/canopy/Canopy.js index 8917fc2..01b58d6 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/commands/VanillaCommand.js b/Canopy[BP]/scripts/lib/canopy/commands/VanillaCommand.js index 39d2fe0..e6c7335 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 0000000..bac84f3 --- /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/__tests__/BP/scripts/lib/canopy/commands/VanillaCommands.test.js b/__tests__/BP/scripts/lib/canopy/commands/VanillaCommands.test.js new file mode 100644 index 0000000..3deb823 --- /dev/null +++ b/__tests__/BP/scripts/lib/canopy/commands/VanillaCommands.test.js @@ -0,0 +1,51 @@ +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'; +import { CustomCommandParamType } from '@minecraft/server'; + +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({}); + }); +}); From 47c35b568493f5bf738f604275aace72b0f21b13 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Fri, 24 Apr 2026 05:29:15 -0700 Subject: [PATCH 32/64] feat: add wikiDescription and suggestedOptions to all global rules Co-Authored-By: Claude Sonnet 4.6 --- Canopy[BP]/scripts/src/commands/camera.js | 1 + Canopy[BP]/scripts/src/commands/claimprojectiles.js | 3 ++- Canopy[BP]/scripts/src/commands/counter.js | 1 + Canopy[BP]/scripts/src/commands/generator.js | 1 + Canopy[BP]/scripts/src/commands/jump.js | 3 ++- Canopy[BP]/scripts/src/commands/pos.js | 3 ++- Canopy[BP]/scripts/src/commands/warp.js | 6 ++++-- Canopy[BP]/scripts/src/rules/allowBubbleColumnPlacement.js | 1 + Canopy[BP]/scripts/src/rules/allowPeekInventory.js | 1 + Canopy[BP]/scripts/src/rules/armorStandRespawning.js | 3 ++- Canopy[BP]/scripts/src/rules/autoItemPickup.js | 3 ++- Canopy[BP]/scripts/src/rules/cauldronConcreteConversion.js | 1 + Canopy[BP]/scripts/src/rules/chunkBorders.js | 1 + Canopy[BP]/scripts/src/rules/collisionBoxes.js | 1 + Canopy[BP]/scripts/src/rules/creativeHotbarSwitching.js | 1 + Canopy[BP]/scripts/src/rules/creativeInstantTame.js | 3 ++- .../scripts/src/rules/creativeNetherWaterPlacement.js | 1 + Canopy[BP]/scripts/src/rules/creativeNoTileDrops.js | 3 ++- Canopy[BP]/scripts/src/rules/creativeOneHitKill.js | 3 ++- Canopy[BP]/scripts/src/rules/dupeTnt.js | 3 ++- Canopy[BP]/scripts/src/rules/durabilityNotifier.js | 3 ++- Canopy[BP]/scripts/src/rules/durabilitySwap.js | 3 ++- Canopy[BP]/scripts/src/rules/echoShardsEnableShriekers.js | 1 + Canopy[BP]/scripts/src/rules/enderPearlChunkLoading.js | 2 ++ Canopy[BP]/scripts/src/rules/entityInstantDeath.js | 1 + Canopy[BP]/scripts/src/rules/entitySeparation.js | 1 + Canopy[BP]/scripts/src/rules/explosionChainReactionOnly.js | 3 ++- Canopy[BP]/scripts/src/rules/explosionNoBlockDamage.js | 3 ++- Canopy[BP]/scripts/src/rules/explosionOff.js | 3 ++- Canopy[BP]/scripts/src/rules/flippinArrows.js | 3 ++- Canopy[BP]/scripts/src/rules/instaminableDeepslate.js | 3 ++- Canopy[BP]/scripts/src/rules/instaminableEndstone.js | 3 ++- Canopy[BP]/scripts/src/rules/minecartChunkLoading.js | 2 ++ Canopy[BP]/scripts/src/rules/pistonBedrockBreaking.js | 3 ++- Canopy[BP]/scripts/src/rules/playerSit.js | 1 + Canopy[BP]/scripts/src/rules/potionBoostedBreeding.js | 1 + Canopy[BP]/scripts/src/rules/quickFillContainer.js | 1 + Canopy[BP]/scripts/src/rules/refillHand.js | 1 + Canopy[BP]/scripts/src/rules/renewableElytraDropChance.js | 2 ++ Canopy[BP]/scripts/src/rules/renewableSponge.js | 3 ++- Canopy[BP]/scripts/src/rules/serverSideCollisionBoxes.js | 1 + Canopy[BP]/scripts/src/rules/spawnEggSpawnWithMinecart.js | 1 + Canopy[BP]/scripts/src/rules/tntFuse.js | 2 ++ Canopy[BP]/scripts/src/rules/tntPrimeMomentum.js | 2 ++ __tests__/BP/scripts/src/rules/dupeTnt.test.js | 3 ++- 45 files changed, 73 insertions(+), 22 deletions(-) diff --git a/Canopy[BP]/scripts/src/commands/camera.js b/Canopy[BP]/scripts/src/commands/camera.js index 4ab5871..5718a13 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); diff --git a/Canopy[BP]/scripts/src/commands/claimprojectiles.js b/Canopy[BP]/scripts/src/commands/claimprojectiles.js index d31a1e5..8edde1a 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( { diff --git a/Canopy[BP]/scripts/src/commands/counter.js b/Canopy[BP]/scripts/src/commands/counter.js index 1b1d525..2541aa9 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() }); diff --git a/Canopy[BP]/scripts/src/commands/generator.js b/Canopy[BP]/scripts/src/commands/generator.js index d3541de..6fc09ab 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() }); diff --git a/Canopy[BP]/scripts/src/commands/jump.js b/Canopy[BP]/scripts/src/commands/jump.js index 286d3d0..a00f032 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({ diff --git a/Canopy[BP]/scripts/src/commands/pos.js b/Canopy[BP]/scripts/src/commands/pos.js index 9317c13..fac49fe 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({ diff --git a/Canopy[BP]/scripts/src/commands/warp.js b/Canopy[BP]/scripts/src/commands/warp.js index 4ff2cff..f2e4012 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({ diff --git a/Canopy[BP]/scripts/src/rules/allowBubbleColumnPlacement.js b/Canopy[BP]/scripts/src/rules/allowBubbleColumnPlacement.js index ad78f43..6cce823 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 index aa70e4c..7b69564 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 index fd8b899..1b2e838 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 index 8714e23..484bfb9 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 index e27e1c1..0af41b4 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 index 01ae6f2..ce80975 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 index e9e8183..9c358c0 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 index 4d61308..30a2025 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 index 43b7735..df13b57 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 index ca8f6b2..09360ba 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 index 27a312a..1d93dfe 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 index 2779983..a9e8122 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 index 654db07..27dae59 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 index a30c620..c5ca3fa 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 index 14b08cd..8d88fdc 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 index 4a92eea..8cdfe1c 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 index e785d9b..b1c85b4 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 index 1687e5e..8a3506f 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 index 14dfcfc..6b75ccb 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 index 444c139..35a5e57 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 index fb33bdd..238d739 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 index 720d498..2ba32a4 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 index 5b559da..8a77acb 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/instaminableDeepslate.js b/Canopy[BP]/scripts/src/rules/instaminableDeepslate.js index 45b2200..90dc694 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 index 52784a9..c51a052 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 index 0cf6c3f..e36e5be 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/pistonBedrockBreaking.js b/Canopy[BP]/scripts/src/rules/pistonBedrockBreaking.js index 4e3585d..ce863d9 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 index b2c8802..0760616 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 index 36de10e..dddf66b 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 index b5f9c4c..285c319 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 index d23c645..b0c79c4 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/renewableElytraDropChance.js b/Canopy[BP]/scripts/src/rules/renewableElytraDropChance.js index 14052e0..6ec127e 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 index c924f63..cfec270 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 index c0251f2..626939a 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 index 61c5d77..8ae10cf 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 index a8ba598..37bee97 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 index 36062af..6da0dd9 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/__tests__/BP/scripts/src/rules/dupeTnt.test.js b/__tests__/BP/scripts/src/rules/dupeTnt.test.js index dd2b7e0..5daf7b9 100644 --- a/__tests__/BP/scripts/src/rules/dupeTnt.test.js +++ b/__tests__/BP/scripts/src/rules/dupeTnt.test.js @@ -37,7 +37,8 @@ describe('dupeTnt Rule', () => { expect(BooleanRule).toHaveBeenCalledWith({ 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)' }); }); From 73f56fe14fd77710e438b8e2bcd43213de183d1f Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Fri, 24 Apr 2026 05:34:11 -0700 Subject: [PATCH 33/64] feat: add wikiDescription to all InfoDisplay rules Adds wikiDescription to the ruleData object in all 27 InfoDisplay rule files to support auto-generated wiki documentation. Co-Authored-By: Claude Sonnet 4.6 --- Canopy[BP]/scripts/src/rules/infodisplay/Biome.js | 7 ++++--- Canopy[BP]/scripts/src/rules/infodisplay/BlockStates.js | 3 ++- .../scripts/src/rules/infodisplay/CardinalFacing.js | 2 +- Canopy[BP]/scripts/src/rules/infodisplay/ChunkCoords.js | 2 +- Canopy[BP]/scripts/src/rules/infodisplay/Coords.js | 2 +- Canopy[BP]/scripts/src/rules/infodisplay/Dimension.js | 2 +- Canopy[BP]/scripts/src/rules/infodisplay/Entities.js | 2 +- .../scripts/src/rules/infodisplay/EventTrackers.js | 2 +- Canopy[BP]/scripts/src/rules/infodisplay/Facing.js | 2 +- .../scripts/src/rules/infodisplay/HopperCounterCounts.js | 2 +- Canopy[BP]/scripts/src/rules/infodisplay/Light.js | 5 +++-- Canopy[BP]/scripts/src/rules/infodisplay/LiquidStates.js | 3 ++- Canopy[BP]/scripts/src/rules/infodisplay/LiquidTarget.js | 2 +- Canopy[BP]/scripts/src/rules/infodisplay/MoonPhase.js | 2 +- .../scripts/src/rules/infodisplay/PeekInventory.js | 9 +++++---- Canopy[BP]/scripts/src/rules/infodisplay/SessionTime.js | 2 +- .../scripts/src/rules/infodisplay/SignalStrength.js | 2 +- .../scripts/src/rules/infodisplay/SimulationMap.js | 2 +- Canopy[BP]/scripts/src/rules/infodisplay/SlimeChunk.js | 2 +- Canopy[BP]/scripts/src/rules/infodisplay/Speed.js | 3 ++- Canopy[BP]/scripts/src/rules/infodisplay/Structures.js | 3 ++- Canopy[BP]/scripts/src/rules/infodisplay/TPS.js | 2 +- Canopy[BP]/scripts/src/rules/infodisplay/Target.js | 2 +- Canopy[BP]/scripts/src/rules/infodisplay/TimeOfDay.js | 2 +- Canopy[BP]/scripts/src/rules/infodisplay/Velocity.js | 3 ++- Canopy[BP]/scripts/src/rules/infodisplay/Weather.js | 2 +- Canopy[BP]/scripts/src/rules/infodisplay/WorldDay.js | 2 +- 27 files changed, 41 insertions(+), 33 deletions(-) diff --git a/Canopy[BP]/scripts/src/rules/infodisplay/Biome.js b/Canopy[BP]/scripts/src/rules/infodisplay/Biome.js index 62a2fb1..73ef2df 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/Biome.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/Biome.js @@ -4,9 +4,10 @@ class Biome extends InfoDisplayElement { 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 index 33ec7b6..cfe65a5 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/BlockStates.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/BlockStates.js @@ -8,7 +8,8 @@ export class BlockStates extends InfoDisplayElement { 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 index 3332e06..1401c9d 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/CardinalFacing.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/CardinalFacing.js @@ -4,7 +4,7 @@ class CardinalFacing extends InfoDisplayElement { 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 index fd56c96..0a330e1 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/ChunkCoords.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/ChunkCoords.js @@ -2,7 +2,7 @@ import { InfoDisplayElement } from './InfoDisplayElement.js'; class ChunkCoords extends InfoDisplayElement { 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 index 0265263..7f39d29 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/Coords.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/Coords.js @@ -4,7 +4,7 @@ class Coords extends InfoDisplayElement { 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 index bc3a883..0cf25ee 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/Dimension.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/Dimension.js @@ -3,7 +3,7 @@ import { InfoDisplayElement } from './InfoDisplayElement.js'; class Dimension extends InfoDisplayElement { 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 index f9e2929..aca81f9 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/Entities.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/Entities.js @@ -5,7 +5,7 @@ class Entities extends InfoDisplayElement { 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 index 6c930f7..0141185 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/EventTrackers.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/EventTrackers.js @@ -3,7 +3,7 @@ import { getAllTrackerInfoString } from 'src/commands/trackevent'; class EventTrackers extends InfoDisplayElement { 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 index 2057540..da0ace2 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/Facing.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/Facing.js @@ -4,7 +4,7 @@ class Facing extends InfoDisplayElement { 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/HopperCounterCounts.js b/Canopy[BP]/scripts/src/rules/infodisplay/HopperCounterCounts.js index d36ecbf..7488a30 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/HopperCounterCounts.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/HopperCounterCounts.js @@ -4,7 +4,7 @@ import { getColorCode } from "../../../include/utils"; class HopperCounterCounts extends InfoDisplayElement { 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/Light.js b/Canopy[BP]/scripts/src/rules/infodisplay/Light.js index 3628d37..6181968 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/Light.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/Light.js @@ -4,9 +4,10 @@ class Light extends InfoDisplayElement { 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 index 9f7f5a1..e81af58 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/LiquidStates.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/LiquidStates.js @@ -7,7 +7,8 @@ export class LiquidStates extends InfoDisplayElement { 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 index 949f20f..fe2b1e0 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/LiquidTarget.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/LiquidTarget.js @@ -3,7 +3,7 @@ import { parseName, stringifyLocation } from "../../../include/utils"; export class LiquidTarget extends InfoDisplayElement { 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 index f55aeb4..df172c5 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/MoonPhase.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/MoonPhase.js @@ -3,7 +3,7 @@ import { world } from '@minecraft/server'; class MoonPhase extends InfoDisplayElement { 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 index 3cbd451..bb3f82d 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/PeekInventory.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/PeekInventory.js @@ -7,10 +7,11 @@ class PeekInventory extends InfoDisplayElement { 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/SessionTime.js b/Canopy[BP]/scripts/src/rules/infodisplay/SessionTime.js index c3e46fc..d33e190 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/SessionTime.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/SessionTime.js @@ -4,7 +4,7 @@ class SessionTime extends InfoDisplayElement { 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 index 8ebc112..7c02e11 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/SignalStrength.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/SignalStrength.js @@ -5,7 +5,7 @@ class SignalStrength extends InfoDisplayElement { 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 index 9fbfb82..94c7fef 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/SimulationMap.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/SimulationMap.js @@ -6,7 +6,7 @@ class SimulationMap extends InfoDisplayElement { 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 index 083490b..f12a21a 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/SlimeChunk.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/SlimeChunk.js @@ -6,7 +6,7 @@ export class SlimeChunk extends InfoDisplayElement { 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 index a37833e..cdb6b16 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/Speed.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/Speed.js @@ -8,7 +8,8 @@ export class Speed extends InfoDisplayElement { 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 index e2f6e94..371199d 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/Structures.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/Structures.js @@ -6,7 +6,8 @@ export class Structures extends InfoDisplayElement { 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 index 6d67b61..4dc3ebb 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/TPS.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/TPS.js @@ -4,7 +4,7 @@ import { TicksPerSecond } from '@minecraft/server'; class TPS extends InfoDisplayElement { 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 index 866b09b..a408f5e 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/Target.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/Target.js @@ -3,7 +3,7 @@ import { getRaycastResults, parseName, stringifyLocation } from "../../../includ export class Target extends InfoDisplayElement { 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 index d63954f..c3b1ad2 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/TimeOfDay.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/TimeOfDay.js @@ -3,7 +3,7 @@ import { world } from '@minecraft/server'; class TimeOfDay extends InfoDisplayElement { 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 index bab0b83..2b09891 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/Velocity.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/Velocity.js @@ -7,7 +7,8 @@ export class Velocity extends InfoDisplayElement { 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 index dbb1873..51ad284 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/Weather.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/Weather.js @@ -2,7 +2,7 @@ import { InfoDisplayElement } from './InfoDisplayElement.js'; class Weather extends InfoDisplayElement { 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 index 67b6c13..3ae3851 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/WorldDay.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/WorldDay.js @@ -3,7 +3,7 @@ import { world } from '@minecraft/server'; class WorldDay extends InfoDisplayElement { 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); } From cbf289c39f4706946374bfcbd0dd76a61ca2b962 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Fri, 24 Apr 2026 05:37:06 -0700 Subject: [PATCH 34/64] feat: add subCommandWikiDescription to biomeedges, camera, and hss commands --- Canopy[BP]/scripts/src/commands/biomeedges.js | 14 ++++++++++++++ Canopy[BP]/scripts/src/commands/camera.js | 16 +++++++++++++++- Canopy[BP]/scripts/src/commands/hss.js | 14 ++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/Canopy[BP]/scripts/src/commands/biomeedges.js b/Canopy[BP]/scripts/src/commands/biomeedges.js index 47c6fb2..3529dd0 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/camera.js b/Canopy[BP]/scripts/src/commands/camera.js index 5718a13..123e909 100644 --- a/Canopy[BP]/scripts/src/commands/camera.js +++ b/Canopy[BP]/scripts/src/commands/camera.js @@ -36,7 +36,21 @@ new VanillaCommand({ permissionLevel: CommandPermissionLevel.Any, contingentRules: ['commandCamera'], allowedSources: [PlayerCommandOrigin], - callback: cameraCommand + callback: cameraCommand, + subCommandWikiDescription: { + 'place': { + description: 'Places a camera at your current location for later viewing.', + params: [] + }, + 'view': { + description: 'Toggles viewing your placed camera. Your player can still move and interact while viewing.', + params: [] + }, + 'spectate': { + description: 'Toggles a survival-friendly freecam. Switches you to spectator mode with night vision and conduit power. Run again to return to your original position.', + params: [] + }, + }, }); new VanillaCommand({ diff --git a/Canopy[BP]/scripts/src/commands/hss.js b/Canopy[BP]/scripts/src/commands/hss.js index c9c9b6a..32fef11 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.', + 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: [] + }, + }, }); } From 9b67b7e77531e428e6b1b92485e19333b07c5456 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Fri, 24 Apr 2026 06:02:50 -0700 Subject: [PATCH 35/64] feat: add generator ESM loader and minecraft mock for Node.js context - Add docs/scripts/mock-hooks.mjs and mock-loader.mjs to intercept @minecraft/* imports, bare lib/ and src/ aliases, and extensionless relative imports when running under plain Node.js - Update __mocks__/@minecraft/server.js to work in both Vitest and plain Node contexts (conditional vi.fn(), Proxy-based eventBus, full export coverage including EntityItemComponent, BlockVolume, DimensionTypes.get/getAll, etc.) - Update __mocks__/@minecraft/server-ui.js similarly - Add __mocks__/@minecraft/debug-utilities.js new mock Co-Authored-By: Claude Sonnet 4.6 --- __mocks__/@minecraft/debug-utilities.js | 15 +++++ __mocks__/@minecraft/server-ui.js | 13 +++- __mocks__/@minecraft/server.js | 81 +++++++++++++++---------- docs/scripts/mock-hooks.mjs | 37 +++++++++++ docs/scripts/mock-loader.mjs | 3 + 5 files changed, 116 insertions(+), 33 deletions(-) create mode 100644 __mocks__/@minecraft/debug-utilities.js create mode 100644 docs/scripts/mock-hooks.mjs create mode 100644 docs/scripts/mock-loader.mjs diff --git a/__mocks__/@minecraft/debug-utilities.js b/__mocks__/@minecraft/debug-utilities.js new file mode 100644 index 0000000..80afa93 --- /dev/null +++ b/__mocks__/@minecraft/debug-utilities.js @@ -0,0 +1,15 @@ +/* eslint-disable max-classes-per-file */ +// Mock for @minecraft/debug-utilities +const noOp = () => {}; + +export const debugDrawer = { + drawArrow: noOp, + drawBox: noOp, + drawLine: noOp, + drawText: noOp, +}; + +export class DebugArrow {} +export class DebugBox {} +export class DebugLine {} +export class DebugText {} diff --git a/__mocks__/@minecraft/server-ui.js b/__mocks__/@minecraft/server-ui.js index fd0840e..bee9768 100644 --- a/__mocks__/@minecraft/server-ui.js +++ b/__mocks__/@minecraft/server-ui.js @@ -1,5 +1,14 @@ /* eslint-disable max-classes-per-file */ -import { vi } from 'vitest'; +// Support both Vitest (vi.fn()) and plain Node.js (noOp) contexts +let mockFn; +try { + const vitest = await import('vitest'); + mockFn = vitest.vi.fn; +} catch { + mockFn = () => () => {}; +} + +const fn = () => mockFn(); export const FormCancelationReason = { UserBusy: 'UserBusy', @@ -7,7 +16,7 @@ export const FormCancelationReason = { }; export const uiManager = { - closeAllForms: vi.fn(), + closeAllForms: fn(), }; export class ModalFormData {} diff --git a/__mocks__/@minecraft/server.js b/__mocks__/@minecraft/server.js index 92cb21f..69404a2 100644 --- a/__mocks__/@minecraft/server.js +++ b/__mocks__/@minecraft/server.js @@ -1,41 +1,42 @@ /* eslint-disable max-classes-per-file */ -import { vi } from 'vitest'; +// Support both Vitest (vi.fn()) and plain Node.js (noOp) contexts +let mockFn; +try { + const vitest = await import('vitest'); + mockFn = vitest.vi.fn; +} catch { + mockFn = () => () => {}; +} + +const noOp = () => {}; +const fn = () => mockFn(); +const eventEmitter = () => ({ subscribe: fn(), unsubscribe: fn() }); + +// Returns an event emitter for any property access, so unknown events don't throw +const eventBus = () => new Proxy({}, { get: () => eventEmitter() }); export const world = { - beforeEvents: { - chatSend: { subscribe: vi.fn() }, - playerPlaceBlock: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - entityRemove: { subscribe: vi.fn() }, - playerLeave: { subscribe: vi.fn() }, - }, - afterEvents: { - worldLoad: { subscribe: vi.fn() }, - entitySpawn: { subscribe: vi.fn() }, - playerInventoryItemChange: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - pistonActivate: { subscribe: vi.fn() }, - }, - getDimension: vi.fn(), - getDynamicProperty: vi.fn(), - setDynamicProperty: vi.fn(), + beforeEvents: eventBus(), + afterEvents: eventBus(), + getDimension: fn(), + getDynamicProperty: fn(), + setDynamicProperty: fn(), + getPlayers: () => [], + getAllPlayers: () => [], structureManager: { - place: vi.fn(), + place: fn(), }, }; export const system = { currentTick: 0, - beforeEvents: { - startup: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - }, - afterEvents: { - scriptEventReceive: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - playerPlaceBlock: { subscribe: vi.fn() }, - }, - runJob: vi.fn(), - run: vi.fn(), - runInterval: vi.fn(), - runTimeout: vi.fn(), - clearRun: vi.fn(), + beforeEvents: eventBus(), + afterEvents: eventBus(), + runJob: fn(), + run: fn(), + runInterval: fn(), + runTimeout: fn(), + clearRun: fn(), }; export const EntityComponentTypes = { @@ -78,14 +79,32 @@ export const CommandPermissionLevel = { Internal: 'Internal', }; -export const DimensionTypes = {}; +export const DimensionTypes = { getAll: () => [], get: () => undefined }; export const ScriptEventSource = {}; export const InputButton = {}; export const ButtonState = {}; +export const GameMode = { survival: 'survival', creative: 'creative', adventure: 'adventure', spectator: 'spectator' }; +export const Direction = { down: 'down', up: 'up', north: 'north', south: 'south', east: 'east', west: 'west' }; +export const LiquidType = {}; +export const FluidType = {}; +export const EquipmentSlot = {}; +export const BlockPistonState = {}; +export const BlockComponentTypes = {}; +export const StructureMirrorAxis = {}; +export const StructureRotation = {}; +export const StructureSaveMode = {}; +export const RawMessage = {}; +export const MolangVariableMap = {}; +export const EntityInitializationCause = {}; export const TicksPerSecond = 20; export const ItemStack = {}; export class Block {} +export class BlockPermutation {} +export class BlockVolume {} +export class Container {} +export class CommandError extends Error {} export class Entity {} +export class EntityItemComponent {} export class Player { - sendMessage = vi.fn(); + sendMessage = fn(); } diff --git a/docs/scripts/mock-hooks.mjs b/docs/scripts/mock-hooks.mjs new file mode 100644 index 0000000..9f189ea --- /dev/null +++ b/docs/scripts/mock-hooks.mjs @@ -0,0 +1,37 @@ +import { fileURLToPath, pathToFileURL } from 'node:url'; +import path from 'node:path'; +import fs from 'node:fs'; + +const __dirname = fileURLToPath(new URL('.', import.meta.url)); +const projectRoot = path.resolve(__dirname, '../../'); +const scriptsRoot = path.join(projectRoot, 'Canopy[BP]/scripts'); + +export async function resolve(specifier, context, nextResolve) { + // Resolve bare path aliases used in BP scripts (lib/ and src/ map to Canopy[BP]/scripts/{lib,src}/) + if (specifier.startsWith('lib/') || specifier.startsWith('src/')) { + const resolved = path.join(scriptsRoot, specifier); + const candidate = fs.existsSync(resolved) ? resolved : resolved + '.js'; + return { url: pathToFileURL(candidate).href, shortCircuit: true }; + } + // Redirect all @minecraft/* packages to mocks in __mocks__/@minecraft/ + if (specifier.startsWith('@minecraft/')) { + const pkgName = specifier.slice('@minecraft/'.length); + const mockPath = path.join(projectRoot, `__mocks__/@minecraft/${pkgName}.js`); + if (fs.existsSync(mockPath)) { + return { url: pathToFileURL(mockPath).href, shortCircuit: true }; + } + } + // Add .js extension for relative imports that resolve to no file (Minecraft BP scripts omit extensions, + // and some files have multi-part names like foo.ipc.js that confuse path.extname) + if (specifier.startsWith('./') || specifier.startsWith('../')) { + const parentDir = context.parentURL ? path.dirname(fileURLToPath(context.parentURL)) : projectRoot; + const resolved = path.resolve(parentDir, specifier); + if (!fs.existsSync(resolved)) { + const candidate = resolved + '.js'; + if (fs.existsSync(candidate)) { + return { url: pathToFileURL(candidate).href, shortCircuit: true }; + } + } + } + return nextResolve(specifier, context); +} diff --git a/docs/scripts/mock-loader.mjs b/docs/scripts/mock-loader.mjs new file mode 100644 index 0000000..6014997 --- /dev/null +++ b/docs/scripts/mock-loader.mjs @@ -0,0 +1,3 @@ +import { register } from 'node:module'; + +register('./mock-hooks.mjs', import.meta.url); From 220fbd5e50525a85fa4d003408f513e6852654cb Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Fri, 24 Apr 2026 13:06:29 -0700 Subject: [PATCH 36/64] revert: undo Task 7 mock changes, will use Vitest-based approach instead --- __mocks__/@minecraft/debug-utilities.js | 15 ----- __mocks__/@minecraft/server-ui.js | 13 +--- __mocks__/@minecraft/server.js | 81 ++++++++++--------------- docs/scripts/mock-hooks.mjs | 37 ----------- docs/scripts/mock-loader.mjs | 3 - 5 files changed, 33 insertions(+), 116 deletions(-) delete mode 100644 __mocks__/@minecraft/debug-utilities.js delete mode 100644 docs/scripts/mock-hooks.mjs delete mode 100644 docs/scripts/mock-loader.mjs diff --git a/__mocks__/@minecraft/debug-utilities.js b/__mocks__/@minecraft/debug-utilities.js deleted file mode 100644 index 80afa93..0000000 --- a/__mocks__/@minecraft/debug-utilities.js +++ /dev/null @@ -1,15 +0,0 @@ -/* eslint-disable max-classes-per-file */ -// Mock for @minecraft/debug-utilities -const noOp = () => {}; - -export const debugDrawer = { - drawArrow: noOp, - drawBox: noOp, - drawLine: noOp, - drawText: noOp, -}; - -export class DebugArrow {} -export class DebugBox {} -export class DebugLine {} -export class DebugText {} diff --git a/__mocks__/@minecraft/server-ui.js b/__mocks__/@minecraft/server-ui.js index bee9768..fd0840e 100644 --- a/__mocks__/@minecraft/server-ui.js +++ b/__mocks__/@minecraft/server-ui.js @@ -1,14 +1,5 @@ /* eslint-disable max-classes-per-file */ -// Support both Vitest (vi.fn()) and plain Node.js (noOp) contexts -let mockFn; -try { - const vitest = await import('vitest'); - mockFn = vitest.vi.fn; -} catch { - mockFn = () => () => {}; -} - -const fn = () => mockFn(); +import { vi } from 'vitest'; export const FormCancelationReason = { UserBusy: 'UserBusy', @@ -16,7 +7,7 @@ export const FormCancelationReason = { }; export const uiManager = { - closeAllForms: fn(), + closeAllForms: vi.fn(), }; export class ModalFormData {} diff --git a/__mocks__/@minecraft/server.js b/__mocks__/@minecraft/server.js index 69404a2..92cb21f 100644 --- a/__mocks__/@minecraft/server.js +++ b/__mocks__/@minecraft/server.js @@ -1,42 +1,41 @@ /* eslint-disable max-classes-per-file */ -// Support both Vitest (vi.fn()) and plain Node.js (noOp) contexts -let mockFn; -try { - const vitest = await import('vitest'); - mockFn = vitest.vi.fn; -} catch { - mockFn = () => () => {}; -} - -const noOp = () => {}; -const fn = () => mockFn(); -const eventEmitter = () => ({ subscribe: fn(), unsubscribe: fn() }); - -// Returns an event emitter for any property access, so unknown events don't throw -const eventBus = () => new Proxy({}, { get: () => eventEmitter() }); +import { vi } from 'vitest'; export const world = { - beforeEvents: eventBus(), - afterEvents: eventBus(), - getDimension: fn(), - getDynamicProperty: fn(), - setDynamicProperty: fn(), - getPlayers: () => [], - getAllPlayers: () => [], + beforeEvents: { + chatSend: { subscribe: vi.fn() }, + playerPlaceBlock: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + entityRemove: { subscribe: vi.fn() }, + playerLeave: { subscribe: vi.fn() }, + }, + afterEvents: { + worldLoad: { subscribe: vi.fn() }, + entitySpawn: { subscribe: vi.fn() }, + playerInventoryItemChange: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + pistonActivate: { subscribe: vi.fn() }, + }, + getDimension: vi.fn(), + getDynamicProperty: vi.fn(), + setDynamicProperty: vi.fn(), structureManager: { - place: fn(), + place: vi.fn(), }, }; export const system = { currentTick: 0, - beforeEvents: eventBus(), - afterEvents: eventBus(), - runJob: fn(), - run: fn(), - runInterval: fn(), - runTimeout: fn(), - clearRun: fn(), + beforeEvents: { + startup: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + }, + afterEvents: { + scriptEventReceive: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + playerPlaceBlock: { subscribe: vi.fn() }, + }, + runJob: vi.fn(), + run: vi.fn(), + runInterval: vi.fn(), + runTimeout: vi.fn(), + clearRun: vi.fn(), }; export const EntityComponentTypes = { @@ -79,32 +78,14 @@ export const CommandPermissionLevel = { Internal: 'Internal', }; -export const DimensionTypes = { getAll: () => [], get: () => undefined }; +export const DimensionTypes = {}; export const ScriptEventSource = {}; export const InputButton = {}; export const ButtonState = {}; -export const GameMode = { survival: 'survival', creative: 'creative', adventure: 'adventure', spectator: 'spectator' }; -export const Direction = { down: 'down', up: 'up', north: 'north', south: 'south', east: 'east', west: 'west' }; -export const LiquidType = {}; -export const FluidType = {}; -export const EquipmentSlot = {}; -export const BlockPistonState = {}; -export const BlockComponentTypes = {}; -export const StructureMirrorAxis = {}; -export const StructureRotation = {}; -export const StructureSaveMode = {}; -export const RawMessage = {}; -export const MolangVariableMap = {}; -export const EntityInitializationCause = {}; export const TicksPerSecond = 20; export const ItemStack = {}; export class Block {} -export class BlockPermutation {} -export class BlockVolume {} -export class Container {} -export class CommandError extends Error {} export class Entity {} -export class EntityItemComponent {} export class Player { - sendMessage = fn(); + sendMessage = vi.fn(); } diff --git a/docs/scripts/mock-hooks.mjs b/docs/scripts/mock-hooks.mjs deleted file mode 100644 index 9f189ea..0000000 --- a/docs/scripts/mock-hooks.mjs +++ /dev/null @@ -1,37 +0,0 @@ -import { fileURLToPath, pathToFileURL } from 'node:url'; -import path from 'node:path'; -import fs from 'node:fs'; - -const __dirname = fileURLToPath(new URL('.', import.meta.url)); -const projectRoot = path.resolve(__dirname, '../../'); -const scriptsRoot = path.join(projectRoot, 'Canopy[BP]/scripts'); - -export async function resolve(specifier, context, nextResolve) { - // Resolve bare path aliases used in BP scripts (lib/ and src/ map to Canopy[BP]/scripts/{lib,src}/) - if (specifier.startsWith('lib/') || specifier.startsWith('src/')) { - const resolved = path.join(scriptsRoot, specifier); - const candidate = fs.existsSync(resolved) ? resolved : resolved + '.js'; - return { url: pathToFileURL(candidate).href, shortCircuit: true }; - } - // Redirect all @minecraft/* packages to mocks in __mocks__/@minecraft/ - if (specifier.startsWith('@minecraft/')) { - const pkgName = specifier.slice('@minecraft/'.length); - const mockPath = path.join(projectRoot, `__mocks__/@minecraft/${pkgName}.js`); - if (fs.existsSync(mockPath)) { - return { url: pathToFileURL(mockPath).href, shortCircuit: true }; - } - } - // Add .js extension for relative imports that resolve to no file (Minecraft BP scripts omit extensions, - // and some files have multi-part names like foo.ipc.js that confuse path.extname) - if (specifier.startsWith('./') || specifier.startsWith('../')) { - const parentDir = context.parentURL ? path.dirname(fileURLToPath(context.parentURL)) : projectRoot; - const resolved = path.resolve(parentDir, specifier); - if (!fs.existsSync(resolved)) { - const candidate = resolved + '.js'; - if (fs.existsSync(candidate)) { - return { url: pathToFileURL(candidate).href, shortCircuit: true }; - } - } - } - return nextResolve(specifier, context); -} diff --git a/docs/scripts/mock-loader.mjs b/docs/scripts/mock-loader.mjs deleted file mode 100644 index 6014997..0000000 --- a/docs/scripts/mock-loader.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import { register } from 'node:module'; - -register('./mock-hooks.mjs', import.meta.url); From 5d277a86668fab3b6e8414c133f04bfe0e3214db Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Fri, 24 Apr 2026 13:09:53 -0700 Subject: [PATCH 37/64] feat: add Vitest-based wiki generator entry point and npm script --- docs/scripts/generate-wiki.test.js | 22 ++++++++++++++++++++++ package.json | 3 ++- vite.config.js | 3 ++- 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 docs/scripts/generate-wiki.test.js diff --git a/docs/scripts/generate-wiki.test.js b/docs/scripts/generate-wiki.test.js new file mode 100644 index 0000000..04e7032 --- /dev/null +++ b/docs/scripts/generate-wiki.test.js @@ -0,0 +1,22 @@ +import { describe, it } from 'vitest'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +// This file is the entry point for wiki generation. Run via: +// WIKI_PATH=../Canopy.wiki npm run generate-wiki +// +// Vitest's Vite aliases automatically resolve @minecraft/server to the mock, +// allowing Canopy source files to be imported in Node.js context. + +describe('Wiki generator', () => { + it('generates wiki pages', async () => { + const wikiPath = process.env.WIKI_PATH; + if (!wikiPath) throw new Error('WIKI_PATH environment variable is required.\nUsage: WIKI_PATH=../Canopy.wiki npm run generate-wiki'); + + const __dirname = fileURLToPath(new URL('.', import.meta.url)); + const resolvedWikiPath = path.resolve(__dirname, wikiPath); + + const { main } = await import('./generate-wiki.js'); + await main(resolvedWikiPath); + }); +}, { timeout: 60000 }); diff --git a/package.json b/package.json index e0a4f7a..d241869 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ }, "scripts": { "test": "vitest run --coverage", - "lint": "eslint . --fix" + "lint": "eslint . --fix", + "generate-wiki": "vitest run docs/scripts/generate-wiki.test.js" }, "devDependencies": { "@eslint/compat": "^1.2.5", diff --git a/vite.config.js b/vite.config.js index d44f64f..754c8c5 100644 --- a/vite.config.js +++ b/vite.config.js @@ -11,6 +11,7 @@ export default defineConfig({ setupFiles: ['./vitest.setup.js'], env: { NODE_ENV: 'test' - } + }, + exclude: ['**/node_modules/**', 'docs/scripts/**'] } }); \ No newline at end of file From acb3e1f8d933db498a08e73c59f18221bbfd57b7 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Fri, 24 Apr 2026 13:17:46 -0700 Subject: [PATCH 38/64] feat: build wiki generator script Creates docs/scripts/generate-wiki.js with main(wikiPath) export that reads Rules/Commands/VanillaCommands registries and writes Global-Rules.md, InfoDisplay-Rules.md, and injects Commands.md. Adds vitest.wiki.config.js for isolated wiki-gen test run and expands @minecraft/server mock with all events, enums, and classes needed when loading the full source tree. Co-Authored-By: Claude Sonnet 4.6 --- __mocks__/@minecraft/debug-utilities.js | 10 ++ __mocks__/@minecraft/server.js | 47 ++++- docs/scripts/generate-wiki.js | 220 ++++++++++++++++++++++++ package.json | 2 +- vitest.wiki.config.js | 22 +++ 5 files changed, 293 insertions(+), 8 deletions(-) create mode 100644 __mocks__/@minecraft/debug-utilities.js create mode 100644 docs/scripts/generate-wiki.js create mode 100644 vitest.wiki.config.js diff --git a/__mocks__/@minecraft/debug-utilities.js b/__mocks__/@minecraft/debug-utilities.js new file mode 100644 index 0000000..3d82b87 --- /dev/null +++ b/__mocks__/@minecraft/debug-utilities.js @@ -0,0 +1,10 @@ +import { vi } from 'vitest'; + +export const debugDrawer = { + addShape: vi.fn(), + removeShape: vi.fn(), +}; + +export class DebugText {} +export class DebugBox {} +export class DebugShape {} diff --git a/__mocks__/@minecraft/server.js b/__mocks__/@minecraft/server.js index 92cb21f..933a0c0 100644 --- a/__mocks__/@minecraft/server.js +++ b/__mocks__/@minecraft/server.js @@ -3,16 +3,36 @@ import { vi } from 'vitest'; export const world = { beforeEvents: { - chatSend: { subscribe: vi.fn() }, + chatSend: { subscribe: vi.fn(), unsubscribe: vi.fn() }, playerPlaceBlock: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - entityRemove: { subscribe: vi.fn() }, - playerLeave: { subscribe: vi.fn() }, + playerBreakBlock: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + entityRemove: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + playerLeave: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + explosion: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + playerInteractWithBlock: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + playerInteractWithEntity: { subscribe: vi.fn(), unsubscribe: vi.fn() }, }, afterEvents: { - worldLoad: { subscribe: vi.fn() }, - entitySpawn: { subscribe: vi.fn() }, + worldLoad: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + entitySpawn: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + entityRemove: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + entityDie: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + entityHitEntity: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + entityHurt: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + entityLoad: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + effectAdd: { subscribe: vi.fn(), unsubscribe: vi.fn() }, playerInventoryItemChange: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - pistonActivate: { subscribe: vi.fn() }, + pistonActivate: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + playerBreakBlock: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + playerDimensionChange: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + playerGameModeChange: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + playerInteractWithBlock: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + playerInteractWithEntity: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + playerJoin: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + playerLeave: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + playerPlaceBlock: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + pressurePlatePush: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + projectileHitEntity: { subscribe: vi.fn(), unsubscribe: vi.fn() }, }, getDimension: vi.fn(), getDynamicProperty: vi.fn(), @@ -26,6 +46,7 @@ export const system = { currentTick: 0, beforeEvents: { startup: { subscribe: vi.fn(), unsubscribe: vi.fn() }, + shutdown: { subscribe: vi.fn(), unsubscribe: vi.fn() }, }, afterEvents: { scriptEventReceive: { subscribe: vi.fn(), unsubscribe: vi.fn() }, @@ -78,12 +99,24 @@ export const CommandPermissionLevel = { Internal: 'Internal', }; -export const DimensionTypes = {}; +export const DimensionTypes = { getAll: () => [], get: () => undefined }; export const ScriptEventSource = {}; export const InputButton = {}; export const ButtonState = {}; export const TicksPerSecond = 20; export const ItemStack = {}; +export const Direction = { Down: 'Down', Up: 'Up', North: 'North', South: 'South', East: 'East', West: 'West' }; +export const GameMode = { survival: 'survival', creative: 'creative', adventure: 'adventure', spectator: 'spectator' }; +export const EntityInitializationCause = {}; +export const LiquidType = { Water: 'Water', Lava: 'Lava' }; +export const StructureMirrorAxis = { None: 'None', X: 'X', Z: 'Z', XZ: 'XZ' }; +export const StructureRotation = { None: 'None', Rotate90: 'Rotate90', Rotate180: 'Rotate180', Rotate270: 'Rotate270' }; +export const StructureSaveMode = { Memory: 'Memory', World: 'World' }; +export const CommandError = class CommandError extends Error {}; +export class BlockPermutation { + static resolve = vi.fn(); +} +export class Container {} export class Block {} export class Entity {} export class Player { diff --git a/docs/scripts/generate-wiki.js b/docs/scripts/generate-wiki.js new file mode 100644 index 0000000..e9e35e9 --- /dev/null +++ b/docs/scripts/generate-wiki.js @@ -0,0 +1,220 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = fileURLToPath(new URL('.', import.meta.url)); +const projectRoot = path.resolve(__dirname, '../../'); + +// ── Lang Parser ─────────────────────────────────────────────────────────────── + +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 ''; +} + +// ── Usage String Builder ────────────────────────────────────────────────────── + +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 = 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 = 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 = resolveDescription(e.description, lang); + return `**Usage: \`${usage}\`** \n${desc}${opSuffix}`; + }).join('\n\n'); +} + +// ── Rules Page Generator ────────────────────────────────────────────────────── + +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---\n\n'); + return `**Table of Contents:**\n${toc}\n\n---\n\n${entries}`; +} + +// ── Commands.md Injector ────────────────────────────────────────────────────── + +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; +} + +// ── Main ────────────────────────────────────────────────────────────────────── + +export async function main(wikiPath) { + // Import registries and main.js — main.js imports all rule and command files, + // populating Rules.rulesToRegister, Commands, and VanillaCommands. + const { Rules } = await import('../../Canopy[BP]/scripts/lib/canopy/rules/Rules.js'); + const { VanillaCommands } = await import('../../Canopy[BP]/scripts/lib/canopy/commands/VanillaCommands.js'); + const { Commands } = await import('../../Canopy[BP]/scripts/lib/canopy/commands/Commands.js'); + await import('../../Canopy[BP]/scripts/main.js'); + + const lang = parseLangFile(path.join(projectRoot, 'Canopy[RP]/texts/en_US.lang')); + + // Build command map: shortName -> { instance, isVanilla } + const commandMap = new Map(); + for (const cmd of Commands.getAll()) { + if (cmd.getExtension()) continue; // skip extension commands + commandMap.set(cmd.getName(), { instance: cmd, isVanilla: false }); + } + for (const cmd of VanillaCommands.getAll()) { + commandMap.set(cmd.getName(), { instance: cmd, isVanilla: true }); + } + + // Collect rules (worldLoad never fires, so rules stay in rulesToRegister) + const allRules = Rules.rulesToRegister; + const globalRules = allRules.filter(r => r.getCategory() === 'Rules'); + const infoDisplayRules = allRules.filter(r => r.getCategory() === 'InfoDisplay'); + + // Generate and write rules pages + 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'); + + // Inject Commands.md + 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'); +} diff --git a/package.json b/package.json index d241869..e0ead66 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "scripts": { "test": "vitest run --coverage", "lint": "eslint . --fix", - "generate-wiki": "vitest run docs/scripts/generate-wiki.test.js" + "generate-wiki": "vitest run --config vitest.wiki.config.js" }, "devDependencies": { "@eslint/compat": "^1.2.5", diff --git a/vitest.wiki.config.js b/vitest.wiki.config.js new file mode 100644 index 0000000..885126a --- /dev/null +++ b/vitest.wiki.config.js @@ -0,0 +1,22 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + resolve: { + alias: { + '@minecraft/server': `${__dirname}/__mocks__/@minecraft/server`, + '@minecraft/server-ui': `${__dirname}/__mocks__/@minecraft/server-ui`, + '@minecraft/debug-utilities': `${__dirname}/__mocks__/@minecraft/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: { + setupFiles: ['./vitest.setup.js'], + env: { + NODE_ENV: 'test' + }, + include: ['docs/scripts/generate-wiki.test.js'] + } +}); From f60110b1302e45c74f88d072ffb8fd4d1a1491be Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Fri, 24 Apr 2026 13:19:17 -0700 Subject: [PATCH 39/64] fix: use Vitest 4-compatible timeout syntax in generate-wiki.test.js --- docs/scripts/generate-wiki.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/scripts/generate-wiki.test.js b/docs/scripts/generate-wiki.test.js index 04e7032..e9a82aa 100644 --- a/docs/scripts/generate-wiki.test.js +++ b/docs/scripts/generate-wiki.test.js @@ -9,7 +9,7 @@ import { fileURLToPath } from 'node:url'; // allowing Canopy source files to be imported in Node.js context. describe('Wiki generator', () => { - it('generates wiki pages', async () => { + it('generates wiki pages', { timeout: 60000 }, async () => { const wikiPath = process.env.WIKI_PATH; if (!wikiPath) throw new Error('WIKI_PATH environment variable is required.\nUsage: WIKI_PATH=../Canopy.wiki npm run generate-wiki'); @@ -19,4 +19,4 @@ describe('Wiki generator', () => { const { main } = await import('./generate-wiki.js'); await main(resolvedWikiPath); }); -}, { timeout: 60000 }); +}); From f99a8c6444943abc491ea076b58ba1b1a40ab9ab Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Fri, 24 Apr 2026 13:39:32 -0700 Subject: [PATCH 40/64] docs: update lang strings and camera descriptions to match wiki prose Update en_US.lang command descriptions to preserve the original hand-written wiki formatting, and update camera.js subCommandWikiDescription strings to match the original wiki text for the /cam and /cs commands. Co-Authored-By: Claude Sonnet 4.6 --- Canopy[BP]/scripts/src/commands/camera.js | 6 +- Canopy[RP]/texts/en_US.lang | 112 +++++++++++----------- 2 files changed, 60 insertions(+), 58 deletions(-) diff --git a/Canopy[BP]/scripts/src/commands/camera.js b/Canopy[BP]/scripts/src/commands/camera.js index 123e909..5dde922 100644 --- a/Canopy[BP]/scripts/src/commands/camera.js +++ b/Canopy[BP]/scripts/src/commands/camera.js @@ -39,15 +39,15 @@ new VanillaCommand({ callback: cameraCommand, subCommandWikiDescription: { 'place': { - description: 'Places a camera at your current location for later viewing.', + description: 'Places down a camera for you to view later on.', params: [] }, 'view': { - description: 'Toggles viewing your placed camera. Your player can still move and interact while viewing.', + 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. Switches you to spectator mode with night vision and conduit power. Run again to return to your original position.', + 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: [] }, }, diff --git a/Canopy[RP]/texts/en_US.lang b/Canopy[RP]/texts/en_US.lang index a970e16..9d525f8 100644 --- a/Canopy[RP]/texts/en_US.lang +++ b/Canopy[RP]/texts/en_US.lang @@ -18,7 +18,7 @@ commands.generic.blocked.survival=§cThis command cannot be used in survival mod commands.generic.invalidsource=§cThis command cannot be run from this source. commands.generic.invalidaction=§cInvalid action. Use /help for more information. -commands.help=Displays help pages. +commands.help=Displays every command'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. commands.help.search.noresult=§cNo results found for '%s'. commands.help.search.results=§l§aCanopy§r §2Help search results for '§r%1§2':%2 commands.help.page.header=§l§aCanopy§r§2 Help Page: §f%1 @@ -35,14 +35,16 @@ commands.biomeedges.notfinding=§cThere are no biome edges regions. commands.biomeedges.missinglocations=§cPlease provide both from and to locations. commands.biomeedges.overcapacity=§cToo many blocks in the specified area. (%1 > %2) -commands.butcher=Instantly remove selected entities or the one you are targeting. +commands.butcher=Instantly removes entities without dropping their items. If the entity argument is unspecified, the entity you are looking at is assumed. commands.butcher.fail.player=§cCannot remove players. commands.butcher.fail.noneremoved=§cNo entities removed. commands.butcher.success=§7Removed %1. commands.butcher.success.many=§7Removed %1 entities: commands.camera=Place a camera, toggle viewing your placed camera, or toggle a survival-friendly spectator mode. -commands.camera.spectate=Toggle a survival-friendly spectator mode. +commands.camera.spectate=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`** +commands.camera.place=Places down a camera for you to view later on. +commands.camera.view=Toggles viewing your placed camera. Your player will still be able to move around and interact as normal while viewing your placed camera. commands.camera.place.viewing=§cYou cannot place a camera while viewing one. commands.camera.place.success=§7Camera placed at %s. commands.camera.view.spectating=§cYou cannot view a camera while spectating. @@ -59,57 +61,57 @@ commands.camera.spectate.flying=§cYou must be on the ground to spectate. commands.camera.invalidaction=§cInvalid camera action. commands.canopy=Enable or disable a rule. -commands.canopy.version=Displays the current version of Canopy and all loaded extensions. +commands.canopy.version=Displays information about the active **Canopy** installation. commands.canopy.version.message=§7This server is running §l§aCanopy§r commands.canopy.version.extensions=§7Loaded extensions: -commands.canopy.menu=Displays a menu to toggle rules. +commands.canopy.menu=Displays a menu with toggles for every rule. Flip the switches for the ones you want and hit the submit button at the bottom to save your changes. commands.canopy.menu.busy=§8Close your chat window to access the form. commands.canopy.menu.timeout=§8Form timed out after %s ticks. commands.canopy.menu.canceled=§8Form canceled. Rules were not updated. commands.canopy.menu.submit=§aApply -commands.canopy.single=Modifies the value of a single rule. -commands.canopy.multiple=Modifies the value of multiple rules. +commands.canopy.single=Sets the value of a Global Rule. You can also use list syntax to change multiple rules at once (ex. `./canopy [ruleOne,ruleTwo] true`). +commands.canopy.multiple=Sets the value of multiple Global Rules at once using list syntax. commands.canopy.infodisplayRule=§cThe rule '%1' is part of the InfoDisplay, and must be toggled using %2info. Use %2help for more information. -commands.changedimension=Teleports entities to the specified dimension. +commands.changedimension=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. commands.changedimension.notfound=§cInvalid dimension. commands.changedimension.success.coords=§7Teleported to %1 in the %2§7. commands.changedimension.success=§7Changed to the %s§7. commands.changedimension.fail.coords=§cInvalid coordinates. Please provide all x, y, z numeric values or none. -commands.claimprojectiles=Changes the owner of all projectiles within a radius. +commands.claimprojectiles=Makes you (or another specified player) the owner of all projectiles within a certain block radius. If no radius is specified, the default is 25 blocks. commands.claimprojectiles.fail.sourcenotplayer=§cSpecify a player to use this command. commands.claimprojectiles.fail.nonefound=§7No projectiles found in range (%s blocks). commands.claimprojectiles.success.self=§7Successfully became the owner of %1 projectiles within %2 blocks of you. commands.claimprojectiles.success.other=§7Successfully changed the owner of %1 projectiles within %2 blocks of %3. -commands.cleanup=Removes all items and experience orbs within a radius. +commands.cleanup=Removes all items and xp orbs within 50 blocks, or at a specified distance. Alias: **`/k`** 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=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`** and **`./ct `** This command can also be triggered with the vanilla command `/scriptevent canopy:counter [color]` (ie. in a command block). 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. -commands.counter.mode=Sets the mode of a hopper counter. +commands.counter.realtime=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`** +commands.counter.mode=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 `** commands.counter.mode.notfound=§cInvalid mode: '%1'. Please use one of the following modes: %2 commands.counter.mode.single=§7Hopper Counter %1§7 mode: %2 commands.counter.mode.single.actionbar=[%1] Set %2 hopper counter mode to %3 commands.counter.mode.all=§7All Hopper Counters mode: %s commands.counter.mode.all.actionbar=[%1] Set all hopper counters mode to %2 -commands.counter.reset=Resets all hopper counters and restarts the timer. +commands.counter.reset=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). commands.counter.reset.single=§7Reset and time restarted: %s commands.counter.reset.single.actionbar=[%1] Reset %2 hopper counter. commands.counter.reset.all=§7All channels have been reset and hopper counter timer started. commands.counter.reset.all.actionbar=[%s] Reset all hopper counters. -commands.counter.remove=Removes all hoppers in the specified channel. +commands.counter.remove=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). commands.counter.remove.single=§7Removed all hoppers in %s. commands.counter.remove.single.actionbar=[%1] Removed all hoppers in %2 commands.counter.remove.all=§7Removed all hoppers in all channels. commands.counter.remove.all.actionbar=[%s] Removed all hoppers in all channels. -commands.data=Displays information about a block or entity you are targeting or one you select. +commands.data=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. commands.data.notarget.id=§cNo entity found with id '%2'. commands.data.properties=§aProperties:§r %s commands.data.states=§aStates:§r %s @@ -126,18 +128,18 @@ commands.debugentity.added=§7Added '%1' to the debug display for %2 entities: commands.debugentity.removed=§7Removed '%1' from the debug display for %2 entities: commands.distance=Calculates the distance between two locations. (Alias: d) -commands.distance.target=Calculates distance between you and the block or entity you are targeting. -commands.distance.fromto=Calculates the distance between two locations. -commands.distance.from=Saves a location to calculate distance from. +commands.distance.target=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`** +commands.distance.fromto=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]`** +commands.distance.from=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]`** commands.distance.from.success=§7Saved location: %s -commands.distance.to=Calculates the distance from the saved location to the specified location. +commands.distance.to=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]`** commands.distance.to.fail.nosave=§cNo saved location found. Save a location with: %sdistance from [x y z] commands.distance.target.notfound=§cNo block or entity found to calculate distance from. commands.distance.cartesian=§7Cartesian: §r§l%s§r commands.distance.cylindrical=§7Cartesian(XZ): §r§l%s§r commands.distance.manhattan=§7§7Manhattan: §r§l%s§r -commands.entitydensity=Find entity-dense areas in a dimension. +commands.entitydensity=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. commands.entitydensity.fail.noentities=§7No dense areas found in %s§r§7. No entities in the dimension? commands.entitydensity.fail.dimension=§cInvalid dimension. Please use one of these: %s commands.entitydensity.fail.gridsize=§cInvalid grid size. Please use a value between 1 and 2048. Recommended: 100-1024. @@ -151,22 +153,22 @@ 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=Displays the count and rates of the hopper generator for the specified color. +commands.generator.query=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`** and **`./gt `** This command can also be triggered with the vanilla command `/scriptevent canopy:generator [color]` (ie. in a command block). 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): -commands.generator.realtime=Displays count and rates, but uses real-world time instead of tick-based time. -commands.generator.reset=Resets all hopper generators and restarts the timer. +commands.generator.realtime=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`** +commands.generator.reset=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). commands.generator.reset.single=§7Reset and time restarted: %s commands.generator.reset.single.actionbar=[%1] Reset %2 hopper generator. commands.generator.reset.all=§7All channels have been reset and hopper generator timer started. commands.generator.reset.all.actionbar=[%s] Reset all hopper generators. -commands.generator.remove=Removes all hopper generators in the specified channel. +commands.generator.remove=Removes all known hopper generators 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). commands.generator.remove.single=§7Removed all hopper generators in %s. commands.generator.remove.single.actionbar=[%1] Removed all hopper generators in %2 commands.generator.remove.all=§7Removed all hopper generators in all channels. commands.generator.remove.all.actionbar=[%s] Removed all hopper generators in all channels. -commands.health=Displays the server's current TPS, MSPT, and entity counts. +commands.health=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. commands.health.startprofile=§7Profiling tick time... commands.health.fail.mspt=§cCould not compute MSPT. Please report this error. @@ -182,31 +184,31 @@ commands.hss.alreadyrunning=§cYou are already finding fortress hardcoded spawn commands.hss.notrunning=§cYou are not currently finding fortress hardcoded spawn spots. commands.info=Toggle InfoDisplay rules. (Alias: i) -commands.info.menu=Displays a menu to toggle InfoDisplay rules. -commands.info.single=Toggle a single InfoDisplay rule. -commands.info.multiple=Toggle multiple InfoDisplay rules. -commands.info.all=Toggle all InfoDisplay rules. +commands.info.menu=Displays a menu with toggles for every InfoDisplay rule. Flip the switches for the ones you want and hit the submit button at the bottom to save your changes. +commands.info.single=Enables/disables an InfoDisplay rule. You can also use list syntax to change multiple rules at once (ex. `./info [ruleOne,ruleTwo] true`). Alias: **`./i `** +commands.info.multiple=Enables/disables multiple InfoDisplay rules at once using list syntax. +commands.info.all=Enables/disables all InfoDisplay rules at once. commands.info.allupdated=§7All InfoDisplay rules are now §l commands.info.canopyRule=§cThe rule '%1' is global rule, and must be toggled using %2canopy. Use %2help for more information. -commands.jump=Teleport to the block you are targeting. (Alias: j) +commands.jump=Teleports you to the block you are currently looking at with a maximum range of 64 chunks. Jumping is disabled in Survival mode unless the `commandJumpSurvival` rule is enabled. Alias: **`/j`** commands.jump.fail.noblock=§cNo block found to jump to. -commands.log=Log tnt, projectile, and falling block movement. +commands.log=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. commands.log.precision=§7Log precision set to %s. commands.log.started=§7Started logging %s. commands.log.stopped=§7Stopped logging %s. commands.log.invalidtype=§cInvalid log type. -commands.lifetime.tracking=Start and stop tracking entity lifetime and spawn/removal reasons. +commands.lifetime.tracking=Starts, stops, or restarts lifetime tracking. commands.lifetime.tracking.unknownaction=§cUnknown tracking action. commands.lifetime.tracking.already=§cLifetimes are already being tracked. commands.lifetime.tracking.not=§cLifetimes are not currently being tracked. commands.lifetime.tracking.start=§aStarted entity lifetime tracking. commands.lifetime.tracking.stop=§aStopped entity lifetime tracking. commands.lifetime.tracking.restart=§aRestarted entity lifetime tracking. -commands.lifetime.query=Query detailed entity lifetime and spawn/removal reasons statistics. -commands.lifetime.query.item=Query detailed item entity lifetime and spawn/removal reasons statistics. +commands.lifetime.query=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. +commands.lifetime.query.item=The same as `/lifetimequery`, but for items instead of entities. commands.lifetime.query.invalidaction=§cPlease enter a valid query action. commands.lifetime.query.invalidentity=§cPlease enter a valid entity type. commands.lifetime.query.header=§lLifetime Statistics§r (tracked %s min of @@ -228,26 +230,26 @@ commands.lifetime.query.realtime.unit= sec commands.lifetime.query.ticktime= ingame time) commands.lifetime.query.ticktime.unit= gt -commands.loop=Run a vanilla command multiple times in a single tick. +commands.loop=Runs the specified command a certain number of times in a single tick. Note that the looped command must be encased in quotes. -commands.peek=Peek into a target's inventory and optionally highlight items that match a query. +commands.peek=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. commands.peek.fail.unloaded=§cTarget at %s is unloaded. commands.peek.fail.noinventory=§cNo inventory found in %1 at %2. commands.peek.fail.noitems=§cNo items found in %1 at %2. commands.peek.query.cleared=§7Peek query cleared. commands.peek.query.set=§7Peek query set to '%s'. -commands.pos=Shows your current position, or the positions of other players. +commands.pos=Displays your current position, or the position of another player. Shows dimension and relative nether/overworld position as well. commands.pos.self=§aYour position: §f%s commands.pos.other=§a%1's position: §f%2 commands.pos.dimension=§7Dimension: §7%s commands.pos.relative.overworld=§7Relative Overworld position: §a%s commands.pos.relative.nether=§7Relative Nether position: §c%s -commands.retest=Resets spawn tracking, hopper counters, and hopper generators. +commands.retest=Resets spawn tracking, hopper counters, and hopper generators. This is great if you want to restart a test for a machine or farm! commands.retest.success=§7Reset spawn counters, hopper counters, and hopper generators. -commands.simmap=Displays a map of the loaded chunks near you or the specified location. +commands.simmap=Displays a map showing the simulation status of chunks in an area. Ticking chunks are displayed in green and non-ticking chunks are displayed in red. The dimension and x/z coordinates configure the center of the map. The distance argument sets the size of the area to display (in chunks away from the center). The display argument allows you to configure these settings for the simulation map in your InfoDisplay. Using "display here" will reset your display settings to follow your player. commands.simmap.help.distance=Displays the map with a radius of chunks equal to the specified distance. commands.simmap.help.location=Display the map around the specified location. commands.simmap.help.display.set=Sets the distance or location for the simulation map in your InfoDisplay. @@ -262,29 +264,29 @@ commands.sit=Makes your player sit down. commands.sit.busy=§cYou are busy and cannot sit down. commands.spawn=Spawn command for tracking and mocking spawns. -commands.spawn.entities=Displays a list of all entities & their positions in the world. +commands.spawn.entities=Displays a list of all entities in the world and their locations. commands.spawn.recent=Displays all mob spawns from the last 30s. Specify a mob name to filter. -commands.spawn.tracking.start=Starts tracking mob spawns. Specify coords to track within an area. +commands.spawn.tracking.start=Starts spawn tracking. Specify coordinates to track a specific area. commands.spawn.tracking.start.success=§7Spawns are now being tracked. commands.spawn.tracking.start.mob=§7Spawns are now being tracked for: %s. commands.spawn.tracking.start.mob.actionbar=[%1] §aAdded %2 to spawn tracking and reset. commands.spawn.tracking.start.area= Area: %1 to %2. commands.spawn.tracking.start.mocking= Since spawn mocking is enabled, mobs will not spawn but they will be tracked. commands.spawn.tracking.start.actionbar=[%s] §7Started tracking spawns. -commands.spawn.tracking.mob=Starts tracking a specific mob spawn. Specify coords to track within an area. Run again to add more mob types to track. +commands.spawn.tracking.mob=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. commands.spawn.tracking.mob.invalid=§cInvalid mob name: %s -commands.spawn.tracking.query=Displays a summary of all spawns that have occurred since the start of your test. +commands.spawn.tracking.query=Displays statistics about mob spawning since spawn tracking started. Mob categories are based on population control. commands.spawn.tracking.query.dimension=§7Dimension %s§r: commands.spawn.tracking.no=§cSpawns are not currently being tracked. commands.spawn.tracking.already=§cSpawns are already being tracked. commands.spawn.tracking.test=Resets all spawn counters. commands.spawn.tracking.test.success=§7Spawn counters reset. commands.spawn.tracking.test.success.actionbar=[%s] §7Reset spawn counters. -commands.spawn.tracking.stop=Stops tracking mob spawns. +commands.spawn.tracking.stop=Displays statistics about mob spawning since spawn tracking started and then stops spawn tracking. commands.spawn.tracking.stop.success=§7Spawns are no longer being tracked. commands.spawn.tracking.stop.actionbar=[%s] §7Stopped tracking spawns. commands.spawn.reset=Resets all spawn counters. -commands.spawn.mocking=Enables/disables mob spawning while allowing the spawning algorithm to run. +commands.spawn.mocking=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. commands.spawn.mocking.enable=§aSpawn mocking is now enabled. Mobs will no longer spawn, but the spawning algorithm will continue. commands.spawn.mocking.disable=§cSpawn mocking is now disabled. Mobs will spawn as normal. commands.spawn.mocking.enable.actionbar=[%s] §aSpawn mocking enabled. @@ -295,16 +297,16 @@ commands.summontnt.fail.none=§cNo TNT summoned. commands.summontnt.success=§7Summoned §c%s TNT§7. commands.tick=Set and control the server tick speed. -commands.tick.mspt=Slows down the server tick speed to the specified mspt. +commands.tick.mspt=Sets the world's tick speed to the desired mspt. There will always be some extra. commands.tick.mspt.fail=§cMSPT cannot be less than 50.0. commands.tick.mspt.success=§7%1 set the tick speed to %2 mspt. -commands.tick.step=Allows the server to run at normal speed for the specified amount of steps. +commands.tick.step=Makes the world run at normal speed for the specified number of ticks. Leave the `[steps]` argument blank to step only one tick. commands.tick.step.fail=§cCannot step ticks without setting a tick speed. commands.tick.step.start=§7%1 stepping %2 tick(s)... commands.tick.step.done=§7Tick step complete. -commands.tick.reset=Resets the server tick speed to normal. +commands.tick.reset=Resets the world's tick speed to normal. commands.tick.reset.success=§7%s reset the tick speed. -commands.tick.sleep=Pauses the server for the specified amount of milliseconds. +commands.tick.sleep=Pauses the game for the specified number of milliseconds. Can be run through a command block using `/scriptevent canopy:tick sleep [milliseconds]`. commands.tick.sleep.fail=§cInvalid sleep time. commands.tick.sleep.success=§7%1 pausing the server for %2 ms. @@ -313,21 +315,21 @@ commands.tntfuse.reset.success=§7Reset TNT fuse time to §a80§7 ticks. commands.tntfuse.set.fail=§cInvalid fuse time: %1 ticks. Must be between %2 and %3 ticks. commands.tntfuse.set.success=§7TNT fuse time set to §a%s§7 ticks. -commands.trackevent=Count the number of times any event occurs. Displays the count in the InfoDisplay. +commands.trackevent=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. commands.trackevent.stop=§7Stopped tracking %s. commands.trackevent.start=§7Started tracking %s. commands.trackevent.invalid=§cEvent %1 not found in %2. -commands.velocity=Affect the velocity of entities. +commands.velocity=Queries or modifies the velocity of the specified entity. commands.velocity.missingvelocity=§cPlease enter x, y, and z velocities. commands.velocity.query=§7Entity velocities (m/gt): commands.velocity.add=§7Added velocity to entities (m/gt): commands.velocity.set=§7Set velocity for entities (m/gt): commands.warp=Teleport to and manage warps. (Alias: w) -commands.warp.edit=Adds or removes a warp. -commands.warp.tp=Teleports you to a warp. -commands.warp.list=List all available warps. +commands.warp.edit=Adds or removes a warp. Alias: **`./w `** +commands.warp.tp=Teleports you to a warp. Alias: **`./w `** +commands.warp.list=Lists all available warps in chat. commands.warp.exists=§cWarp '%s' already exists. Use ./warps to see the list of warps. commands.warp.noexist=§cWarp '%s' not found. Use ./warps to see the list of warps. commands.warp.add.success=§7Warp '%s' has been added. From 14237e9be6a6b7fa2676096d1eb14b188fd7a707 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Fri, 24 Apr 2026 13:41:16 -0700 Subject: [PATCH 41/64] ci: add wiki generation workflow on push to main --- .github/workflows/wiki.yml | 46 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/workflows/wiki.yml diff --git a/.github/workflows/wiki.yml b/.github/workflows/wiki.yml new file mode 100644 index 0000000..7453dfb --- /dev/null +++ b/.github/workflows/wiki.yml @@ -0,0 +1,46 @@ +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 + uses: actions/checkout@v4 + with: + repository: ${{ github.repository }}.wiki + path: wiki + token: ${{ secrets.GITHUB_TOKEN }} + + - 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 From 1190e8098b5cbe85435ee8ab2f553d23027053d9 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Fri, 24 Apr 2026 13:46:56 -0700 Subject: [PATCH 42/64] revert: restore en_US.lang in-game descriptions to original short form --- Canopy[RP]/texts/en_US.lang | 112 ++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 57 deletions(-) diff --git a/Canopy[RP]/texts/en_US.lang b/Canopy[RP]/texts/en_US.lang index 9d525f8..a970e16 100644 --- a/Canopy[RP]/texts/en_US.lang +++ b/Canopy[RP]/texts/en_US.lang @@ -18,7 +18,7 @@ commands.generic.blocked.survival=§cThis command cannot be used in survival mod commands.generic.invalidsource=§cThis command cannot be run from this source. commands.generic.invalidaction=§cInvalid action. Use /help for more information. -commands.help=Displays every command'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. +commands.help=Displays help pages. commands.help.search.noresult=§cNo results found for '%s'. commands.help.search.results=§l§aCanopy§r §2Help search results for '§r%1§2':%2 commands.help.page.header=§l§aCanopy§r§2 Help Page: §f%1 @@ -35,16 +35,14 @@ commands.biomeedges.notfinding=§cThere are no biome edges regions. commands.biomeedges.missinglocations=§cPlease provide both from and to locations. commands.biomeedges.overcapacity=§cToo many blocks in the specified area. (%1 > %2) -commands.butcher=Instantly removes entities without dropping their items. If the entity argument is unspecified, the entity you are looking at is assumed. +commands.butcher=Instantly remove selected entities or the one you are targeting. commands.butcher.fail.player=§cCannot remove players. commands.butcher.fail.noneremoved=§cNo entities removed. commands.butcher.success=§7Removed %1. commands.butcher.success.many=§7Removed %1 entities: commands.camera=Place a camera, toggle viewing your placed camera, or toggle a survival-friendly spectator mode. -commands.camera.spectate=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`** -commands.camera.place=Places down a camera for you to view later on. -commands.camera.view=Toggles viewing your placed camera. Your player will still be able to move around and interact as normal while viewing your placed camera. +commands.camera.spectate=Toggle a survival-friendly spectator mode. commands.camera.place.viewing=§cYou cannot place a camera while viewing one. commands.camera.place.success=§7Camera placed at %s. commands.camera.view.spectating=§cYou cannot view a camera while spectating. @@ -61,57 +59,57 @@ commands.camera.spectate.flying=§cYou must be on the ground to spectate. commands.camera.invalidaction=§cInvalid camera action. commands.canopy=Enable or disable a rule. -commands.canopy.version=Displays information about the active **Canopy** installation. +commands.canopy.version=Displays the current version of Canopy and all loaded extensions. commands.canopy.version.message=§7This server is running §l§aCanopy§r commands.canopy.version.extensions=§7Loaded extensions: -commands.canopy.menu=Displays a menu with toggles for every rule. Flip the switches for the ones you want and hit the submit button at the bottom to save your changes. +commands.canopy.menu=Displays a menu to toggle rules. commands.canopy.menu.busy=§8Close your chat window to access the form. commands.canopy.menu.timeout=§8Form timed out after %s ticks. commands.canopy.menu.canceled=§8Form canceled. Rules were not updated. commands.canopy.menu.submit=§aApply -commands.canopy.single=Sets the value of a Global Rule. You can also use list syntax to change multiple rules at once (ex. `./canopy [ruleOne,ruleTwo] true`). -commands.canopy.multiple=Sets the value of multiple Global Rules at once using list syntax. +commands.canopy.single=Modifies the value of a single rule. +commands.canopy.multiple=Modifies the value of multiple rules. commands.canopy.infodisplayRule=§cThe rule '%1' is part of the InfoDisplay, and must be toggled using %2info. Use %2help for more information. -commands.changedimension=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. +commands.changedimension=Teleports entities to the specified dimension. commands.changedimension.notfound=§cInvalid dimension. commands.changedimension.success.coords=§7Teleported to %1 in the %2§7. commands.changedimension.success=§7Changed to the %s§7. commands.changedimension.fail.coords=§cInvalid coordinates. Please provide all x, y, z numeric values or none. -commands.claimprojectiles=Makes you (or another specified player) the owner of all projectiles within a certain block radius. If no radius is specified, the default is 25 blocks. +commands.claimprojectiles=Changes the owner of all projectiles within a radius. commands.claimprojectiles.fail.sourcenotplayer=§cSpecify a player to use this command. commands.claimprojectiles.fail.nonefound=§7No projectiles found in range (%s blocks). commands.claimprojectiles.success.self=§7Successfully became the owner of %1 projectiles within %2 blocks of you. commands.claimprojectiles.success.other=§7Successfully changed the owner of %1 projectiles within %2 blocks of %3. -commands.cleanup=Removes all items and xp orbs within 50 blocks, or at a specified distance. Alias: **`/k`** +commands.cleanup=Removes all items and experience orbs within a radius. 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 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`** and **`./ct `** This command can also be triggered with the vanilla command `/scriptevent canopy:counter [color]` (ie. in a command block). +commands.counter.query=Displays the count and rates of the hopper counter for the specified color. 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 information about the item counts using real-world time instead of Minecraft tick-based time to do rate calculations. Alias: **`./ct realtime`**, **`./ct realtime`** -commands.counter.mode=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 `** +commands.counter.realtime=Displays count and rates, but uses real-world time instead of tick-based time. +commands.counter.mode=Sets the mode of a hopper counter. commands.counter.mode.notfound=§cInvalid mode: '%1'. Please use one of the following modes: %2 commands.counter.mode.single=§7Hopper Counter %1§7 mode: %2 commands.counter.mode.single.actionbar=[%1] Set %2 hopper counter mode to %3 commands.counter.mode.all=§7All Hopper Counters mode: %s commands.counter.mode.all.actionbar=[%1] Set all hopper counters mode to %2 -commands.counter.reset=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). +commands.counter.reset=Resets all hopper counters and restarts the timer. commands.counter.reset.single=§7Reset and time restarted: %s commands.counter.reset.single.actionbar=[%1] Reset %2 hopper counter. commands.counter.reset.all=§7All channels have been reset and hopper counter timer started. commands.counter.reset.all.actionbar=[%s] Reset all hopper counters. -commands.counter.remove=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). +commands.counter.remove=Removes all hoppers in the specified channel. commands.counter.remove.single=§7Removed all hoppers in %s. commands.counter.remove.single.actionbar=[%1] Removed all hoppers in %2 commands.counter.remove.all=§7Removed all hoppers in all channels. commands.counter.remove.all.actionbar=[%s] Removed all hoppers in all channels. -commands.data=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. +commands.data=Displays information about a block or entity you are targeting or one you select. commands.data.notarget.id=§cNo entity found with id '%2'. commands.data.properties=§aProperties:§r %s commands.data.states=§aStates:§r %s @@ -128,18 +126,18 @@ commands.debugentity.added=§7Added '%1' to the debug display for %2 entities: commands.debugentity.removed=§7Removed '%1' from the debug display for %2 entities: commands.distance=Calculates the distance between two locations. (Alias: d) -commands.distance.target=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`** -commands.distance.fromto=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]`** -commands.distance.from=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]`** +commands.distance.target=Calculates distance between you and the block or entity you are targeting. +commands.distance.fromto=Calculates the distance between two locations. +commands.distance.from=Saves a location to calculate distance from. commands.distance.from.success=§7Saved location: %s -commands.distance.to=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]`** +commands.distance.to=Calculates the distance from the saved location to the specified location. commands.distance.to.fail.nosave=§cNo saved location found. Save a location with: %sdistance from [x y z] commands.distance.target.notfound=§cNo block or entity found to calculate distance from. commands.distance.cartesian=§7Cartesian: §r§l%s§r commands.distance.cylindrical=§7Cartesian(XZ): §r§l%s§r commands.distance.manhattan=§7§7Manhattan: §r§l%s§r -commands.entitydensity=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. +commands.entitydensity=Find entity-dense areas in a dimension. commands.entitydensity.fail.noentities=§7No dense areas found in %s§r§7. No entities in the dimension? commands.entitydensity.fail.dimension=§cInvalid dimension. Please use one of these: %s commands.entitydensity.fail.gridsize=§cInvalid grid size. Please use a value between 1 and 2048. Recommended: 100-1024. @@ -153,22 +151,22 @@ 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=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`** and **`./gt `** This command can also be triggered with the vanilla command `/scriptevent canopy:generator [color]` (ie. in a command block). +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): -commands.generator.realtime=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`** -commands.generator.reset=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). +commands.generator.realtime=Displays count and rates, but uses real-world time instead of tick-based time. +commands.generator.reset=Resets all hopper generators and restarts the timer. commands.generator.reset.single=§7Reset and time restarted: %s commands.generator.reset.single.actionbar=[%1] Reset %2 hopper generator. commands.generator.reset.all=§7All channels have been reset and hopper generator timer started. commands.generator.reset.all.actionbar=[%s] Reset all hopper generators. -commands.generator.remove=Removes all known hopper generators 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). +commands.generator.remove=Removes all hopper generators in the specified channel. commands.generator.remove.single=§7Removed all hopper generators in %s. commands.generator.remove.single.actionbar=[%1] Removed all hopper generators in %2 commands.generator.remove.all=§7Removed all hopper generators in all channels. commands.generator.remove.all.actionbar=[%s] Removed all hopper generators in all channels. -commands.health=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. +commands.health=Displays the server's current TPS, MSPT, and entity counts. commands.health.startprofile=§7Profiling tick time... commands.health.fail.mspt=§cCould not compute MSPT. Please report this error. @@ -184,31 +182,31 @@ commands.hss.alreadyrunning=§cYou are already finding fortress hardcoded spawn commands.hss.notrunning=§cYou are not currently finding fortress hardcoded spawn spots. commands.info=Toggle InfoDisplay rules. (Alias: i) -commands.info.menu=Displays a menu with toggles for every InfoDisplay rule. Flip the switches for the ones you want and hit the submit button at the bottom to save your changes. -commands.info.single=Enables/disables an InfoDisplay rule. You can also use list syntax to change multiple rules at once (ex. `./info [ruleOne,ruleTwo] true`). Alias: **`./i `** -commands.info.multiple=Enables/disables multiple InfoDisplay rules at once using list syntax. -commands.info.all=Enables/disables all InfoDisplay rules at once. +commands.info.menu=Displays a menu to toggle InfoDisplay rules. +commands.info.single=Toggle a single InfoDisplay rule. +commands.info.multiple=Toggle multiple InfoDisplay rules. +commands.info.all=Toggle all InfoDisplay rules. commands.info.allupdated=§7All InfoDisplay rules are now §l commands.info.canopyRule=§cThe rule '%1' is global rule, and must be toggled using %2canopy. Use %2help for more information. -commands.jump=Teleports you to the block you are currently looking at with a maximum range of 64 chunks. Jumping is disabled in Survival mode unless the `commandJumpSurvival` rule is enabled. Alias: **`/j`** +commands.jump=Teleport to the block you are targeting. (Alias: j) commands.jump.fail.noblock=§cNo block found to jump to. -commands.log=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. +commands.log=Log tnt, projectile, and falling block movement. commands.log.precision=§7Log precision set to %s. commands.log.started=§7Started logging %s. commands.log.stopped=§7Stopped logging %s. commands.log.invalidtype=§cInvalid log type. -commands.lifetime.tracking=Starts, stops, or restarts lifetime tracking. +commands.lifetime.tracking=Start and stop tracking entity lifetime and spawn/removal reasons. commands.lifetime.tracking.unknownaction=§cUnknown tracking action. commands.lifetime.tracking.already=§cLifetimes are already being tracked. commands.lifetime.tracking.not=§cLifetimes are not currently being tracked. commands.lifetime.tracking.start=§aStarted entity lifetime tracking. commands.lifetime.tracking.stop=§aStopped entity lifetime tracking. commands.lifetime.tracking.restart=§aRestarted entity lifetime tracking. -commands.lifetime.query=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. -commands.lifetime.query.item=The same as `/lifetimequery`, but for items instead of entities. +commands.lifetime.query=Query detailed entity lifetime and spawn/removal reasons statistics. +commands.lifetime.query.item=Query detailed item entity lifetime and spawn/removal reasons statistics. commands.lifetime.query.invalidaction=§cPlease enter a valid query action. commands.lifetime.query.invalidentity=§cPlease enter a valid entity type. commands.lifetime.query.header=§lLifetime Statistics§r (tracked %s min of @@ -230,26 +228,26 @@ commands.lifetime.query.realtime.unit= sec commands.lifetime.query.ticktime= ingame time) commands.lifetime.query.ticktime.unit= gt -commands.loop=Runs the specified command a certain number of times in a single tick. Note that the looped command must be encased in quotes. +commands.loop=Run a vanilla command multiple times in a single tick. -commands.peek=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. +commands.peek=Peek into a target's inventory and optionally highlight items that match a query. commands.peek.fail.unloaded=§cTarget at %s is unloaded. commands.peek.fail.noinventory=§cNo inventory found in %1 at %2. commands.peek.fail.noitems=§cNo items found in %1 at %2. commands.peek.query.cleared=§7Peek query cleared. commands.peek.query.set=§7Peek query set to '%s'. -commands.pos=Displays your current position, or the position of another player. Shows dimension and relative nether/overworld position as well. +commands.pos=Shows your current position, or the positions of other players. commands.pos.self=§aYour position: §f%s commands.pos.other=§a%1's position: §f%2 commands.pos.dimension=§7Dimension: §7%s commands.pos.relative.overworld=§7Relative Overworld position: §a%s commands.pos.relative.nether=§7Relative Nether position: §c%s -commands.retest=Resets spawn tracking, hopper counters, and hopper generators. This is great if you want to restart a test for a machine or farm! +commands.retest=Resets spawn tracking, hopper counters, and hopper generators. commands.retest.success=§7Reset spawn counters, hopper counters, and hopper generators. -commands.simmap=Displays a map showing the simulation status of chunks in an area. Ticking chunks are displayed in green and non-ticking chunks are displayed in red. The dimension and x/z coordinates configure the center of the map. The distance argument sets the size of the area to display (in chunks away from the center). The display argument allows you to configure these settings for the simulation map in your InfoDisplay. Using "display here" will reset your display settings to follow your player. +commands.simmap=Displays a map of the loaded chunks near you or the specified location. commands.simmap.help.distance=Displays the map with a radius of chunks equal to the specified distance. commands.simmap.help.location=Display the map around the specified location. commands.simmap.help.display.set=Sets the distance or location for the simulation map in your InfoDisplay. @@ -264,29 +262,29 @@ commands.sit=Makes your player sit down. commands.sit.busy=§cYou are busy and cannot sit down. commands.spawn=Spawn command for tracking and mocking spawns. -commands.spawn.entities=Displays a list of all entities in the world and their locations. +commands.spawn.entities=Displays a list of all entities & their positions in the world. commands.spawn.recent=Displays all mob spawns from the last 30s. Specify a mob name to filter. -commands.spawn.tracking.start=Starts spawn tracking. Specify coordinates to track a specific area. +commands.spawn.tracking.start=Starts tracking mob spawns. Specify coords to track within an area. commands.spawn.tracking.start.success=§7Spawns are now being tracked. commands.spawn.tracking.start.mob=§7Spawns are now being tracked for: %s. commands.spawn.tracking.start.mob.actionbar=[%1] §aAdded %2 to spawn tracking and reset. commands.spawn.tracking.start.area= Area: %1 to %2. commands.spawn.tracking.start.mocking= Since spawn mocking is enabled, mobs will not spawn but they will be tracked. commands.spawn.tracking.start.actionbar=[%s] §7Started tracking spawns. -commands.spawn.tracking.mob=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. +commands.spawn.tracking.mob=Starts tracking a specific mob spawn. Specify coords to track within an area. Run again to add more mob types to track. commands.spawn.tracking.mob.invalid=§cInvalid mob name: %s -commands.spawn.tracking.query=Displays statistics about mob spawning since spawn tracking started. Mob categories are based on population control. +commands.spawn.tracking.query=Displays a summary of all spawns that have occurred since the start of your test. commands.spawn.tracking.query.dimension=§7Dimension %s§r: commands.spawn.tracking.no=§cSpawns are not currently being tracked. commands.spawn.tracking.already=§cSpawns are already being tracked. commands.spawn.tracking.test=Resets all spawn counters. commands.spawn.tracking.test.success=§7Spawn counters reset. commands.spawn.tracking.test.success.actionbar=[%s] §7Reset spawn counters. -commands.spawn.tracking.stop=Displays statistics about mob spawning since spawn tracking started and then stops spawn tracking. +commands.spawn.tracking.stop=Stops tracking mob spawns. commands.spawn.tracking.stop.success=§7Spawns are no longer being tracked. commands.spawn.tracking.stop.actionbar=[%s] §7Stopped tracking spawns. commands.spawn.reset=Resets all spawn counters. -commands.spawn.mocking=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. +commands.spawn.mocking=Enables/disables mob spawning while allowing the spawning algorithm to run. commands.spawn.mocking.enable=§aSpawn mocking is now enabled. Mobs will no longer spawn, but the spawning algorithm will continue. commands.spawn.mocking.disable=§cSpawn mocking is now disabled. Mobs will spawn as normal. commands.spawn.mocking.enable.actionbar=[%s] §aSpawn mocking enabled. @@ -297,16 +295,16 @@ commands.summontnt.fail.none=§cNo TNT summoned. commands.summontnt.success=§7Summoned §c%s TNT§7. commands.tick=Set and control the server tick speed. -commands.tick.mspt=Sets the world's tick speed to the desired mspt. There will always be some extra. +commands.tick.mspt=Slows down the server tick speed to the specified mspt. commands.tick.mspt.fail=§cMSPT cannot be less than 50.0. commands.tick.mspt.success=§7%1 set the tick speed to %2 mspt. -commands.tick.step=Makes the world run at normal speed for the specified number of ticks. Leave the `[steps]` argument blank to step only one tick. +commands.tick.step=Allows the server to run at normal speed for the specified amount of steps. commands.tick.step.fail=§cCannot step ticks without setting a tick speed. commands.tick.step.start=§7%1 stepping %2 tick(s)... commands.tick.step.done=§7Tick step complete. -commands.tick.reset=Resets the world's tick speed to normal. +commands.tick.reset=Resets the server tick speed to normal. commands.tick.reset.success=§7%s reset the tick speed. -commands.tick.sleep=Pauses the game for the specified number of milliseconds. Can be run through a command block using `/scriptevent canopy:tick sleep [milliseconds]`. +commands.tick.sleep=Pauses the server for the specified amount of milliseconds. commands.tick.sleep.fail=§cInvalid sleep time. commands.tick.sleep.success=§7%1 pausing the server for %2 ms. @@ -315,21 +313,21 @@ commands.tntfuse.reset.success=§7Reset TNT fuse time to §a80§7 ticks. commands.tntfuse.set.fail=§cInvalid fuse time: %1 ticks. Must be between %2 and %3 ticks. commands.tntfuse.set.success=§7TNT fuse time set to §a%s§7 ticks. -commands.trackevent=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. +commands.trackevent=Count the number of times any event occurs. Displays the count in the InfoDisplay. commands.trackevent.stop=§7Stopped tracking %s. commands.trackevent.start=§7Started tracking %s. commands.trackevent.invalid=§cEvent %1 not found in %2. -commands.velocity=Queries or modifies the velocity of the specified entity. +commands.velocity=Affect the velocity of entities. commands.velocity.missingvelocity=§cPlease enter x, y, and z velocities. commands.velocity.query=§7Entity velocities (m/gt): commands.velocity.add=§7Added velocity to entities (m/gt): commands.velocity.set=§7Set velocity for entities (m/gt): commands.warp=Teleport to and manage warps. (Alias: w) -commands.warp.edit=Adds or removes a warp. Alias: **`./w `** -commands.warp.tp=Teleports you to a warp. Alias: **`./w `** -commands.warp.list=Lists all available warps in chat. +commands.warp.edit=Adds or removes a warp. +commands.warp.tp=Teleports you to a warp. +commands.warp.list=List all available warps. commands.warp.exists=§cWarp '%s' already exists. Use ./warps to see the list of warps. commands.warp.noexist=§cWarp '%s' not found. Use ./warps to see the list of warps. commands.warp.add.success=§7Warp '%s' has been added. From 3cf7c4ef078085c42f451dc9886d7540f2a16132 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Fri, 24 Apr 2026 16:58:27 -0700 Subject: [PATCH 43/64] feat: add logo header to generated rules pages --- docs/scripts/generate-wiki.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/scripts/generate-wiki.js b/docs/scripts/generate-wiki.js index e9e35e9..7836733 100644 --- a/docs/scripts/generate-wiki.js +++ b/docs/scripts/generate-wiki.js @@ -5,8 +5,6 @@ import { fileURLToPath } from 'node:url'; const __dirname = fileURLToPath(new URL('.', import.meta.url)); const projectRoot = path.resolve(__dirname, '../../'); -// ── Lang Parser ─────────────────────────────────────────────────────────────── - function parseLangFile(langPath) { const content = fs.readFileSync(langPath, 'utf8'); const map = {}; @@ -28,8 +26,6 @@ function resolveDescription(desc, lang) { return ''; } -// ── Usage String Builder ────────────────────────────────────────────────────── - const PARAM_TYPE_DISPLAY = { Boolean: 'bool', Enum: null, // replaced by enum values inline @@ -143,7 +139,7 @@ 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---\n\n'); - return `**Table of Contents:**\n${toc}\n\n---\n\n${entries}`; + return `

\npack_icon\n

\n\n**Table of Contents:**\n${toc}\n\n---\n\n${entries}`; } // ── Commands.md Injector ────────────────────────────────────────────────────── From e2647bef9d14a9d44f2dee7c023190c0f5cbd504 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Sat, 25 Apr 2026 00:50:22 -0700 Subject: [PATCH 44/64] fix: resolve wiki build issues --- .../scripts/lib/canopy/commands/Command.js | 8 ++- Canopy[BP]/scripts/src/commands/butcher.js | 3 +- Canopy[BP]/scripts/src/commands/camera.js | 1 + Canopy[BP]/scripts/src/commands/canopy.js | 2 +- .../scripts/src/commands/changedimension.js | 3 +- .../scripts/src/commands/claimprojectiles.js | 3 +- Canopy[BP]/scripts/src/commands/cleanup.js | 3 +- Canopy[BP]/scripts/src/commands/counter.js | 11 ++-- Canopy[BP]/scripts/src/commands/data.js | 3 +- .../scripts/src/commands/debugentity.js | 3 +- Canopy[BP]/scripts/src/commands/distance.js | 8 +-- .../scripts/src/commands/entitydensity.js | 3 +- Canopy[BP]/scripts/src/commands/generator.js | 9 +-- Canopy[BP]/scripts/src/commands/health.js | 3 +- Canopy[BP]/scripts/src/commands/help.js | 4 +- Canopy[BP]/scripts/src/commands/hss.js | 6 +- Canopy[BP]/scripts/src/commands/info.js | 2 +- Canopy[BP]/scripts/src/commands/jump.js | 3 +- .../scripts/src/commands/lifetimequery.js | 1 + .../scripts/src/commands/lifetimequeryitem.js | 1 + .../scripts/src/commands/lifetimetracking.js | 2 +- Canopy[BP]/scripts/src/commands/log.js | 1 + Canopy[BP]/scripts/src/commands/loop.js | 3 +- Canopy[BP]/scripts/src/commands/peek.js | 3 +- Canopy[BP]/scripts/src/commands/pos.js | 3 +- Canopy[BP]/scripts/src/commands/retest.js | 3 +- Canopy[BP]/scripts/src/commands/spawn.js | 8 +-- Canopy[BP]/scripts/src/commands/trackevent.js | 3 +- Canopy[BP]/scripts/src/commands/warp.js | 4 +- .../src/rules/infodisplay/InfoDisplay.js | 2 + Canopy[RP]/texts/en_US.lang | 2 + __mocks__/@minecraft/server.js | 52 +++++++++++++++-- docs/scripts/generate-wiki.js | 58 ++++++++++--------- docs/scripts/generate-wiki.test.js | 2 +- package.json | 2 +- 35 files changed, 153 insertions(+), 75 deletions(-) diff --git a/Canopy[BP]/scripts/lib/canopy/commands/Command.js b/Canopy[BP]/scripts/lib/canopy/commands/Command.js index 67cf596..4b0b4f4 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/src/commands/butcher.js b/Canopy[BP]/scripts/src/commands/butcher.js index 625c9b4..11aa1ec 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 index 5dde922..893adf0 100644 --- a/Canopy[BP]/scripts/src/commands/camera.js +++ b/Canopy[BP]/scripts/src/commands/camera.js @@ -61,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 index c553c54..027d7c7 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' } } diff --git a/Canopy[BP]/scripts/src/commands/changedimension.js b/Canopy[BP]/scripts/src/commands/changedimension.js index 9e0d66e..a1f5fd3 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 index 8edde1a..a982cc6 100644 --- a/Canopy[BP]/scripts/src/commands/claimprojectiles.js +++ b/Canopy[BP]/scripts/src/commands/claimprojectiles.js @@ -19,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 index 3de4918..3531cac 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 index 2541aa9..794e1fe 100644 --- a/Canopy[BP]/scripts/src/commands/counter.js +++ b/Canopy[BP]/scripts/src/commands/counter.js @@ -22,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 index 5174041..4b5f9eb 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 index 4b20f83..4cc1288 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 index df34adf..17ce56d 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 index 047d571..96e47cb 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/generator.js b/Canopy[BP]/scripts/src/commands/generator.js index 6fc09ab..9a25e2a 100644 --- a/Canopy[BP]/scripts/src/commands/generator.js +++ b/Canopy[BP]/scripts/src/commands/generator.js @@ -22,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 index 4641cd4..1d2c10e 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 index 099ff3a..e3f885f 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 index 32fef11..c9e28d9 100644 --- a/Canopy[BP]/scripts/src/commands/hss.js +++ b/Canopy[BP]/scripts/src/commands/hss.js @@ -24,7 +24,7 @@ export class HSS extends VanillaCommand { 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.', + 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': { @@ -34,8 +34,8 @@ export class HSS extends VanillaCommand { '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 index d36a242..a41ac80 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 index a00f032..85a133b 100644 --- a/Canopy[BP]/scripts/src/commands/jump.js +++ b/Canopy[BP]/scripts/src/commands/jump.js @@ -15,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 index 309290f..9db9216 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 index 6ecd6ed..520c554 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 index 1f5f189..f24927a 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 index 5c29573..4192837 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 index 2209f9e..fc56a10 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 index 7cb76e0..a1065a6 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 index fac49fe..8f438f5 100644 --- a/Canopy[BP]/scripts/src/commands/pos.js +++ b/Canopy[BP]/scripts/src/commands/pos.js @@ -16,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 index 5c21647..c4fe3f9 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/spawn.js b/Canopy[BP]/scripts/src/commands/spawn.js index 4c737a0..8fcafc3 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/trackevent.js b/Canopy[BP]/scripts/src/commands/trackevent.js index 7d58a2e..69b9408 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/warp.js b/Canopy[BP]/scripts/src/commands/warp.js index f2e4012..529e28b 100644 --- a/Canopy[BP]/scripts/src/commands/warp.js +++ b/Canopy[BP]/scripts/src/commands/warp.js @@ -28,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/rules/infodisplay/InfoDisplay.js b/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplay.js index 7f0779e..7ecea0f 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplay.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplay.js @@ -167,3 +167,5 @@ world.beforeEvents.playerLeave.subscribe((event) => { if (!event.player) return; delete playerToInfoDisplayMap[event.player.id]; }); + +export { InfoDisplay }; diff --git a/Canopy[RP]/texts/en_US.lang b/Canopy[RP]/texts/en_US.lang index a970e16..64d88c2 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): diff --git a/__mocks__/@minecraft/server.js b/__mocks__/@minecraft/server.js index 933a0c0..0934915 100644 --- a/__mocks__/@minecraft/server.js +++ b/__mocks__/@minecraft/server.js @@ -1,6 +1,34 @@ /* eslint-disable max-classes-per-file */ import { vi } from 'vitest'; +let nextRunId = 1; +const scheduled = new Map(); +let currentTick = 0; + +export function advanceTicks(n = 1) { + for (let i = 0; i < n; i++) { + currentTick++; + system.currentTick = currentTick; + for (const [id, entry] of [...scheduled.entries()]) { + if (!scheduled.has(id)) continue; + if (entry.nextTick <= currentTick) { + entry.callback(); + if (entry.interval === null) + scheduled.delete(id); + else if (scheduled.has(id)) + entry.nextTick = currentTick + entry.interval; + } + } + } +} + +export function resetScheduler() { + nextRunId = 1; + scheduled.clear(); + currentTick = 0; + system.currentTick = 0; +} + export const world = { beforeEvents: { chatSend: { subscribe: vi.fn(), unsubscribe: vi.fn() }, @@ -34,6 +62,7 @@ export const world = { pressurePlatePush: { subscribe: vi.fn(), unsubscribe: vi.fn() }, projectileHitEntity: { subscribe: vi.fn(), unsubscribe: vi.fn() }, }, + getAllPlayers: vi.fn(() => []), getDimension: vi.fn(), getDynamicProperty: vi.fn(), setDynamicProperty: vi.fn(), @@ -53,10 +82,25 @@ export const system = { playerPlaceBlock: { subscribe: vi.fn() }, }, runJob: vi.fn(), - run: vi.fn(), - runInterval: vi.fn(), - runTimeout: vi.fn(), - clearRun: vi.fn(), + run: vi.fn(callback => { + const id = nextRunId++; + scheduled.set(id, { callback, nextTick: currentTick + 1, interval: null }); + return id; + }), + runInterval: vi.fn((callback, tickInterval = 0) => { + const id = nextRunId++; + const interval = Math.max(tickInterval, 1); + scheduled.set(id, { callback, nextTick: currentTick + interval, interval }); + return id; + }), + runTimeout: vi.fn((callback, tickDelay = 0) => { + const id = nextRunId++; + scheduled.set(id, { callback, nextTick: currentTick + Math.max(tickDelay, 1), interval: null }); + return id; + }), + clearRun: vi.fn(runId => { + scheduled.delete(runId); + }), }; export const EntityComponentTypes = { diff --git a/docs/scripts/generate-wiki.js b/docs/scripts/generate-wiki.js index 7836733..89f0efa 100644 --- a/docs/scripts/generate-wiki.js +++ b/docs/scripts/generate-wiki.js @@ -66,7 +66,7 @@ function buildVanillaCommandBlock(cmd, lang) { const label = p.name.replace(/^[^:]+:/, ''); parts.push(`[${label}: ${display}]`); } - const desc = resolveDescription(cc.description, lang); + const desc = cc.wikiDescription ?? resolveDescription(cc.description, lang); return `**Usage: \`${parts.join(' ')}\`** \n${desc}${opSuffix}`; } @@ -102,20 +102,18 @@ function buildCommandBlock(cmd, lang) { if (entries.length === 0) { const usage = cmd.getUsage(); - const desc = resolveDescription(cmd.getDescription(), lang); + 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 = resolveDescription(e.description, lang); + const desc = e.wikiDescription ?? resolveDescription(e.description, lang); return `**Usage: \`${usage}\`** \n${desc}${opSuffix}`; }).join('\n\n'); } -// ── Rules Page Generator ────────────────────────────────────────────────────── - function buildRuleEntry(rule, lang) { const id = rule.getID(); const desc = rule.getWikiDescription() @@ -138,12 +136,10 @@ function buildRuleEntry(rule, lang) { 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---\n\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}`; } -// ── Commands.md Injector ────────────────────────────────────────────────────── - function injectCommandsPage(template, commandMap, lang) { const usedKeys = new Set(); let result = template.replace(/\{\{(\w+)\}\}/g, (match, key) => { @@ -175,42 +171,48 @@ function injectCommandsPage(template, commandMap, lang) { return result; } -// ── Main ────────────────────────────────────────────────────────────────────── - export async function main(wikiPath) { - // Import registries and main.js — main.js imports all rule and command files, - // populating Rules.rulesToRegister, Commands, and VanillaCommands. - const { Rules } = await import('../../Canopy[BP]/scripts/lib/canopy/rules/Rules.js'); - const { VanillaCommands } = await import('../../Canopy[BP]/scripts/lib/canopy/commands/VanillaCommands.js'); - const { Commands } = await import('../../Canopy[BP]/scripts/lib/canopy/commands/Commands.js'); await import('../../Canopy[BP]/scripts/main.js'); - const lang = parseLangFile(path.join(projectRoot, 'Canopy[RP]/texts/en_US.lang')); + generateRulesPages(wikiPath, lang); + generateCommandsPage(wikiPath, lang); +} - // Build command map: shortName -> { instance, isVanilla } - const commandMap = new Map(); - for (const cmd of Commands.getAll()) { - if (cmd.getExtension()) continue; // skip extension commands - commandMap.set(cmd.getName(), { instance: cmd, isVanilla: false }); - } - for (const cmd of VanillaCommands.getAll()) { - commandMap.set(cmd.getName(), { instance: cmd, isVanilla: true }); - } +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: () => {} }); - // Collect rules (worldLoad never fires, so rules stay in rulesToRegister) const allRules = Rules.rulesToRegister; const globalRules = allRules.filter(r => r.getCategory() === 'Rules'); const infoDisplayRules = allRules.filter(r => r.getCategory() === 'InfoDisplay'); - // Generate and write rules pages 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'); +} - // Inject Commands.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 index e9a82aa..4e2d31f 100644 --- a/docs/scripts/generate-wiki.test.js +++ b/docs/scripts/generate-wiki.test.js @@ -3,7 +3,7 @@ import path from 'node:path'; import { fileURLToPath } from 'node:url'; // This file is the entry point for wiki generation. Run via: -// WIKI_PATH=../Canopy.wiki npm run generate-wiki +// npm run generate-wiki ../Canopy.wiki // // Vitest's Vite aliases automatically resolve @minecraft/server to the mock, // allowing Canopy source files to be imported in Node.js context. diff --git a/package.json b/package.json index e0ead66..0cb8eb0 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "scripts": { "test": "vitest run --coverage", "lint": "eslint . --fix", - "generate-wiki": "vitest run --config vitest.wiki.config.js" + "generate-wiki": "sh -c 'WIKI_PATH=${WIKI_PATH:-$1} npx vitest run --config vitest.wiki.config.js' --" }, "devDependencies": { "@eslint/compat": "^1.2.5", From 71f1aef04729a6ef90aa007d11ee721482b32279 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Sat, 25 Apr 2026 01:22:36 -0700 Subject: [PATCH 45/64] chore: make wiki generation workflow more verbose --- .github/workflows/wiki.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/wiki.yml b/.github/workflows/wiki.yml index 7453dfb..40aef46 100644 --- a/.github/workflows/wiki.yml +++ b/.github/workflows/wiki.yml @@ -13,13 +13,18 @@ jobs: - name: Checkout Canopy repo uses: actions/checkout@v4 - - name: Checkout Wiki repo + - 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: From e767f1f3704c4e53867252f3a8ba17071dbce5aa Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Sat, 25 Apr 2026 01:28:48 -0700 Subject: [PATCH 46/64] fix: make npm run test run normal tests --- package.json | 2 +- vite.config.js | 2 +- vitest.config.js | 16 ++++++++++++++++ vitest.setup.js | 4 ---- vitest.wiki.config.js | 1 - 5 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 vitest.config.js delete mode 100644 vitest.setup.js diff --git a/package.json b/package.json index 0cb8eb0..fbce2d2 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "@minecraft/server-ui": "^2.1.0-beta.1.21.130-stable" }, "scripts": { - "test": "vitest run --coverage", + "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' --" }, diff --git a/vite.config.js b/vite.config.js index 754c8c5..99f91d1 100644 --- a/vite.config.js +++ b/vite.config.js @@ -12,6 +12,6 @@ export default defineConfig({ env: { NODE_ENV: 'test' }, - exclude: ['**/node_modules/**', 'docs/scripts/**'] + 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 0000000..5e1b11a --- /dev/null +++ b/vitest.config.js @@ -0,0 +1,16 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + resolve: { + alias: { + '@minecraft/server': `${__dirname}/__mocks__/@minecraft/server`, + '@minecraft/server-ui': `${__dirname}/__mocks__/@minecraft/server-ui`, + } + }, + test: { + env: { + NODE_ENV: 'test' + }, + include: ['__tests__/**/*.test.js'] + } +}); diff --git a/vitest.setup.js b/vitest.setup.js deleted file mode 100644 index 2ac8013..0000000 --- a/vitest.setup.js +++ /dev/null @@ -1,4 +0,0 @@ -// Global test setup. Add shared configuration here. -// Mock implementations live in __mocks__/@minecraft/server.js and __mocks__/@minecraft/server-ui.js. -// Test files that need different mock behavior (e.g. subscribe callbacks called immediately) -// can still override with vi.mock() locally. diff --git a/vitest.wiki.config.js b/vitest.wiki.config.js index 885126a..ae8729d 100644 --- a/vitest.wiki.config.js +++ b/vitest.wiki.config.js @@ -13,7 +13,6 @@ export default defineConfig({ } }, test: { - setupFiles: ['./vitest.setup.js'], env: { NODE_ENV: 'test' }, From e40719737a539cbf7479690f9449ebc583a187e9 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Sat, 25 Apr 2026 04:27:10 -0700 Subject: [PATCH 47/64] chore: use @forestoflight/minecraft-vitest-mocks --- __mocks__/@minecraft/debug-utilities.js | 10 - __mocks__/@minecraft/server-ui.js | 14 - __mocks__/@minecraft/server.js | 168 ----- package-lock.json | 831 +++++++++++++++++------- package.json | 1 + vitest.config.js | 7 +- vitest.wiki.config.js | 9 +- 7 files changed, 614 insertions(+), 426 deletions(-) delete mode 100644 __mocks__/@minecraft/debug-utilities.js delete mode 100644 __mocks__/@minecraft/server-ui.js delete mode 100644 __mocks__/@minecraft/server.js diff --git a/__mocks__/@minecraft/debug-utilities.js b/__mocks__/@minecraft/debug-utilities.js deleted file mode 100644 index 3d82b87..0000000 --- a/__mocks__/@minecraft/debug-utilities.js +++ /dev/null @@ -1,10 +0,0 @@ -import { vi } from 'vitest'; - -export const debugDrawer = { - addShape: vi.fn(), - removeShape: vi.fn(), -}; - -export class DebugText {} -export class DebugBox {} -export class DebugShape {} diff --git a/__mocks__/@minecraft/server-ui.js b/__mocks__/@minecraft/server-ui.js deleted file mode 100644 index fd0840e..0000000 --- a/__mocks__/@minecraft/server-ui.js +++ /dev/null @@ -1,14 +0,0 @@ -/* eslint-disable max-classes-per-file */ -import { vi } from 'vitest'; - -export const FormCancelationReason = { - UserBusy: 'UserBusy', - UserClosed: 'UserClosed', -}; - -export const uiManager = { - closeAllForms: vi.fn(), -}; - -export class ModalFormData {} -export class ActionFormData {} diff --git a/__mocks__/@minecraft/server.js b/__mocks__/@minecraft/server.js deleted file mode 100644 index 0934915..0000000 --- a/__mocks__/@minecraft/server.js +++ /dev/null @@ -1,168 +0,0 @@ -/* eslint-disable max-classes-per-file */ -import { vi } from 'vitest'; - -let nextRunId = 1; -const scheduled = new Map(); -let currentTick = 0; - -export function advanceTicks(n = 1) { - for (let i = 0; i < n; i++) { - currentTick++; - system.currentTick = currentTick; - for (const [id, entry] of [...scheduled.entries()]) { - if (!scheduled.has(id)) continue; - if (entry.nextTick <= currentTick) { - entry.callback(); - if (entry.interval === null) - scheduled.delete(id); - else if (scheduled.has(id)) - entry.nextTick = currentTick + entry.interval; - } - } - } -} - -export function resetScheduler() { - nextRunId = 1; - scheduled.clear(); - currentTick = 0; - system.currentTick = 0; -} - -export const world = { - beforeEvents: { - chatSend: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - playerPlaceBlock: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - playerBreakBlock: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - entityRemove: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - playerLeave: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - explosion: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - playerInteractWithBlock: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - playerInteractWithEntity: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - }, - afterEvents: { - worldLoad: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - entitySpawn: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - entityRemove: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - entityDie: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - entityHitEntity: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - entityHurt: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - entityLoad: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - effectAdd: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - playerInventoryItemChange: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - pistonActivate: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - playerBreakBlock: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - playerDimensionChange: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - playerGameModeChange: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - playerInteractWithBlock: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - playerInteractWithEntity: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - playerJoin: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - playerLeave: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - playerPlaceBlock: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - pressurePlatePush: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - projectileHitEntity: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - }, - getAllPlayers: vi.fn(() => []), - getDimension: vi.fn(), - getDynamicProperty: vi.fn(), - setDynamicProperty: vi.fn(), - structureManager: { - place: vi.fn(), - }, -}; - -export const system = { - currentTick: 0, - beforeEvents: { - startup: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - shutdown: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - }, - afterEvents: { - scriptEventReceive: { subscribe: vi.fn(), unsubscribe: vi.fn() }, - playerPlaceBlock: { subscribe: vi.fn() }, - }, - runJob: vi.fn(), - run: vi.fn(callback => { - const id = nextRunId++; - scheduled.set(id, { callback, nextTick: currentTick + 1, interval: null }); - return id; - }), - runInterval: vi.fn((callback, tickInterval = 0) => { - const id = nextRunId++; - const interval = Math.max(tickInterval, 1); - scheduled.set(id, { callback, nextTick: currentTick + interval, interval }); - return id; - }), - runTimeout: vi.fn((callback, tickDelay = 0) => { - const id = nextRunId++; - scheduled.set(id, { callback, nextTick: currentTick + Math.max(tickDelay, 1), interval: null }); - return id; - }), - clearRun: vi.fn(runId => { - scheduled.delete(runId); - }), -}; - -export const EntityComponentTypes = { - Inventory: 'inventory', -}; - -export const ItemComponentTypes = { - Durability: 'durability', - Enchantable: 'enchantable', -}; - -export const CustomCommandSource = { - Block: 'Block', - Entity: 'Entity', - Server: 'Server', -}; - -export const CustomCommandStatus = { - Failure: 'Failure', - Success: 'Success', -}; - -export const CustomCommandParamType = { - Boolean: 'Boolean', - Enum: 'Enum', - Float: 'Float', - Integer: 'Integer', - Location: 'Location', - String: 'String', - EntitySelector: 'EntitySelector', - EntityType: 'EntityType', - BlockType: 'BlockType', -}; - -export const CommandPermissionLevel = { - Any: 'Any', - GameDirectors: 'GameDirectors', - Admin: 'Admin', - Owner: 'Owner', - Internal: 'Internal', -}; - -export const DimensionTypes = { getAll: () => [], get: () => undefined }; -export const ScriptEventSource = {}; -export const InputButton = {}; -export const ButtonState = {}; -export const TicksPerSecond = 20; -export const ItemStack = {}; -export const Direction = { Down: 'Down', Up: 'Up', North: 'North', South: 'South', East: 'East', West: 'West' }; -export const GameMode = { survival: 'survival', creative: 'creative', adventure: 'adventure', spectator: 'spectator' }; -export const EntityInitializationCause = {}; -export const LiquidType = { Water: 'Water', Lava: 'Lava' }; -export const StructureMirrorAxis = { None: 'None', X: 'X', Z: 'Z', XZ: 'XZ' }; -export const StructureRotation = { None: 'None', Rotate90: 'Rotate90', Rotate180: 'Rotate180', Rotate270: 'Rotate270' }; -export const StructureSaveMode = { Memory: 'Memory', World: 'World' }; -export const CommandError = class CommandError extends Error {}; -export class BlockPermutation { - static resolve = vi.fn(); -} -export class Container {} -export class Block {} -export class Entity {} -export class Player { - sendMessage = vi.fn(); -} diff --git a/package-lock.json b/package-lock.json index 209d769..32b51ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "devDependencies": { "@eslint/compat": "^1.2.5", "@eslint/js": "^9.19.0", + "@forestoflight/minecraft-vitest-mocks": "^1.0.3", "@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.3", + "resolved": "https://registry.npmjs.org/@forestoflight/minecraft-vitest-mocks/-/minecraft-vitest-mocks-1.0.3.tgz", + "integrity": "sha512-RkxAniODlGr0AEuAIcoP31yd1sxtBScbfyGrljaAWzzycqCQtODZwbqP42XAuGeq9ryUT9UMrn26kPqAr9aN7Q==", + "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" }, @@ -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.6.0", + "resolved": "https://registry.npmjs.org/@minecraft/server/-/server-2.6.0.tgz", + "integrity": "sha512-/l1gbUHcTl/3gb3cB7Ilb8pfW1QytoAc+iMXUPSzvv8cxGluf6M635jl29P8Bid3iVmUB9hWyCUZtRUVVlk5ew==", "license": "MIT", "peerDependencies": { "@minecraft/common": "^1.2.0", @@ -892,276 +933,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-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-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-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-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-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-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 +1435,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 +1452,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 +1502,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 +1518,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 +1559,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 +1662,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 +1674,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 +1728,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 +1760,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 +1868,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 +1905,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 +1928,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 +1945,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 +1958,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 +1982,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 +2016,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 +2064,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 +2136,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 +2153,7 @@ "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -1946,13 +2180,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 +2210,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 +2305,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 +2327,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 +2380,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 +2513,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 +2535,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 +2634,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 +2659,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 +2668,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 +2780,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 +2844,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 +2857,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 +2875,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -2532,16 +2895,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 +2919,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 +2941,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 +3247,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 fbce2d2..19d4533 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "devDependencies": { "@eslint/compat": "^1.2.5", "@eslint/js": "^9.19.0", + "@forestoflight/minecraft-vitest-mocks": "^1.0.3", "@vitest/coverage-v8": "^3.0.4", "axios": "^1.7.9", "eslint": "^9.19.0", diff --git a/vitest.config.js b/vitest.config.js index 5e1b11a..2263706 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -3,9 +3,10 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ resolve: { alias: { - '@minecraft/server': `${__dirname}/__mocks__/@minecraft/server`, - '@minecraft/server-ui': `${__dirname}/__mocks__/@minecraft/server-ui`, - } + '@minecraft/server': `@forestoflight/minecraft-vitest-mocks/server`, + '@minecraft/server-ui': `@forestoflight/minecraft-vitest-mocks/server-ui`, + }, + setupFiles: ['@forestoflight/minecraft-vitest-mocks/setup'] }, test: { env: { diff --git a/vitest.wiki.config.js b/vitest.wiki.config.js index ae8729d..af35d5a 100644 --- a/vitest.wiki.config.js +++ b/vitest.wiki.config.js @@ -3,14 +3,15 @@ import { defineConfig } from 'vite'; export default defineConfig({ resolve: { alias: { - '@minecraft/server': `${__dirname}/__mocks__/@minecraft/server`, - '@minecraft/server-ui': `${__dirname}/__mocks__/@minecraft/server-ui`, - '@minecraft/debug-utilities': `${__dirname}/__mocks__/@minecraft/debug-utilities`, + '@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`, - } + }, + setupFiles: ['@forestoflight/minecraft-vitest-mocks/setup'] }, test: { env: { From 0bca2da5e080c3debcb24c5dcfe35034531261a7 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Sun, 26 Apr 2026 00:35:19 -0700 Subject: [PATCH 48/64] feat: start end gateway exit visualization --- .../src/classes/EndGatewayExitFinder.js | 36 +++++++++++++++ .../src/classes/EndGatewayFinder.test.js | 46 +++++++++++++++++++ vitest.config.js | 1 + 3 files changed, 83 insertions(+) create mode 100644 Canopy[BP]/scripts/src/classes/EndGatewayExitFinder.js create mode 100644 __tests__/BP/scripts/src/classes/EndGatewayFinder.test.js diff --git a/Canopy[BP]/scripts/src/classes/EndGatewayExitFinder.js b/Canopy[BP]/scripts/src/classes/EndGatewayExitFinder.js new file mode 100644 index 0000000..f163daf --- /dev/null +++ b/Canopy[BP]/scripts/src/classes/EndGatewayExitFinder.js @@ -0,0 +1,36 @@ +import { debugDrawer, debugBox } from "@minecraft/debug-utilities" +import { system, world } from "@minecraft/server"; + +export class EndGatewayExitFinder { + gatewayExits = []; + runner = void 0; + + constructor() { + this.start(); + } + + destroy() { + this.stop(); + this.gatewayExits.length = 0; + } + + start() { + if (this.runner) + return; + this.runner = system.runInterval(this.onTick.bind(this)); + } + + onTick() { + const players = world.getPlayers(); + for (const player of players) { + const dimension = player.dimension; + if (this.isStandingInGateway(player, dimension)) { + const exitRender = debugDrawer.createRender(); + } + } + } + + isStandingInGateway(player, dimension) { + return dimension.getBlock(player.location).id === "minecraft:end_gateway"; + } +} \ No newline at end of file diff --git a/__tests__/BP/scripts/src/classes/EndGatewayFinder.test.js b/__tests__/BP/scripts/src/classes/EndGatewayFinder.test.js new file mode 100644 index 0000000..5ba4d94 --- /dev/null +++ b/__tests__/BP/scripts/src/classes/EndGatewayFinder.test.js @@ -0,0 +1,46 @@ +import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { EndGatewayExitFinder } from "../../../../../Canopy[BP]/scripts/src/classes/EndGatewayExitFinder"; +import { world } from "@minecraft/server"; +import { scheduler } from "@forestoflight/minecraft-vitest-mocks"; + +describe('EntitMovementLog', () => { + let gatewayFinder; + beforeAll(() => { + gatewayFinder = new EndGatewayExitFinder(); + }); + + describe('constructor', () => { + it('should initialize properties correctly', () => { + expect(gatewayFinder.gatewayExits).toEqual([]); + expect(gatewayFinder.runner).toBeUndefined(); + }); + }); + + describe('onTick', () => { + let mockPlayer; + beforeEach(() => { + mockPlayer = { + location: { x: 0, y: 0, z: 0 }, + dimension: { + id: "minecraft:the_end", + getBlock: vi.fn(() => ({ id: "minecraft:end_gateway" })) + } + }; + }); + + describe('when player is standing in gateway', () => { + it('should create an end gateway exit render at the player position one tick later if theyre in an end gateway', () => { + vi.spyOn(world, "getPlayers").mockReturnValue([mockPlayer]); + gatewayFinder.onTick(); + scheduler.advanceTicks(1); + expect(mockPlayer.dimension.getBlock).toHaveBeenCalledWith({ x: 0, y: 0, z: 0 }); + }); + + it('should not create an end gateway exit render if theyre not in an end gateway', () => { + vi.spyOn(world, "getPlayers").mockReturnValue([mockPlayer]); + mockPlayer.dimension.getBlock.mockReturnValue({ id: "minecraft:stone" }); + gatewayFinder.onTick(); + }); + }); + }); +}); \ No newline at end of file diff --git a/vitest.config.js b/vitest.config.js index 2263706..537c3ff 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -5,6 +5,7 @@ export default defineConfig({ 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` }, setupFiles: ['@forestoflight/minecraft-vitest-mocks/setup'] }, From 18a81e7f987cf4e8a7ff95f502264f8537830d91 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Sun, 26 Apr 2026 01:43:31 -0700 Subject: [PATCH 49/64] feat: add End Gateway Exit Render --- Canopy[BP]/scripts/lib/Vector.js | 2 + .../src/classes/EndGatewayExitFinder.js | 46 +++++-- .../src/classes/EndGatewayExitRender.js | 52 +++++++ .../src/classes/EndGatewayExitFinder.test.js | 127 ++++++++++++++++++ .../src/classes/EndGatewayExitRender.test.js | 32 +++++ .../src/classes/EndGatewayFinder.test.js | 46 ------- 6 files changed, 247 insertions(+), 58 deletions(-) create mode 100644 Canopy[BP]/scripts/src/classes/EndGatewayExitRender.js create mode 100644 __tests__/BP/scripts/src/classes/EndGatewayExitFinder.test.js create mode 100644 __tests__/BP/scripts/src/classes/EndGatewayExitRender.test.js delete mode 100644 __tests__/BP/scripts/src/classes/EndGatewayFinder.test.js diff --git a/Canopy[BP]/scripts/lib/Vector.js b/Canopy[BP]/scripts/lib/Vector.js index 41ac2ec..19a6d3f 100644 --- a/Canopy[BP]/scripts/lib/Vector.js +++ b/Canopy[BP]/scripts/lib/Vector.js @@ -40,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 }; @@ -59,6 +60,7 @@ Vector.prototype = { add(vec) { return Vector.add(this, vec); }, subtract(vec) { return Vector.subtract(this, vec); }, 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/src/classes/EndGatewayExitFinder.js b/Canopy[BP]/scripts/src/classes/EndGatewayExitFinder.js index f163daf..af99be8 100644 --- a/Canopy[BP]/scripts/src/classes/EndGatewayExitFinder.js +++ b/Canopy[BP]/scripts/src/classes/EndGatewayExitFinder.js @@ -1,36 +1,58 @@ -import { debugDrawer, debugBox } from "@minecraft/debug-utilities" -import { system, world } from "@minecraft/server"; +import { LocationInUnloadedChunkError, LocationOutOfWorldBoundariesError, system, world } from "@minecraft/server"; +import { Vector } from "../../lib/Vector"; +import { EndGatewayExitRender } from "./EndGatewayExitRender"; export class EndGatewayExitFinder { gatewayExits = []; runner = void 0; - constructor() { - this.start(); - } - destroy() { this.stop(); + for (const exit of this.gatewayExits) + exit.render.destroy(); this.gatewayExits.length = 0; } start() { - if (this.runner) + if (this.runner !== void 0) return; this.runner = system.runInterval(this.onTick.bind(this)); } + stop() { + if (this.runner === void 0) + return; + system.clearRun(this.runner); + this.runner = void 0; + } + onTick() { const players = world.getPlayers(); for (const player of players) { const dimension = player.dimension; - if (this.isStandingInGateway(player, dimension)) { - const exitRender = debugDrawer.createRender(); - } + if (this.isEndGateway(dimension, player.location)) + system.runTimeout(() => this.addEndGatewayExit(dimension, player.location), 1); } } - isStandingInGateway(player, dimension) { - return dimension.getBlock(player.location).id === "minecraft:end_gateway"; + isEndGateway(dimension, location) { + try { + return dimension.getBlock(location)?.id === "minecraft:end_gateway"; + } catch (error) { + if (error instanceof LocationOutOfWorldBoundariesError || error instanceof LocationInUnloadedChunkError) + return false; + throw error; + } + } + + addEndGatewayExit(dimension, location) { + if (this.exitIsKnown(location)) + return; + const render = new EndGatewayExitRender(dimension, location); + this.gatewayExits.push({ location: Vector.from(location), dimension, render }); + } + + exitIsKnown(location) { + return this.gatewayExits.some(exit => exit.location.equals(Vector.from(location))); } } \ 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 0000000..6cdd8a6 --- /dev/null +++ b/Canopy[BP]/scripts/src/classes/EndGatewayExitRender.js @@ -0,0 +1,52 @@ +import { debugDrawer, DebugBox, DebugLine } from "@minecraft/debug-utilities"; + +export class EndGatewayExitRender { + debugShapes = []; + SEARCH_AREA_SIZE = 16; + + constructor(dimension, location) { + this.dimension = dimension; + this.location = location; + this.render(); + } + + destroy() { + for(const shape of this.debugShapes) + shape.remove(); + this.debugShapes.length = 0; + } + + render() { + this.drawExit(); + this.drawSearchArea(); + } + + drawExit() { + const center = { x: this.location.x + 0.5, y: this.location.y + 0.5, z: this.location.z + 0.5 }; + const box = new DebugBox(center); + box.color = { r: 1, g: 1, b: 0, a: 1 }; + this.drawShape(box); + } + + drawSearchArea() { + const halfSize = this.SEARCH_AREA_SIZE / 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++) { + const start = corners[i]; + const end = corners[(i + 1) % corners.length]; + const line = new DebugLine(start, end); + line.color = { r: 0, g: 1, b: 0, a: 1 }; + this.drawShape(line); + } + } + + drawShape(shape) { + debugDrawer.addShape(shape, this.dimension); + this.debugShapes.push(shape); + } +} \ No newline at end of file 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 0000000..a49a27f --- /dev/null +++ b/__tests__/BP/scripts/src/classes/EndGatewayExitFinder.test.js @@ -0,0 +1,127 @@ +import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { EndGatewayExitFinder } from "../../../../../Canopy[BP]/scripts/src/classes/EndGatewayExitFinder"; +import { LocationInUnloadedChunkError, LocationOutOfWorldBoundariesError, world } from "@minecraft/server"; +import { scheduler } from "@forestoflight/minecraft-vitest-mocks"; +import { EndGatewayExitRender } from "../../../../../Canopy[BP]/scripts/src/classes/EndGatewayExitRender"; + +describe('EndGatewayExitFinder', () => { + let gatewayFinder; + beforeAll(() => { + gatewayFinder = new EndGatewayExitFinder(); + }); + + describe('constructor', () => { + it('should initialize properties correctly', () => { + expect(gatewayFinder.gatewayExits).toEqual([]); + expect(gatewayFinder.runner).toBeUndefined(); + }); + }); + + describe('destroy', () => { + let mockRender; + beforeAll(() => { + mockRender = { location: { x: 1, y: 2, z: 3 }, render: new EndGatewayExitRender({ id: "minecraft:the_end" }, { x: 1, y: 2, z: 3 }) }; + }); + + it('should clear gateway exits and stop the runner', () => { + gatewayFinder.gatewayExits.push(mockRender); + gatewayFinder.start(); + gatewayFinder.destroy(); + expect(gatewayFinder.gatewayExits).toEqual([]); + expect(gatewayFinder.runner).toBeUndefined(); + }); + + it('should destroy all gateway exit renders', () => { + vi.spyOn(mockRender.render, "destroy"); + gatewayFinder.gatewayExits.push(mockRender); + gatewayFinder.destroy(); + expect(mockRender.render.destroy).toHaveBeenCalled(); + }); + }); + + describe('start', () => { + it('should start the runner if not already running', () => { + gatewayFinder.runner = void 0; + gatewayFinder.start(); + expect(gatewayFinder.runner).toBeDefined(); + }); + + it('should not start a new runner if one is already running', () => { + gatewayFinder.start(); + const existingRunner = gatewayFinder.runner; + gatewayFinder.start(); + expect(gatewayFinder.runner).toBe(existingRunner); + }); + }); + + describe('stop', () => { + it('should stop the runner if it is running', () => { + gatewayFinder.start(); + gatewayFinder.stop(); + expect(gatewayFinder.runner).toBeUndefined(); + }); + + it('should not throw if stop is called when no runner is running', () => { + gatewayFinder.runner = void 0; + expect(() => gatewayFinder.stop()).not.toThrow(); + }); + }); + + describe('onTick', () => { + let mockPlayer; + beforeEach(() => { + vi.resetAllMocks(); + gatewayFinder.destroy(); + gatewayFinder = new EndGatewayExitFinder(); + mockPlayer = { + location: { x: 0, y: 0, z: 0 }, + dimension: { + id: "minecraft:the_end", + getBlock: vi.fn(() => ({ id: "minecraft:end_gateway" })) + } + }; + vi.spyOn(world, "getPlayers").mockReturnValue([mockPlayer]); + gatewayFinder.start(); + }); + + describe('when player is standing in gateway', () => { + it('should create an end gateway exit render at the player position one tick later', () => { + gatewayFinder.onTick(); + scheduler.advanceTicks(1); + expect(gatewayFinder.gatewayExits).toEqual([expect.objectContaining({ location: { x: 0, y: 0, z: 0 } })]); + }); + + it('should not create duplicate entries for the same gateway exit', () => { + gatewayFinder.onTick(); + scheduler.advanceTicks(1); + gatewayFinder.onTick(); + scheduler.advanceTicks(1); + expect(gatewayFinder.gatewayExits).toEqual([expect.objectContaining({ location: { x: 0, y: 0, z: 0 } })]); + }); + }); + + describe('when player is not standing in gateway', () => { + it('should not create an end gateway exit render', () => { + mockPlayer.dimension.getBlock.mockReturnValue({ id: "minecraft:stone" }); + gatewayFinder.onTick(); + scheduler.advanceTicks(1); + expect(gatewayFinder.gatewayExits).toEqual([]); + }); + }); + + it('should throw unknown errors from getBlock', () => { + mockPlayer.dimension.getBlock.mockImplementation(() => { throw new Error("Test error"); }); + expect(() => gatewayFinder.onTick()).toThrow(); + }); + + it('should ignore LocationOutOfWorldBoundariesError from getBlock', () => { + mockPlayer.dimension.getBlock.mockImplementation(() => { throw new LocationOutOfWorldBoundariesError(); }); + expect(() => gatewayFinder.onTick()).not.toThrow(); + }); + + it('should ignore LocationInUnloadedChunkError from getBlock', () => { + mockPlayer.dimension.getBlock.mockImplementation(() => { throw new LocationInUnloadedChunkError(); }); + expect(() => gatewayFinder.onTick()).not.toThrow(); + }); + }); +}); \ No newline at end of file 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 0000000..f829418 --- /dev/null +++ b/__tests__/BP/scripts/src/classes/EndGatewayExitRender.test.js @@ -0,0 +1,32 @@ +import { beforeAll, describe, expect, it, vi } from "vitest"; +import { EndGatewayExitRender } from "../../../../../Canopy[BP]/scripts/src/classes/EndGatewayExitRender"; + +describe('EndGatewayExitRender', () => { + let exitRender; + beforeAll(() => { + const dimension = { id: "minecraft:the_end" }; + const location = { x: 0, y: 0, z: 0 }; + exitRender = new EndGatewayExitRender(dimension, location); + }); + + describe('constructor', () => { + it('should initialize properties correctly', () => { + expect(exitRender.location).toEqual({ x: 0, y: 0, z: 0 }); + expect(exitRender.dimension).toEqual({ id: "minecraft:the_end" }); + }); + + it('should render the gateway exit', () => { + expect(exitRender.debugShapes.length).toBeGreaterThan(0); + }); + }); + + describe('destroy', () => { + it('should remove all debug shapes', () => { + const mockShape = { remove: vi.fn() }; + exitRender.debugShapes.push(mockShape); + exitRender.destroy(); + expect(mockShape.remove).toHaveBeenCalled(); + expect(exitRender.debugShapes.length).toBe(0); + }); + }); +}); \ No newline at end of file diff --git a/__tests__/BP/scripts/src/classes/EndGatewayFinder.test.js b/__tests__/BP/scripts/src/classes/EndGatewayFinder.test.js deleted file mode 100644 index 5ba4d94..0000000 --- a/__tests__/BP/scripts/src/classes/EndGatewayFinder.test.js +++ /dev/null @@ -1,46 +0,0 @@ -import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { EndGatewayExitFinder } from "../../../../../Canopy[BP]/scripts/src/classes/EndGatewayExitFinder"; -import { world } from "@minecraft/server"; -import { scheduler } from "@forestoflight/minecraft-vitest-mocks"; - -describe('EntitMovementLog', () => { - let gatewayFinder; - beforeAll(() => { - gatewayFinder = new EndGatewayExitFinder(); - }); - - describe('constructor', () => { - it('should initialize properties correctly', () => { - expect(gatewayFinder.gatewayExits).toEqual([]); - expect(gatewayFinder.runner).toBeUndefined(); - }); - }); - - describe('onTick', () => { - let mockPlayer; - beforeEach(() => { - mockPlayer = { - location: { x: 0, y: 0, z: 0 }, - dimension: { - id: "minecraft:the_end", - getBlock: vi.fn(() => ({ id: "minecraft:end_gateway" })) - } - }; - }); - - describe('when player is standing in gateway', () => { - it('should create an end gateway exit render at the player position one tick later if theyre in an end gateway', () => { - vi.spyOn(world, "getPlayers").mockReturnValue([mockPlayer]); - gatewayFinder.onTick(); - scheduler.advanceTicks(1); - expect(mockPlayer.dimension.getBlock).toHaveBeenCalledWith({ x: 0, y: 0, z: 0 }); - }); - - it('should not create an end gateway exit render if theyre not in an end gateway', () => { - vi.spyOn(world, "getPlayers").mockReturnValue([mockPlayer]); - mockPlayer.dimension.getBlock.mockReturnValue({ id: "minecraft:stone" }); - gatewayFinder.onTick(); - }); - }); - }); -}); \ No newline at end of file From e163f39841ed1102e191a430115356141747016e Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Sun, 26 Apr 2026 02:29:46 -0700 Subject: [PATCH 50/64] feat: add renderEndGatewayExits --- .../src/classes/EndGatewayExitFinder.js | 34 +++------- .../src/classes/EndGatewayExitRender.js | 4 +- .../infodisplay/InfoDisplayShapeElement.js | 7 --- .../infodisplay/RenderEndGatewayExits.js | 37 +++++++++++ .../rules/infodisplay/RenderSignalStrength.js | 7 ++- Canopy[RP]/texts/en_US.lang | 1 + .../src/classes/EndGatewayExitFinder.test.js | 62 +++++-------------- .../src/classes/EndGatewayExitRender.test.js | 5 +- .../src/rules/infodisplay/BlockStates.test.js | 2 +- .../src/rules/infodisplay/ChunkCoords.test.js | 2 +- .../src/rules/infodisplay/Dimension.test.js | 2 +- .../rules/infodisplay/PeekInventory.test.js | 2 +- .../infodisplay/RenderEndGatewayExits.test.js | 51 +++++++++++++++ .../src/rules/infodisplay/Speed.test.js | 2 +- .../src/rules/infodisplay/Structures.test.js | 2 +- .../src/rules/infodisplay/Weather.test.js | 2 +- 16 files changed, 133 insertions(+), 89 deletions(-) create mode 100644 Canopy[BP]/scripts/src/rules/infodisplay/RenderEndGatewayExits.js create mode 100644 __tests__/BP/scripts/src/rules/infodisplay/RenderEndGatewayExits.test.js diff --git a/Canopy[BP]/scripts/src/classes/EndGatewayExitFinder.js b/Canopy[BP]/scripts/src/classes/EndGatewayExitFinder.js index af99be8..60a643f 100644 --- a/Canopy[BP]/scripts/src/classes/EndGatewayExitFinder.js +++ b/Canopy[BP]/scripts/src/classes/EndGatewayExitFinder.js @@ -1,38 +1,24 @@ -import { LocationInUnloadedChunkError, LocationOutOfWorldBoundariesError, system, world } from "@minecraft/server"; +import { LocationInUnloadedChunkError, LocationOutOfWorldBoundariesError, system } from "@minecraft/server"; import { Vector } from "../../lib/Vector"; import { EndGatewayExitRender } from "./EndGatewayExitRender"; export class EndGatewayExitFinder { gatewayExits = []; - runner = void 0; + + constructor(player) { + this.player = player; + } destroy() { - this.stop(); for (const exit of this.gatewayExits) exit.render.destroy(); this.gatewayExits.length = 0; } - start() { - if (this.runner !== void 0) - return; - this.runner = system.runInterval(this.onTick.bind(this)); - } - - stop() { - if (this.runner === void 0) - return; - system.clearRun(this.runner); - this.runner = void 0; - } - - onTick() { - const players = world.getPlayers(); - for (const player of players) { - const dimension = player.dimension; - if (this.isEndGateway(dimension, player.location)) - system.runTimeout(() => this.addEndGatewayExit(dimension, player.location), 1); - } + onTickTryFind() { + const dimension = this.player.dimension; + if (this.isEndGateway(dimension, this.player.location)) + system.runTimeout(() => this.addEndGatewayExit(dimension, this.player.location), 1); } isEndGateway(dimension, location) { @@ -48,7 +34,7 @@ export class EndGatewayExitFinder { addEndGatewayExit(dimension, location) { if (this.exitIsKnown(location)) return; - const render = new EndGatewayExitRender(dimension, location); + const render = new EndGatewayExitRender(this.player, dimension, location); this.gatewayExits.push({ location: Vector.from(location), dimension, render }); } diff --git a/Canopy[BP]/scripts/src/classes/EndGatewayExitRender.js b/Canopy[BP]/scripts/src/classes/EndGatewayExitRender.js index 6cdd8a6..b642eeb 100644 --- a/Canopy[BP]/scripts/src/classes/EndGatewayExitRender.js +++ b/Canopy[BP]/scripts/src/classes/EndGatewayExitRender.js @@ -4,7 +4,8 @@ export class EndGatewayExitRender { debugShapes = []; SEARCH_AREA_SIZE = 16; - constructor(dimension, location) { + constructor(player, dimension, location) { + this.player = player; this.dimension = dimension; this.location = location; this.render(); @@ -46,6 +47,7 @@ export class EndGatewayExitRender { } drawShape(shape) { + shape.visibleTo = [this.player]; debugDrawer.addShape(shape, this.dimension); this.debugShapes.push(shape); } diff --git a/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayShapeElement.js b/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayShapeElement.js index 3a177a6..c240e70 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayShapeElement.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayShapeElement.js @@ -1,9 +1,6 @@ import { InfoDisplayElement } from './InfoDisplayElement'; class InfoDisplayShapeElement extends InfoDisplayElement { - identifier; - isWorldwide; - constructor(ruleData, isWorldwide = false) { const originalOnEnableCallback = ruleData.onEnableCallback; ruleData.onEnableCallback = () => { @@ -22,10 +19,6 @@ class InfoDisplayShapeElement extends InfoDisplayElement { throw new TypeError("Abstract class 'InfoDisplayShapeElement' cannot be instantiated directly."); } - setupCallbacks(ruleData) { - return ruleData; - } - startRender() { this.isRendering = true; } diff --git a/Canopy[BP]/scripts/src/rules/infodisplay/RenderEndGatewayExits.js b/Canopy[BP]/scripts/src/rules/infodisplay/RenderEndGatewayExits.js new file mode 100644 index 0000000..8d7a0f5 --- /dev/null +++ b/Canopy[BP]/scripts/src/rules/infodisplay/RenderEndGatewayExits.js @@ -0,0 +1,37 @@ +import { InfoDisplayShapeElement } from './InfoDisplayShapeElement'; +import { EndGatewayExitFinder } from '../../classes/EndGatewayExitFinder'; + +export class RenderEndGatewayExits extends InfoDisplayShapeElement { + player; + playerId; + endGatewayExitFinder; + + constructor(player) { + const ruleData = { + identifier: 'renderEndGatewayExits', + description: { translate: 'rules.infoDisplay.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 the exit to if the exit becomes invalid.', + onEnableCallback: () => this.startRender(), + onDisableCallback: () => this.stopRender() + }; + super(ruleData, 0); + this.player = player; + this.playerId = player.id; + } + + startRender() { + this.stopRender(); + this.endGatewayExitFinder = new EndGatewayExitFinder(this.player); + } + + stopRender() { + if (this.endGatewayExitFinder) { + this.endGatewayExitFinder.destroy(); + this.endGatewayExitFinder = void 0; + } + } + + onTick() { + this.endGatewayExitFinder?.onTickTryFind(); + } +} diff --git a/Canopy[BP]/scripts/src/rules/infodisplay/RenderSignalStrength.js b/Canopy[BP]/scripts/src/rules/infodisplay/RenderSignalStrength.js index f0175e4..e333b4f 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/RenderSignalStrength.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/RenderSignalStrength.js @@ -6,13 +6,14 @@ import { Vector } from '../../../lib/Vector'; class RenderSignalStrength extends InfoDisplayShapeElement { player; playerId; - RENDER_DISTANCE = 10; + 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.startRender(), onDisableCallback: () => this.stopRender() }; @@ -61,8 +62,8 @@ class RenderSignalStrength extends InfoDisplayShapeElement { getNearbyBlockLocationIterator(dimension, location) { const locationVector = Vector.from(location); - const min = locationVector.subtract(new Vector(this.RENDER_DISTANCE, this.RENDER_DISTANCE, this.RENDER_DISTANCE)); - const max = locationVector.add(new Vector(this.RENDER_DISTANCE, 2, this.RENDER_DISTANCE)); + 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(); diff --git a/Canopy[RP]/texts/en_US.lang b/Canopy[RP]/texts/en_US.lang index 96446e9..5b2de33 100644 --- a/Canopy[RP]/texts/en_US.lang +++ b/Canopy[RP]/texts/en_US.lang @@ -433,6 +433,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.renderEndGatewayExits=Renders the exit locations of end gateways after passing through them. 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 diff --git a/__tests__/BP/scripts/src/classes/EndGatewayExitFinder.test.js b/__tests__/BP/scripts/src/classes/EndGatewayExitFinder.test.js index a49a27f..426b0c8 100644 --- a/__tests__/BP/scripts/src/classes/EndGatewayExitFinder.test.js +++ b/__tests__/BP/scripts/src/classes/EndGatewayExitFinder.test.js @@ -1,34 +1,34 @@ import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { EndGatewayExitFinder } from "../../../../../Canopy[BP]/scripts/src/classes/EndGatewayExitFinder"; -import { LocationInUnloadedChunkError, LocationOutOfWorldBoundariesError, world } from "@minecraft/server"; +import { LocationInUnloadedChunkError, LocationOutOfWorldBoundariesError, Player } from "@minecraft/server"; import { scheduler } from "@forestoflight/minecraft-vitest-mocks"; import { EndGatewayExitRender } from "../../../../../Canopy[BP]/scripts/src/classes/EndGatewayExitRender"; describe('EndGatewayExitFinder', () => { let gatewayFinder; beforeAll(() => { - gatewayFinder = new EndGatewayExitFinder(); + const player = new Player(); + gatewayFinder = new EndGatewayExitFinder(player); }); describe('constructor', () => { it('should initialize properties correctly', () => { expect(gatewayFinder.gatewayExits).toEqual([]); - expect(gatewayFinder.runner).toBeUndefined(); + expect(gatewayFinder.player).toBeDefined(); }); }); describe('destroy', () => { let mockRender; beforeAll(() => { - mockRender = { location: { x: 1, y: 2, z: 3 }, render: new EndGatewayExitRender({ id: "minecraft:the_end" }, { x: 1, y: 2, z: 3 }) }; + const player = new Player(); + mockRender = { location: { x: 1, y: 2, z: 3 }, render: new EndGatewayExitRender(player, { id: "minecraft:the_end" }, { x: 1, y: 2, z: 3 }) }; }); - it('should clear gateway exits and stop the runner', () => { + it('should clear gateway exits', () => { gatewayFinder.gatewayExits.push(mockRender); - gatewayFinder.start(); gatewayFinder.destroy(); expect(gatewayFinder.gatewayExits).toEqual([]); - expect(gatewayFinder.runner).toBeUndefined(); }); it('should destroy all gateway exit renders', () => { @@ -39,40 +39,11 @@ describe('EndGatewayExitFinder', () => { }); }); - describe('start', () => { - it('should start the runner if not already running', () => { - gatewayFinder.runner = void 0; - gatewayFinder.start(); - expect(gatewayFinder.runner).toBeDefined(); - }); - - it('should not start a new runner if one is already running', () => { - gatewayFinder.start(); - const existingRunner = gatewayFinder.runner; - gatewayFinder.start(); - expect(gatewayFinder.runner).toBe(existingRunner); - }); - }); - - describe('stop', () => { - it('should stop the runner if it is running', () => { - gatewayFinder.start(); - gatewayFinder.stop(); - expect(gatewayFinder.runner).toBeUndefined(); - }); - - it('should not throw if stop is called when no runner is running', () => { - gatewayFinder.runner = void 0; - expect(() => gatewayFinder.stop()).not.toThrow(); - }); - }); - - describe('onTick', () => { + describe('onTickTryFindTryFind', () => { let mockPlayer; beforeEach(() => { vi.resetAllMocks(); gatewayFinder.destroy(); - gatewayFinder = new EndGatewayExitFinder(); mockPlayer = { location: { x: 0, y: 0, z: 0 }, dimension: { @@ -80,21 +51,20 @@ describe('EndGatewayExitFinder', () => { getBlock: vi.fn(() => ({ id: "minecraft:end_gateway" })) } }; - vi.spyOn(world, "getPlayers").mockReturnValue([mockPlayer]); - gatewayFinder.start(); + gatewayFinder = new EndGatewayExitFinder(mockPlayer); }); describe('when player is standing in gateway', () => { it('should create an end gateway exit render at the player position one tick later', () => { - gatewayFinder.onTick(); + gatewayFinder.onTickTryFind(); scheduler.advanceTicks(1); expect(gatewayFinder.gatewayExits).toEqual([expect.objectContaining({ location: { x: 0, y: 0, z: 0 } })]); }); it('should not create duplicate entries for the same gateway exit', () => { - gatewayFinder.onTick(); + gatewayFinder.onTickTryFind(); scheduler.advanceTicks(1); - gatewayFinder.onTick(); + gatewayFinder.onTickTryFind(); scheduler.advanceTicks(1); expect(gatewayFinder.gatewayExits).toEqual([expect.objectContaining({ location: { x: 0, y: 0, z: 0 } })]); }); @@ -103,7 +73,7 @@ describe('EndGatewayExitFinder', () => { describe('when player is not standing in gateway', () => { it('should not create an end gateway exit render', () => { mockPlayer.dimension.getBlock.mockReturnValue({ id: "minecraft:stone" }); - gatewayFinder.onTick(); + gatewayFinder.onTickTryFind(); scheduler.advanceTicks(1); expect(gatewayFinder.gatewayExits).toEqual([]); }); @@ -111,17 +81,17 @@ describe('EndGatewayExitFinder', () => { it('should throw unknown errors from getBlock', () => { mockPlayer.dimension.getBlock.mockImplementation(() => { throw new Error("Test error"); }); - expect(() => gatewayFinder.onTick()).toThrow(); + expect(() => gatewayFinder.onTickTryFind()).toThrow(); }); it('should ignore LocationOutOfWorldBoundariesError from getBlock', () => { mockPlayer.dimension.getBlock.mockImplementation(() => { throw new LocationOutOfWorldBoundariesError(); }); - expect(() => gatewayFinder.onTick()).not.toThrow(); + expect(() => gatewayFinder.onTickTryFind()).not.toThrow(); }); it('should ignore LocationInUnloadedChunkError from getBlock', () => { mockPlayer.dimension.getBlock.mockImplementation(() => { throw new LocationInUnloadedChunkError(); }); - expect(() => gatewayFinder.onTick()).not.toThrow(); + expect(() => gatewayFinder.onTickTryFind()).not.toThrow(); }); }); }); \ No newline at end of file diff --git a/__tests__/BP/scripts/src/classes/EndGatewayExitRender.test.js b/__tests__/BP/scripts/src/classes/EndGatewayExitRender.test.js index f829418..50cc7d5 100644 --- a/__tests__/BP/scripts/src/classes/EndGatewayExitRender.test.js +++ b/__tests__/BP/scripts/src/classes/EndGatewayExitRender.test.js @@ -1,16 +1,19 @@ import { beforeAll, describe, expect, it, vi } from "vitest"; import { EndGatewayExitRender } from "../../../../../Canopy[BP]/scripts/src/classes/EndGatewayExitRender"; +import { Player } from "@minecraft/server"; describe('EndGatewayExitRender', () => { let exitRender; beforeAll(() => { + const player = new Player(); const dimension = { id: "minecraft:the_end" }; const location = { x: 0, y: 0, z: 0 }; - exitRender = new EndGatewayExitRender(dimension, location); + exitRender = new EndGatewayExitRender(player, dimension, location); }); describe('constructor', () => { it('should initialize properties correctly', () => { + expect(exitRender.player).toBeDefined(); expect(exitRender.location).toEqual({ x: 0, y: 0, z: 0 }); expect(exitRender.dimension).toEqual({ id: "minecraft:the_end" }); }); diff --git a/__tests__/BP/scripts/src/rules/infodisplay/BlockStates.test.js b/__tests__/BP/scripts/src/rules/infodisplay/BlockStates.test.js index c2b0b0b..4db8cf0 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/BlockStates.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/BlockStates.test.js @@ -1,7 +1,7 @@ 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 { InfoDisplayTextElement } from '../../../../../../Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayTextElement'; vi.mock('@minecraft/server', async (importOriginal) => { const original = await importOriginal(); diff --git a/__tests__/BP/scripts/src/rules/infodisplay/ChunkCoords.test.js b/__tests__/BP/scripts/src/rules/infodisplay/ChunkCoords.test.js index 441bedc..424a8b5 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/ChunkCoords.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/ChunkCoords.test.js @@ -1,7 +1,7 @@ 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 { InfoDisplayTextElement } from '../../../../../../Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayTextElement'; vi.mock('@minecraft/server', async (importOriginal) => { const original = await importOriginal(); diff --git a/__tests__/BP/scripts/src/rules/infodisplay/Dimension.test.js b/__tests__/BP/scripts/src/rules/infodisplay/Dimension.test.js index e0f7969..d73e69d 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/Dimension.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/Dimension.test.js @@ -1,6 +1,6 @@ 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 { 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) => { diff --git a/__tests__/BP/scripts/src/rules/infodisplay/PeekInventory.test.js b/__tests__/BP/scripts/src/rules/infodisplay/PeekInventory.test.js index 301c422..47c967e 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/PeekInventory.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/PeekInventory.test.js @@ -1,6 +1,6 @@ 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 { 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) => { diff --git a/__tests__/BP/scripts/src/rules/infodisplay/RenderEndGatewayExits.test.js b/__tests__/BP/scripts/src/rules/infodisplay/RenderEndGatewayExits.test.js new file mode 100644 index 0000000..71b3527 --- /dev/null +++ b/__tests__/BP/scripts/src/rules/infodisplay/RenderEndGatewayExits.test.js @@ -0,0 +1,51 @@ +import { beforeAll, describe, expect, it, vi } from "vitest"; +import { RenderEndGatewayExits } from "../../../../../../Canopy[BP]/scripts/src/rules/infodisplay/RenderEndGatewayExits"; +import { InfoDisplayRule } from "../../../../../../Canopy[BP]/scripts/lib/canopy/Canopy"; + +describe('RenderEndGatewayExits', () => { + let renderEndGatewayExits; + beforeAll(() => { + const player = { id: 'player1', dimension: { getBlock: () => ({}) }, location: {} }; + renderEndGatewayExits = new RenderEndGatewayExits(player); + }); + + describe('constructor', () => { + it('should create a new infodisplay rule', () => { + expect(renderEndGatewayExits.rule).toBeInstanceOf(InfoDisplayRule); + }); + + it('should have the correct identifier', () => { + expect(renderEndGatewayExits.identifier).toBe('renderEndGatewayExits'); + }); + }); + + describe('onEnable', () => { + beforeAll(() => { + renderEndGatewayExits.startRender(); + }); + + it('should start finding and rendering end gateway exits', () => { + const spy = vi.spyOn(renderEndGatewayExits.endGatewayExitFinder, 'onTickTryFind'); + renderEndGatewayExits.onTick(); + expect(spy).toHaveBeenCalled(); + }); + + it('should start finding and rendering end gateway exits', () => { + const spy = vi.spyOn(renderEndGatewayExits, 'startRender'); + renderEndGatewayExits.rule.onEnable(); + expect(spy).toHaveBeenCalled(); + }); + }); + + describe('onDisable', () => { + beforeAll(() => { + renderEndGatewayExits.startRender(); + }); + + it('should destroy the endGatewayExitFinder', () => { + const spy = vi.spyOn(renderEndGatewayExits.endGatewayExitFinder, 'destroy'); + renderEndGatewayExits.rule.onDisable(); + expect(spy).toHaveBeenCalled(); + }); + }); +}); \ 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 9dc7e85..1bb0d10 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/Speed.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/Speed.test.js @@ -1,6 +1,6 @@ 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 { 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) => { diff --git a/__tests__/BP/scripts/src/rules/infodisplay/Structures.test.js b/__tests__/BP/scripts/src/rules/infodisplay/Structures.test.js index 4ecce14..9f72ed0 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/Structures.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/Structures.test.js @@ -1,6 +1,6 @@ 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 { 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) => { diff --git a/__tests__/BP/scripts/src/rules/infodisplay/Weather.test.js b/__tests__/BP/scripts/src/rules/infodisplay/Weather.test.js index 0447f72..110fd85 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/Weather.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/Weather.test.js @@ -1,6 +1,6 @@ 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 { 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) => { From ba7af7b202ce3578a4c35b22e39c5ad34480d63a Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Sun, 26 Apr 2026 06:11:38 -0700 Subject: [PATCH 51/64] feat: complete renderEndGatewayExits --- .claude/settings.local.json | 8 + Canopy[BP]/scripts/main.js | 1 + .../src/classes/EndGatewayExitFinder.js | 103 +++++- .../src/classes/EndGatewayExitRender.js | 37 ++- .../scripts/src/classes/EndGatewayExits.js | 29 ++ .../src/rules/infodisplay/InfoDisplay.js | 9 + .../infodisplay/InfoDisplayShapeElement.js | 2 + .../infodisplay/RenderEndGatewayExits.js | 37 --- .../rules/infodisplay/RenderSignalStrength.js | 8 +- .../src/rules/renderEndGatewayExits.js | 30 ++ Canopy[RP]/texts/en_US.lang | 2 +- .../src/classes/EndGatewayExitFinder.test.js | 296 ++++++++++++++---- .../src/classes/EndGatewayExitRender.test.js | 64 ++-- .../src/classes/EndGatewayExits.test.js | 77 +++++ .../infodisplay/RenderEndGatewayExits.test.js | 51 --- .../src/rules/renderEndGatewayExits.test.js | 81 +++++ package-lock.json | 9 +- package.json | 2 +- 18 files changed, 632 insertions(+), 214 deletions(-) create mode 100644 .claude/settings.local.json create mode 100644 Canopy[BP]/scripts/src/classes/EndGatewayExits.js delete mode 100644 Canopy[BP]/scripts/src/rules/infodisplay/RenderEndGatewayExits.js create mode 100644 Canopy[BP]/scripts/src/rules/renderEndGatewayExits.js create mode 100644 __tests__/BP/scripts/src/classes/EndGatewayExits.test.js delete mode 100644 __tests__/BP/scripts/src/rules/infodisplay/RenderEndGatewayExits.test.js create mode 100644 __tests__/BP/scripts/src/rules/renderEndGatewayExits.test.js diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..f2f4ee8 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "Bash(npm test *)", + "Bash(grep -v \"^$\")" + ] + } +} diff --git a/Canopy[BP]/scripts/main.js b/Canopy[BP]/scripts/main.js index 613930e..e944168 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/EndGatewayExitFinder.js b/Canopy[BP]/scripts/src/classes/EndGatewayExitFinder.js index 60a643f..8829d69 100644 --- a/Canopy[BP]/scripts/src/classes/EndGatewayExitFinder.js +++ b/Canopy[BP]/scripts/src/classes/EndGatewayExitFinder.js @@ -1,12 +1,15 @@ -import { LocationInUnloadedChunkError, LocationOutOfWorldBoundariesError, system } from "@minecraft/server"; +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(player) { - this.player = player; + constructor() { + this.populateKnownExits(); } destroy() { @@ -15,30 +18,102 @@ export class EndGatewayExitFinder { this.gatewayExits.length = 0; } - onTickTryFind() { - const dimension = this.player.dimension; - if (this.isEndGateway(dimension, this.player.location)) - system.runTimeout(() => this.addEndGatewayExit(dimension, this.player.location), 1); + 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); } - isEndGateway(dimension, location) { + 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.getBlock(location)?.id === "minecraft:end_gateway"; + return dimension?.getBlocks(blockVolume, { includeTypes: ['minecraft:end_gateway'] })?.getCapacity() > 0; } catch (error) { - if (error instanceof LocationOutOfWorldBoundariesError || error instanceof LocationInUnloadedChunkError) + if (error instanceof UnloadedChunksError) return false; throw error; } } - addEndGatewayExit(dimension, location) { - if (this.exitIsKnown(location)) + tryAddEndGatewayExit(dimension, previousLocation, location) { + const flooredLocation = Vector.from(location).floor(); + if (!this.hasTraveledFar(previousLocation, flooredLocation) && !this.exitIsKnown(flooredLocation)) return; - const render = new EndGatewayExitRender(this.player, dimension, location); - this.gatewayExits.push({ location: Vector.from(location), dimension, render }); + 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 index b642eeb..73f70a5 100644 --- a/Canopy[BP]/scripts/src/classes/EndGatewayExitRender.js +++ b/Canopy[BP]/scripts/src/classes/EndGatewayExitRender.js @@ -1,13 +1,13 @@ import { debugDrawer, DebugBox, DebugLine } from "@minecraft/debug-utilities"; +import { Vector } from "../../lib/Vector"; export class EndGatewayExitRender { debugShapes = []; - SEARCH_AREA_SIZE = 16; - constructor(player, dimension, location) { - this.player = player; + constructor(dimension, location, searchAreaSize) { this.dimension = dimension; - this.location = location; + this.location = Vector.from(location).floor(); + this.searchAreaSize = searchAreaSize ?? 1; this.render(); } @@ -23,14 +23,14 @@ export class EndGatewayExitRender { } drawExit() { - const center = { x: this.location.x + 0.5, y: this.location.y + 0.5, z: this.location.z + 0.5 }; + const center = this.location.add(new Vector(0.5, 0.5, 0.5)); const box = new DebugBox(center); - box.color = { r: 1, g: 1, b: 0, a: 1 }; + box.color = { red: 1, green: 1, blue: 0, alpha: 1 }; this.drawShape(box); } drawSearchArea() { - const halfSize = this.SEARCH_AREA_SIZE / 2; + 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 }, @@ -38,16 +38,27 @@ export class EndGatewayExitRender { { x: this.location.x + halfSize, y: this.location.y, z: this.location.z - halfSize } ]; for (let i = 0; i < corners.length; i++) { - const start = corners[i]; - const end = corners[(i + 1) % corners.length]; - const line = new DebugLine(start, end); - line.color = { r: 0, g: 1, b: 0, a: 1 }; - this.drawShape(line); + 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, end); + line.color = { red: 0, green: 1, blue: 0, alpha: 1 }; + this.drawShape(line); + } + + drawEnclosingBox() { + const box = new DebugBox(this.location); + 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) { - shape.visibleTo = [this.player]; debugDrawer.addShape(shape, this.dimension); this.debugShapes.push(shape); } diff --git a/Canopy[BP]/scripts/src/classes/EndGatewayExits.js b/Canopy[BP]/scripts/src/classes/EndGatewayExits.js new file mode 100644 index 0000000..0d77626 --- /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/rules/infodisplay/InfoDisplay.js b/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplay.js index e451a64..feff955 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplay.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplay.js @@ -74,6 +74,7 @@ class InfoDisplay { new RenderSignalStrength(player) ]; InfoDisplay.playerToInfoDisplayMap[player.id] = this; + this.enableEnabledRules(); } update() { @@ -174,6 +175,14 @@ 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(() => { diff --git a/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayShapeElement.js b/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayShapeElement.js index c240e70..fe22749 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayShapeElement.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplayShapeElement.js @@ -1,6 +1,8 @@ import { InfoDisplayElement } from './InfoDisplayElement'; class InfoDisplayShapeElement extends InfoDisplayElement { + isRendering = false; + constructor(ruleData, isWorldwide = false) { const originalOnEnableCallback = ruleData.onEnableCallback; ruleData.onEnableCallback = () => { diff --git a/Canopy[BP]/scripts/src/rules/infodisplay/RenderEndGatewayExits.js b/Canopy[BP]/scripts/src/rules/infodisplay/RenderEndGatewayExits.js deleted file mode 100644 index 8d7a0f5..0000000 --- a/Canopy[BP]/scripts/src/rules/infodisplay/RenderEndGatewayExits.js +++ /dev/null @@ -1,37 +0,0 @@ -import { InfoDisplayShapeElement } from './InfoDisplayShapeElement'; -import { EndGatewayExitFinder } from '../../classes/EndGatewayExitFinder'; - -export class RenderEndGatewayExits extends InfoDisplayShapeElement { - player; - playerId; - endGatewayExitFinder; - - constructor(player) { - const ruleData = { - identifier: 'renderEndGatewayExits', - description: { translate: 'rules.infoDisplay.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 the exit to if the exit becomes invalid.', - onEnableCallback: () => this.startRender(), - onDisableCallback: () => this.stopRender() - }; - super(ruleData, 0); - this.player = player; - this.playerId = player.id; - } - - startRender() { - this.stopRender(); - this.endGatewayExitFinder = new EndGatewayExitFinder(this.player); - } - - stopRender() { - if (this.endGatewayExitFinder) { - this.endGatewayExitFinder.destroy(); - this.endGatewayExitFinder = void 0; - } - } - - onTick() { - this.endGatewayExitFinder?.onTickTryFind(); - } -} diff --git a/Canopy[BP]/scripts/src/rules/infodisplay/RenderSignalStrength.js b/Canopy[BP]/scripts/src/rules/infodisplay/RenderSignalStrength.js index e333b4f..877cd20 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/RenderSignalStrength.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/RenderSignalStrength.js @@ -14,19 +14,19 @@ class RenderSignalStrength extends InfoDisplayShapeElement { 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.startRender(), - onDisableCallback: () => this.stopRender() + onEnableCallback: () => this.start(), + onDisableCallback: () => this.stop() }; super(ruleData, 0); this.player = player; this.playerId = player.id; } - startRender() { + start() { this.signalStrengthRenderers = {}; } - stopRender() { + stop() { for (const [key, renderer] of Object.entries(this.signalStrengthRenderers)) { renderer.destroy(); delete this.signalStrengthRenderers[key]; diff --git a/Canopy[BP]/scripts/src/rules/renderEndGatewayExits.js b/Canopy[BP]/scripts/src/rules/renderEndGatewayExits.js new file mode 100644 index 0000000..d4f5bed --- /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[RP]/texts/en_US.lang b/Canopy[RP]/texts/en_US.lang index 5b2de33..a60426b 100644 --- a/Canopy[RP]/texts/en_US.lang +++ b/Canopy[RP]/texts/en_US.lang @@ -397,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. @@ -433,7 +434,6 @@ 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.renderEndGatewayExits=Renders the exit locations of end gateways after passing through them. 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 diff --git a/__tests__/BP/scripts/src/classes/EndGatewayExitFinder.test.js b/__tests__/BP/scripts/src/classes/EndGatewayExitFinder.test.js index 426b0c8..1ba6900 100644 --- a/__tests__/BP/scripts/src/classes/EndGatewayExitFinder.test.js +++ b/__tests__/BP/scripts/src/classes/EndGatewayExitFinder.test.js @@ -1,97 +1,257 @@ -import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { EndGatewayExitFinder } from "../../../../../Canopy[BP]/scripts/src/classes/EndGatewayExitFinder"; -import { LocationInUnloadedChunkError, LocationOutOfWorldBoundariesError, Player } from "@minecraft/server"; -import { scheduler } from "@forestoflight/minecraft-vitest-mocks"; -import { EndGatewayExitRender } from "../../../../../Canopy[BP]/scripts/src/classes/EndGatewayExitRender"; +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 gatewayFinder; - beforeAll(() => { - const player = new Player(); - gatewayFinder = new EndGatewayExitFinder(player); + 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('should initialize properties correctly', () => { - expect(gatewayFinder.gatewayExits).toEqual([]); - expect(gatewayFinder.player).toBeDefined(); + 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', () => { - let mockRender; - beforeAll(() => { - const player = new Player(); - mockRender = { location: { x: 1, y: 2, z: 3 }, render: new EndGatewayExitRender(player, { id: "minecraft:the_end" }, { x: 1, y: 2, z: 3 }) }; + 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); }); + }); - it('should clear gateway exits', () => { - gatewayFinder.gatewayExits.push(mockRender); - gatewayFinder.destroy(); - expect(gatewayFinder.gatewayExits).toEqual([]); + 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('should destroy all gateway exit renders', () => { - vi.spyOn(mockRender.render, "destroy"); - gatewayFinder.gatewayExits.push(mockRender); - gatewayFinder.destroy(); - expect(mockRender.render.destroy).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('onTickTryFindTryFind', () => { - let mockPlayer; - beforeEach(() => { - vi.resetAllMocks(); - gatewayFinder.destroy(); - mockPlayer = { - location: { x: 0, y: 0, z: 0 }, - dimension: { - id: "minecraft:the_end", - getBlock: vi.fn(() => ({ id: "minecraft:end_gateway" })) - } - }; - gatewayFinder = new EndGatewayExitFinder(mockPlayer); + 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); }); - describe('when player is standing in gateway', () => { - it('should create an end gateway exit render at the player position one tick later', () => { - gatewayFinder.onTickTryFind(); - scheduler.advanceTicks(1); - expect(gatewayFinder.gatewayExits).toEqual([expect.objectContaining({ location: { x: 0, y: 0, z: 0 } })]); - }); + 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('should not create duplicate entries for the same gateway exit', () => { - gatewayFinder.onTickTryFind(); - scheduler.advanceTicks(1); - gatewayFinder.onTickTryFind(); - scheduler.advanceTicks(1); - expect(gatewayFinder.gatewayExits).toEqual([expect.objectContaining({ location: { x: 0, y: 0, z: 0 } })]); - }); + 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); }); - describe('when player is not standing in gateway', () => { - it('should not create an end gateway exit render', () => { - mockPlayer.dimension.getBlock.mockReturnValue({ id: "minecraft:stone" }); - gatewayFinder.onTickTryFind(); - scheduler.advanceTicks(1); - expect(gatewayFinder.gatewayExits).toEqual([]); - }); + it('rethrows unknown errors', () => { + mockDimension.getBlocks.mockImplementation(() => { throw new Error('unexpected'); }); + expect(() => finder.isNearEndGateway(mockDimension, { x: 0, y: 64, z: 0 })).toThrow('unexpected'); }); + }); - it('should throw unknown errors from getBlock', () => { - mockPlayer.dimension.getBlock.mockImplementation(() => { throw new Error("Test error"); }); - expect(() => gatewayFinder.onTickTryFind()).toThrow(); + 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('should ignore LocationOutOfWorldBoundariesError from getBlock', () => { - mockPlayer.dimension.getBlock.mockImplementation(() => { throw new LocationOutOfWorldBoundariesError(); }); - expect(() => gatewayFinder.onTickTryFind()).not.toThrow(); + 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('should ignore LocationInUnloadedChunkError from getBlock', () => { - mockPlayer.dimension.getBlock.mockImplementation(() => { throw new LocationInUnloadedChunkError(); }); - expect(() => gatewayFinder.onTickTryFind()).not.toThrow(); + 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); }); }); -}); \ No newline at end of file +}); diff --git a/__tests__/BP/scripts/src/classes/EndGatewayExitRender.test.js b/__tests__/BP/scripts/src/classes/EndGatewayExitRender.test.js index 50cc7d5..579b69a 100644 --- a/__tests__/BP/scripts/src/classes/EndGatewayExitRender.test.js +++ b/__tests__/BP/scripts/src/classes/EndGatewayExitRender.test.js @@ -1,35 +1,59 @@ -import { beforeAll, describe, expect, it, vi } from "vitest"; -import { EndGatewayExitRender } from "../../../../../Canopy[BP]/scripts/src/classes/EndGatewayExitRender"; -import { Player } from "@minecraft/server"; +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', () => { - let exitRender; - beforeAll(() => { - const player = new Player(); - const dimension = { id: "minecraft:the_end" }; - const location = { x: 0, y: 0, z: 0 }; - exitRender = new EndGatewayExitRender(player, dimension, location); + 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('should initialize properties correctly', () => { - expect(exitRender.player).toBeDefined(); - expect(exitRender.location).toEqual({ x: 0, y: 0, z: 0 }); - expect(exitRender.dimension).toEqual({ id: "minecraft:the_end" }); + 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('should render the gateway exit', () => { - expect(exitRender.debugShapes.length).toBeGreaterThan(0); + it('renders shapes on construction', () => { + expect(render.debugShapes.length).toBeGreaterThan(0); + expect(debugDrawer.addShape).toHaveBeenCalled(); }); }); describe('destroy', () => { - it('should remove all debug shapes', () => { + it('calls remove on each debug shape', () => { const mockShape = { remove: vi.fn() }; - exitRender.debugShapes.push(mockShape); - exitRender.destroy(); + render.debugShapes.push(mockShape); + render.destroy(); expect(mockShape.remove).toHaveBeenCalled(); - expect(exitRender.debugShapes.length).toBe(0); + }); + + 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, dimension); + expect(render.debugShapes).toContain(shape); }); }); -}); \ No newline at end of file +}); 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 0000000..41c4adb --- /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/rules/infodisplay/RenderEndGatewayExits.test.js b/__tests__/BP/scripts/src/rules/infodisplay/RenderEndGatewayExits.test.js deleted file mode 100644 index 71b3527..0000000 --- a/__tests__/BP/scripts/src/rules/infodisplay/RenderEndGatewayExits.test.js +++ /dev/null @@ -1,51 +0,0 @@ -import { beforeAll, describe, expect, it, vi } from "vitest"; -import { RenderEndGatewayExits } from "../../../../../../Canopy[BP]/scripts/src/rules/infodisplay/RenderEndGatewayExits"; -import { InfoDisplayRule } from "../../../../../../Canopy[BP]/scripts/lib/canopy/Canopy"; - -describe('RenderEndGatewayExits', () => { - let renderEndGatewayExits; - beforeAll(() => { - const player = { id: 'player1', dimension: { getBlock: () => ({}) }, location: {} }; - renderEndGatewayExits = new RenderEndGatewayExits(player); - }); - - describe('constructor', () => { - it('should create a new infodisplay rule', () => { - expect(renderEndGatewayExits.rule).toBeInstanceOf(InfoDisplayRule); - }); - - it('should have the correct identifier', () => { - expect(renderEndGatewayExits.identifier).toBe('renderEndGatewayExits'); - }); - }); - - describe('onEnable', () => { - beforeAll(() => { - renderEndGatewayExits.startRender(); - }); - - it('should start finding and rendering end gateway exits', () => { - const spy = vi.spyOn(renderEndGatewayExits.endGatewayExitFinder, 'onTickTryFind'); - renderEndGatewayExits.onTick(); - expect(spy).toHaveBeenCalled(); - }); - - it('should start finding and rendering end gateway exits', () => { - const spy = vi.spyOn(renderEndGatewayExits, 'startRender'); - renderEndGatewayExits.rule.onEnable(); - expect(spy).toHaveBeenCalled(); - }); - }); - - describe('onDisable', () => { - beforeAll(() => { - renderEndGatewayExits.startRender(); - }); - - it('should destroy the endGatewayExitFinder', () => { - const spy = vi.spyOn(renderEndGatewayExits.endGatewayExitFinder, 'destroy'); - renderEndGatewayExits.rule.onDisable(); - expect(spy).toHaveBeenCalled(); - }); - }); -}); \ No newline at end of file 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 0000000..e2025e9 --- /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/package-lock.json b/package-lock.json index 0a90935..1c10e6e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "devDependencies": { "@eslint/compat": "^1.2.5", "@eslint/js": "^9.19.0", - "@forestoflight/minecraft-vitest-mocks": "^1.0.3", + "@forestoflight/minecraft-vitest-mocks": "^1.0.5", "@vitest/coverage-v8": "^3.0.4", "axios": "^1.7.9", "eslint": "^9.19.0", @@ -727,9 +727,9 @@ } }, "node_modules/@forestoflight/minecraft-vitest-mocks": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@forestoflight/minecraft-vitest-mocks/-/minecraft-vitest-mocks-1.0.3.tgz", - "integrity": "sha512-RkxAniODlGr0AEuAIcoP31yd1sxtBScbfyGrljaAWzzycqCQtODZwbqP42XAuGeq9ryUT9UMrn26kPqAr9aN7Q==", + "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": "*" @@ -3564,4 +3564,3 @@ } } } - diff --git a/package.json b/package.json index 7614e9c..593baf1 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "devDependencies": { "@eslint/compat": "^1.2.5", "@eslint/js": "^9.19.0", - "@forestoflight/minecraft-vitest-mocks": "^1.0.3", + "@forestoflight/minecraft-vitest-mocks": "^1.0.5", "@vitest/coverage-v8": "^3.0.4", "axios": "^1.7.9", "eslint": "^9.19.0", From 51ae3bca4f9b5e46f8d976ea216315d99c3e5bba Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Tue, 28 Apr 2026 12:35:07 -0700 Subject: [PATCH 52/64] feat: add HandDurability to InfoDisplay feat: register handDurability in InfoDisplay feat: add handDurability lang strings refactor: clean up handDurability --- .claude/settings.local.json | 3 +- .../src/rules/infodisplay/HandDurability.js | 47 ++++++++++ .../src/rules/infodisplay/InfoDisplay.js | 14 +-- Canopy[RP]/texts/en_US.lang | 2 + .../rules/infodisplay/HandDurability.test.js | 85 +++++++++++++++++++ 5 files changed, 144 insertions(+), 7 deletions(-) create mode 100644 Canopy[BP]/scripts/src/rules/infodisplay/HandDurability.js create mode 100644 __tests__/BP/scripts/src/rules/infodisplay/HandDurability.test.js diff --git a/.claude/settings.local.json b/.claude/settings.local.json index f2f4ee8..8b1ab16 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -2,7 +2,8 @@ "permissions": { "allow": [ "Bash(npm test *)", - "Bash(grep -v \"^$\")" + "Bash(grep -v \"^$\")", + "Bash(npx vitest *)" ] } } diff --git a/Canopy[BP]/scripts/src/rules/infodisplay/HandDurability.js b/Canopy[BP]/scripts/src/rules/infodisplay/HandDurability.js new file mode 100644 index 0000000..1df6f34 --- /dev/null +++ b/Canopy[BP]/scripts/src/rules/infodisplay/HandDurability.js @@ -0,0 +1,47 @@ +import { EntityComponentTypes, EquipmentSlot, ItemComponentTypes } from '@minecraft/server'; +import { InfoDisplayTextElement } from './InfoDisplayTextElement.js'; + +export class HandDurability extends InfoDisplayTextElement { + player; + + constructor(player, displayLine) { + const ruleData = { + identifier: 'handDurability', + description: { translate: 'rules.infoDisplay.handDurability' } + }; + super(ruleData, displayLine); + this.player = player; + } + + getFormattedDataOwnLine() { + const durabilityComponent = this.tryGetDurabilityComponent(); + if (!durabilityComponent) + return { text: '' }; + return { translate: 'rules.infoDisplay.handDurability.display', with: [this.getFormattedRatio(durabilityComponent)] }; + } + + getFormattedDataSharedLine() { + return this.getFormattedDataOwnLine(); + } + + tryGetDurabilityComponent() { + const equippable = this.player.getComponent(EntityComponentTypes.Equippable); + const itemStack = equippable?.getEquipment(EquipmentSlot.Mainhand); + if (!itemStack) + return void 0; + return itemStack.getComponent(ItemComponentTypes.Durability) || void 0; + } + + getFormattedRatio(durabilityComponent) { + const max = durabilityComponent.maxDurability; + const remaining = max - durabilityComponent.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/InfoDisplay.js b/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplay.js index feff955..8cdaa73 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplay.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplay.js @@ -29,6 +29,7 @@ import { Dimension } from './Dimension'; import { Weather } from './Weather'; import { LiquidTarget } from './LiquidTarget'; import { LiquidStates } from './LiquidStates'; +import { HandDurability } from './HandDurability'; import { RenderSignalStrength } from './RenderSignalStrength'; @@ -64,12 +65,13 @@ 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 HandDurability(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) ]; diff --git a/Canopy[RP]/texts/en_US.lang b/Canopy[RP]/texts/en_US.lang index a60426b..488c47f 100644 --- a/Canopy[RP]/texts/en_US.lang +++ b/Canopy[RP]/texts/en_US.lang @@ -418,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.handDurability=Shows the durability of the item in your main hand. +rules.infoDisplay.handDurability.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) diff --git a/__tests__/BP/scripts/src/rules/infodisplay/HandDurability.test.js b/__tests__/BP/scripts/src/rules/infodisplay/HandDurability.test.js new file mode 100644 index 0000000..9bc8c5d --- /dev/null +++ b/__tests__/BP/scripts/src/rules/infodisplay/HandDurability.test.js @@ -0,0 +1,85 @@ +import { HandDurability } from '../../../../../../Canopy[BP]/scripts/src/rules/infodisplay/HandDurability'; +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('HandDurability', () => { + let handDurability; + beforeAll(() => { + handDurability = new HandDurability(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(handDurability).toBeInstanceOf(InfoDisplayTextElement); + }); + + it('should create a new InfoDisplay rule', () => { + expect(Rules.get(handDurability.identifier)).toBeDefined(); + }); + + it('should return empty text when no item is held', () => { + mockEquippableComponent.getEquipment.mockReturnValueOnce(undefined); + expect(handDurability.getFormattedDataOwnLine()).toEqual({ text: '' }); + }); + + it('should return empty text when held item has no durability component', () => { + mockItemStack.getComponent.mockReturnValueOnce(undefined); + expect(handDurability.getFormattedDataOwnLine()).toEqual({ text: '' }); + }); + + it('should show green remaining for high durability (>=50%)', () => { + mockDurabilityComponent.damage = 0; + expect(handDurability.getFormattedDataOwnLine()).toEqual({ + translate: 'rules.infoDisplay.handDurability.display', + with: ['§a250§7/§a250§r'] + }); + }); + + it('should show yellow remaining for mid durability (10-49%)', () => { + mockDurabilityComponent.damage = 175; + expect(handDurability.getFormattedDataOwnLine()).toEqual({ + translate: 'rules.infoDisplay.handDurability.display', + with: ['§e75§7/§a250§r'] + }); + }); + + it('should show red remaining for low durability (<10%)', () => { + mockDurabilityComponent.damage = 232; + expect(handDurability.getFormattedDataOwnLine()).toEqual({ + translate: 'rules.infoDisplay.handDurability.display', + with: ['§c18§7/§a250§r'] + }); + }); + + it('should return same data for shared line as own line', () => { + expect(handDurability.getFormattedDataSharedLine()).toEqual( + handDurability.getFormattedDataOwnLine() + ); + }); +}); From 023e397b2257081b96d73d5c7d3fc442b62bd370 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Wed, 29 Apr 2026 00:38:03 -0700 Subject: [PATCH 53/64] fix: stale reference issue in handDurability --- .../src/rules/infodisplay/HandDurability.js | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/Canopy[BP]/scripts/src/rules/infodisplay/HandDurability.js b/Canopy[BP]/scripts/src/rules/infodisplay/HandDurability.js index 1df6f34..2decdbc 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/HandDurability.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/HandDurability.js @@ -14,32 +14,34 @@ export class HandDurability extends InfoDisplayTextElement { } getFormattedDataOwnLine() { - const durabilityComponent = this.tryGetDurabilityComponent(); - if (!durabilityComponent) + const durability = this.#tryGetDurabilityValues(); + if (!durability) return { text: '' }; - return { translate: 'rules.infoDisplay.handDurability.display', with: [this.getFormattedRatio(durabilityComponent)] }; + return { translate: 'rules.infoDisplay.handDurability.display', with: [this.#getFormattedRatio(durability)] }; } getFormattedDataSharedLine() { return this.getFormattedDataOwnLine(); } - tryGetDurabilityComponent() { + #tryGetDurabilityValues() { const equippable = this.player.getComponent(EntityComponentTypes.Equippable); const itemStack = equippable?.getEquipment(EquipmentSlot.Mainhand); if (!itemStack) return void 0; - return itemStack.getComponent(ItemComponentTypes.Durability) || void 0; + const durabilityComponent = itemStack.getComponent(ItemComponentTypes.Durability); + if (!durabilityComponent) + return void 0; + return { max: durabilityComponent.maxDurability, damage: durabilityComponent.damage }; } - getFormattedRatio(durabilityComponent) { - const max = durabilityComponent.maxDurability; - const remaining = max - durabilityComponent.damage; - const color = this.getDurabilityColor(remaining / max); + #getFormattedRatio({ max, damage }) { + const remaining = max - damage; + const color = this.#getDurabilityColor(remaining / max); return `${color}${remaining}§7/§a${max}§r`; } - getDurabilityColor(ratio) { + #getDurabilityColor(ratio) { if (ratio >= 0.5) return '§a'; if (ratio >= 0.1) return '§e'; return '§c'; From dfc4dfbf4a3ddbb8738d93acc242e4223a0813ac Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Wed, 29 Apr 2026 00:41:50 -0700 Subject: [PATCH 54/64] fix: end gateway renders across all dimensions --- .../scripts/src/classes/EndGatewayExitRender.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Canopy[BP]/scripts/src/classes/EndGatewayExitRender.js b/Canopy[BP]/scripts/src/classes/EndGatewayExitRender.js index 73f70a5..feef3af 100644 --- a/Canopy[BP]/scripts/src/classes/EndGatewayExitRender.js +++ b/Canopy[BP]/scripts/src/classes/EndGatewayExitRender.js @@ -23,7 +23,10 @@ export class EndGatewayExitRender { } drawExit() { - const center = this.location.add(new Vector(0.5, 0.5, 0.5)); + 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); @@ -46,20 +49,24 @@ export class EndGatewayExitRender { drawCornerToCornerLine(corners, i) { const start = corners[i]; const end = corners[(i + 1) % corners.length]; - const line = new DebugLine(start, end); + const line = new DebugLine({ ...start, dimension: this.dimension }, end); line.color = { red: 0, green: 1, blue: 0, alpha: 1 }; this.drawShape(line); } drawEnclosingBox() { - const box = new DebugBox(this.location); + 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.dimension); + debugDrawer.addShape(shape); this.debugShapes.push(shape); } } \ No newline at end of file From 441b37a017dc490dccc79938e9c25e8ba61d6c79 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Wed, 29 Apr 2026 00:48:06 -0700 Subject: [PATCH 55/64] feat: rename handDurability -> heldItemDurability --- .../{HandDurability.js => HeldItemDurability.js} | 8 ++++---- Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplay.js | 4 ++-- Canopy[RP]/texts/en_US.lang | 4 ++-- ...{HandDurability.test.js => HeldItemDurability.test.js} | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) rename Canopy[BP]/scripts/src/rules/infodisplay/{HandDurability.js => HeldItemDurability.js} (81%) rename __tests__/BP/scripts/src/rules/infodisplay/{HandDurability.test.js => HeldItemDurability.test.js} (94%) diff --git a/Canopy[BP]/scripts/src/rules/infodisplay/HandDurability.js b/Canopy[BP]/scripts/src/rules/infodisplay/HeldItemDurability.js similarity index 81% rename from Canopy[BP]/scripts/src/rules/infodisplay/HandDurability.js rename to Canopy[BP]/scripts/src/rules/infodisplay/HeldItemDurability.js index 2decdbc..a7bbcf9 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/HandDurability.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/HeldItemDurability.js @@ -1,13 +1,13 @@ import { EntityComponentTypes, EquipmentSlot, ItemComponentTypes } from '@minecraft/server'; import { InfoDisplayTextElement } from './InfoDisplayTextElement.js'; -export class HandDurability extends InfoDisplayTextElement { +export class HeldItemDurability extends InfoDisplayTextElement { player; constructor(player, displayLine) { const ruleData = { - identifier: 'handDurability', - description: { translate: 'rules.infoDisplay.handDurability' } + identifier: 'heldItemDurability', + description: { translate: 'rules.infoDisplay.heldItemDurability' } }; super(ruleData, displayLine); this.player = player; @@ -17,7 +17,7 @@ export class HandDurability extends InfoDisplayTextElement { const durability = this.#tryGetDurabilityValues(); if (!durability) return { text: '' }; - return { translate: 'rules.infoDisplay.handDurability.display', with: [this.#getFormattedRatio(durability)] }; + return { translate: 'rules.infoDisplay.heldItemDurability.display', with: [this.#getFormattedRatio(durability)] }; } getFormattedDataSharedLine() { diff --git a/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplay.js b/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplay.js index 8cdaa73..5253f3f 100644 --- a/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplay.js +++ b/Canopy[BP]/scripts/src/rules/infodisplay/InfoDisplay.js @@ -29,7 +29,7 @@ import { Dimension } from './Dimension'; import { Weather } from './Weather'; import { LiquidTarget } from './LiquidTarget'; import { LiquidStates } from './LiquidStates'; -import { HandDurability } from './HandDurability'; +import { HeldItemDurability } from './HeldItemDurability'; import { RenderSignalStrength } from './RenderSignalStrength'; @@ -65,7 +65,7 @@ class InfoDisplay { new EventTrackers(17), new HopperCounterCounts(18), new SimulationMap(player, 19), - new HandDurability(player, 20), + new HeldItemDurability(player, 20), new Target(player, 21), new SignalStrength(player, 21), new BlockStates(player, 22), diff --git a/Canopy[RP]/texts/en_US.lang b/Canopy[RP]/texts/en_US.lang index 488c47f..e764b2b 100644 --- a/Canopy[RP]/texts/en_US.lang +++ b/Canopy[RP]/texts/en_US.lang @@ -418,8 +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.handDurability=Shows the durability of the item in your main hand. -rules.infoDisplay.handDurability.display=Durability %s +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) diff --git a/__tests__/BP/scripts/src/rules/infodisplay/HandDurability.test.js b/__tests__/BP/scripts/src/rules/infodisplay/HeldItemDurability.test.js similarity index 94% rename from __tests__/BP/scripts/src/rules/infodisplay/HandDurability.test.js rename to __tests__/BP/scripts/src/rules/infodisplay/HeldItemDurability.test.js index 9bc8c5d..f2b9647 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/HandDurability.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/HeldItemDurability.test.js @@ -1,4 +1,4 @@ -import { HandDurability } from '../../../../../../Canopy[BP]/scripts/src/rules/infodisplay/HandDurability'; +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'; @@ -23,7 +23,7 @@ vi.spyOn(mockPlayer, 'getComponent').mockImplementation((type) => type === 'equi describe('HandDurability', () => { let handDurability; beforeAll(() => { - handDurability = new HandDurability(mockPlayer, 0); + handDurability = new HeldItemDurability(mockPlayer, 0); }); beforeEach(() => { mockDurabilityComponent.damage = 0; From a9ec2ba4bb6074ba55ef910efb4f9250b3ee5237 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Wed, 29 Apr 2026 01:04:07 -0700 Subject: [PATCH 56/64] fix: canopy menu defaults text fields to their current value --- Canopy[BP]/scripts/src/commands/canopy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Canopy[BP]/scripts/src/commands/canopy.js b/Canopy[BP]/scripts/src/commands/canopy.js index 027d7c7..afe9dec 100644 --- a/Canopy[BP]/scripts/src/commands/canopy.js +++ b/Canopy[BP]/scripts/src/commands/canopy.js @@ -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()}`); } From 3e6494c1b0ad15935e4414afcbfc573c46ccff3d Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Wed, 29 Apr 2026 01:23:25 -0700 Subject: [PATCH 57/64] perf: further optimize renderSignalStrength --- Canopy[BP]/scripts/src/classes/SignalStrengthRenderer.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Canopy[BP]/scripts/src/classes/SignalStrengthRenderer.js b/Canopy[BP]/scripts/src/classes/SignalStrengthRenderer.js index e29ab7b..0445899 100644 --- a/Canopy[BP]/scripts/src/classes/SignalStrengthRenderer.js +++ b/Canopy[BP]/scripts/src/classes/SignalStrengthRenderer.js @@ -40,7 +40,13 @@ export class SignalStrengthRenderer { this.stopRender(); return; } - this.textShape.setText(String(this.block.getRedstonePower())); + this.updateRedstonePower(); + } + + updateRedstonePower() { + const power = this.block.getRedstonePower(); + if (this.textShape.text !== String(power)) + this.textShape.setText(String(power)); } createTextShape() { @@ -51,6 +57,7 @@ export class SignalStrengthRenderer { this.textShape.rotation = { x: 90, y: 0, z: 0 }; this.textShape.useRotation = true; this.textShape.depthTest = true; + this.textShape.backfaceVisible = false; this.drawShape(); } From 52ceb08b2dbfe33910afc6e2660090829780ab81 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Wed, 6 May 2026 01:16:44 -0700 Subject: [PATCH 58/64] chore: remove .claude directory --- .claude/settings.local.json | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 8b1ab16..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(npm test *)", - "Bash(grep -v \"^$\")", - "Bash(npx vitest *)" - ] - } -} From 1c6b840b632636df260ec9b847005bc7628e94bb Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Wed, 6 May 2026 02:34:55 -0700 Subject: [PATCH 59/64] feat: bump versions to 1.5.6 & MC 1.26.20 --- Canopy[BP]/manifest.json | 122 +++++++++++++++++++------------- Canopy[BP]/scripts/constants.js | 4 +- Canopy[RP]/manifest.json | 32 +++++++-- README.md | 2 +- package-lock.json | 40 ++++++----- package.json | 8 +-- 6 files changed, 125 insertions(+), 83 deletions(-) diff --git a/Canopy[BP]/manifest.json b/Canopy[BP]/manifest.json index abeffd7..19802a3 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.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, 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]/scripts/constants.js b/Canopy[BP]/scripts/constants.js index 5c87a25..7c521ef 100644 --- a/Canopy[BP]/scripts/constants.js +++ b/Canopy[BP]/scripts/constants.js @@ -1,4 +1,4 @@ -const PACK_VERSION = '1.5.5'; -const MC_VERSION = '1.26.10.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[RP]/manifest.json b/Canopy[RP]/manifest.json index 10b81bb..25c7031 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/README.md b/README.md index 511acbe..9dcc095 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) diff --git a/package-lock.json b/package-lock.json index 1c10e6e..ba398c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,10 +7,10 @@ "name": "canopy", "license": "MIT", "dependencies": { - "@minecraft/debug-utilities": "^1.0.0-beta.1.26.20-preview.20", - "@minecraft/server": "^2.8.0-beta.1.26.20-preview.20", - "@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", @@ -878,9 +878,9 @@ "peer": true }, "node_modules/@minecraft/debug-utilities": { - "version": "1.0.0-beta.1.26.20-preview.20", - "resolved": "https://registry.npmjs.org/@minecraft/debug-utilities/-/debug-utilities-1.0.0-beta.1.26.20-preview.20.tgz", - "integrity": "sha512-neXYjfMaFTh9H52Ey5QsrjqAIOPUB5baPgX6TT+BP86vTnHP+zpxK3SL2u8Ppknysi56gWaSSykqunmedFuZag==", + "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", @@ -888,31 +888,33 @@ } }, "node_modules/@minecraft/server": { - "version": "2.8.0-beta.1.26.20-preview.20", - "resolved": "https://registry.npmjs.org/@minecraft/server/-/server-2.8.0-beta.1.26.20-preview.20.tgz", - "integrity": "sha512-3PknB6QJcV79O+apC+6RVJTawM7syQPL9Ie5DwUylJJ/ODXswYHHBWHs0JaoYbvdN6DY+a/WyxOomJUKmVS6Bg==", + "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", - "@minecraft/vanilla-data": ">=1.20.70 || 1.26.20-preview.20" + "@minecraft/vanilla-data": ">=1.20.70" } }, "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": { diff --git a/package.json b/package.json index 593baf1..6d04a37 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,10 @@ }, "license": "MIT", "dependencies": { - "@minecraft/debug-utilities": "^1.0.0-beta.1.26.20-preview.20", - "@minecraft/server": "^2.8.0-beta.1.26.20-preview.20", - "@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 --config vitest.config.js --coverage", From 44fb305ae51a8ee3cb61a21e2aba475d4f6bffa0 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Wed, 6 May 2026 02:35:29 -0700 Subject: [PATCH 60/64] fix: solve eslint errors --- docs/scripts/generate-wiki.js | 12 ++++++------ docs/scripts/generate-wiki.test.js | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/scripts/generate-wiki.js b/docs/scripts/generate-wiki.js index 89f0efa..f18dcad 100644 --- a/docs/scripts/generate-wiki.js +++ b/docs/scripts/generate-wiki.js @@ -2,8 +2,8 @@ import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; -const __dirname = fileURLToPath(new URL('.', import.meta.url)); -const projectRoot = path.resolve(__dirname, '../../'); +const currPath = fileURLToPath(new URL('.', import.meta.url)); +const projectRoot = path.resolve(currPath, '../../'); function parseLangFile(langPath) { const content = fs.readFileSync(langPath, 'utf8'); @@ -164,9 +164,9 @@ function injectCommandsPage(template, commandMap, lang) { return `### ${name}\n${block}`; }); - if (unlisted.length > 0) { + if (unlisted.length > 0) result += `\n\n## Unlisted Commands\n\n${unlisted.join('\n\n')}`; - } + return result; } @@ -211,8 +211,8 @@ async function getCommandMap() { if (cmd.isHelpHidden()) continue; commandMap.set(cmd.getName(), { instance: cmd, isVanilla: false }); } - for (const cmd of VanillaCommands.getAll()) { + 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 index 4e2d31f..98e5dde 100644 --- a/docs/scripts/generate-wiki.test.js +++ b/docs/scripts/generate-wiki.test.js @@ -13,8 +13,8 @@ describe('Wiki generator', () => { const wikiPath = process.env.WIKI_PATH; if (!wikiPath) throw new Error('WIKI_PATH environment variable is required.\nUsage: WIKI_PATH=../Canopy.wiki npm run generate-wiki'); - const __dirname = fileURLToPath(new URL('.', import.meta.url)); - const resolvedWikiPath = path.resolve(__dirname, wikiPath); + const currPath = fileURLToPath(new URL('.', import.meta.url)); + const resolvedWikiPath = path.resolve(currPath, wikiPath); const { main } = await import('./generate-wiki.js'); await main(resolvedWikiPath); From 0a4a207090fee3c2e4c534ce8d104a6c8a1a388b Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Wed, 6 May 2026 02:35:48 -0700 Subject: [PATCH 61/64] test: fix tests --- .../BP/scripts/lib/canopy/Extensions.test.js | 12 ++++++-- .../lib/canopy/commands/Commands.test.js | 10 +++++-- .../canopy/commands/VanillaCommands.test.js | 1 - .../lib/canopy/rules/AbilityRule.test.js | 2 ++ .../src/classes/EndGatewayExitRender.test.js | 2 +- .../BP/scripts/src/rules/dupeTnt.test.js | 24 ++++++++++----- .../infodisplay/HeldItemDurability.test.js | 30 +++++++++---------- vitest.config.js | 9 ++++-- 8 files changed, 57 insertions(+), 33 deletions(-) diff --git a/__tests__/BP/scripts/lib/canopy/Extensions.test.js b/__tests__/BP/scripts/lib/canopy/Extensions.test.js index 7a49ea5..ff33b6a 100644 --- a/__tests__/BP/scripts/lib/canopy/Extensions.test.js +++ b/__tests__/BP/scripts/lib/canopy/Extensions.test.js @@ -1,4 +1,4 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; +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(); @@ -19,6 +19,13 @@ import { Extension } from "../../../../../Canopy[BP]/scripts/lib/canopy/Extensio 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({ @@ -96,8 +103,7 @@ describe("Extensions", () => { describe('setupExtensionRegistration()', () => { it('should register an extension and log when the IPC message is received', () => { - const registerCall = IPC.on.mock.calls.find(([channel]) => channel === 'canopyExtension:registerExtension'); - const callback = registerCall[2]; + const callback = registerCallback; Extensions.clear(); const consoleSpy = vi.spyOn(console, 'info').mockImplementation(() => {}); diff --git a/__tests__/BP/scripts/lib/canopy/commands/Commands.test.js b/__tests__/BP/scripts/lib/canopy/commands/Commands.test.js index 432ac22..5a3d17d 100644 --- a/__tests__/BP/scripts/lib/canopy/commands/Commands.test.js +++ b/__tests__/BP/scripts/lib/canopy/commands/Commands.test.js @@ -1,4 +1,4 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; +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"; @@ -24,6 +24,12 @@ vi.mock('@minecraft/server', async (importOriginal) => { }); describe('Commands', () => { + let chatCallback; + + beforeAll(() => { + chatCallback = world.beforeEvents.chatSend.subscribe.mock.calls[0][0]; + }); + beforeEach(() => { Commands.clear(); }); @@ -292,13 +298,11 @@ describe('Commands', () => { }); describe('chatSend event handler', () => { - let chatCallback; let chatSender; beforeEach(() => { Commands.clear(); Rules.clear(); - chatCallback = world.beforeEvents.chatSend.subscribe.mock.calls[0][0]; chatSender = { sendMessage: vi.fn(), commandPermissionLevel: 1 }; }); diff --git a/__tests__/BP/scripts/lib/canopy/commands/VanillaCommands.test.js b/__tests__/BP/scripts/lib/canopy/commands/VanillaCommands.test.js index 3deb823..3567885 100644 --- a/__tests__/BP/scripts/lib/canopy/commands/VanillaCommands.test.js +++ b/__tests__/BP/scripts/lib/canopy/commands/VanillaCommands.test.js @@ -1,7 +1,6 @@ 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'; -import { CustomCommandParamType } from '@minecraft/server'; describe('VanillaCommands registry', () => { beforeEach(() => { diff --git a/__tests__/BP/scripts/lib/canopy/rules/AbilityRule.test.js b/__tests__/BP/scripts/lib/canopy/rules/AbilityRule.test.js index 2364f16..7b92b03 100644 --- a/__tests__/BP/scripts/lib/canopy/rules/AbilityRule.test.js +++ b/__tests__/BP/scripts/lib/canopy/rules/AbilityRule.test.js @@ -1,6 +1,7 @@ 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"; +import { world } from "@minecraft/server"; vi.mock('@minecraft/server', async (importOriginal) => { const original = await importOriginal(); @@ -41,6 +42,7 @@ describe('AbilityRule', () => { beforeEach(() => { Rules.clear(); vi.clearAllMocks(); + world.getAllPlayers.mockReturnValue([]); abilityRule = new AbilityRule(testRuleData, { slotNumber: 1, onPlayerEnableCallback, onPlayerDisableCallback }); }); diff --git a/__tests__/BP/scripts/src/classes/EndGatewayExitRender.test.js b/__tests__/BP/scripts/src/classes/EndGatewayExitRender.test.js index 579b69a..c44b814 100644 --- a/__tests__/BP/scripts/src/classes/EndGatewayExitRender.test.js +++ b/__tests__/BP/scripts/src/classes/EndGatewayExitRender.test.js @@ -52,7 +52,7 @@ describe('EndGatewayExitRender', () => { it('registers the shape with debugDrawer and tracks it in debugShapes', () => { const shape = { remove: vi.fn() }; render.drawShape(shape); - expect(debugDrawer.addShape).toHaveBeenCalledWith(shape, dimension); + expect(debugDrawer.addShape).toHaveBeenCalledWith(shape); expect(render.debugShapes).toContain(shape); }); }); diff --git a/__tests__/BP/scripts/src/rules/dupeTnt.test.js b/__tests__/BP/scripts/src/rules/dupeTnt.test.js index 5daf7b9..c6e8f49 100644 --- a/__tests__/BP/scripts/src/rules/dupeTnt.test.js +++ b/__tests__/BP/scripts/src/rules/dupeTnt.test.js @@ -1,4 +1,4 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; +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'; @@ -29,12 +29,24 @@ vi.mock('../../../../../Canopy[BP]/scripts/lib/canopy/Canopy', () => ({ })); 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' }, @@ -43,7 +55,6 @@ describe('dupeTnt Rule', () => { }); 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); @@ -56,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', @@ -79,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: { @@ -102,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/infodisplay/HeldItemDurability.test.js b/__tests__/BP/scripts/src/rules/infodisplay/HeldItemDurability.test.js index f2b9647..b78451e 100644 --- a/__tests__/BP/scripts/src/rules/infodisplay/HeldItemDurability.test.js +++ b/__tests__/BP/scripts/src/rules/infodisplay/HeldItemDurability.test.js @@ -20,10 +20,10 @@ const mockEquippableComponent = { const mockPlayer = new Player(); vi.spyOn(mockPlayer, 'getComponent').mockImplementation((type) => type === 'equippable' ? mockEquippableComponent : undefined); -describe('HandDurability', () => { - let handDurability; +describe('HeldItemDurability', () => { + let heldItemDurability; beforeAll(() => { - handDurability = new HeldItemDurability(mockPlayer, 0); + heldItemDurability = new HeldItemDurability(mockPlayer, 0); }); beforeEach(() => { mockDurabilityComponent.damage = 0; @@ -36,50 +36,50 @@ describe('HandDurability', () => { }); it('should inherit from InfoDisplayTextElement', () => { - expect(handDurability).toBeInstanceOf(InfoDisplayTextElement); + expect(heldItemDurability).toBeInstanceOf(InfoDisplayTextElement); }); it('should create a new InfoDisplay rule', () => { - expect(Rules.get(handDurability.identifier)).toBeDefined(); + expect(Rules.get(heldItemDurability.identifier)).toBeDefined(); }); it('should return empty text when no item is held', () => { mockEquippableComponent.getEquipment.mockReturnValueOnce(undefined); - expect(handDurability.getFormattedDataOwnLine()).toEqual({ text: '' }); + expect(heldItemDurability.getFormattedDataOwnLine()).toEqual({ text: '' }); }); it('should return empty text when held item has no durability component', () => { mockItemStack.getComponent.mockReturnValueOnce(undefined); - expect(handDurability.getFormattedDataOwnLine()).toEqual({ text: '' }); + expect(heldItemDurability.getFormattedDataOwnLine()).toEqual({ text: '' }); }); it('should show green remaining for high durability (>=50%)', () => { mockDurabilityComponent.damage = 0; - expect(handDurability.getFormattedDataOwnLine()).toEqual({ - translate: 'rules.infoDisplay.handDurability.display', + 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(handDurability.getFormattedDataOwnLine()).toEqual({ - translate: 'rules.infoDisplay.handDurability.display', + 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(handDurability.getFormattedDataOwnLine()).toEqual({ - translate: 'rules.infoDisplay.handDurability.display', + 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(handDurability.getFormattedDataSharedLine()).toEqual( - handDurability.getFormattedDataOwnLine() + expect(heldItemDurability.getFormattedDataSharedLine()).toEqual( + heldItemDurability.getFormattedDataOwnLine() ); }); }); diff --git a/vitest.config.js b/vitest.config.js index 537c3ff..1d834e0 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -6,10 +6,15 @@ export default defineConfig({ '@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` - }, - setupFiles: ['@forestoflight/minecraft-vitest-mocks/setup'] + } }, test: { + setupFiles: ['@forestoflight/minecraft-vitest-mocks/setup'], + server: { + deps: { + inline: ['@forestoflight/minecraft-vitest-mocks'] + } + }, env: { NODE_ENV: 'test' }, From 2e4654eb06b7b93842b786585ddfcc65db4aef6a Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Wed, 6 May 2026 02:38:41 -0700 Subject: [PATCH 62/64] chore: fix misconfigured path to the pack version --- filters/package_mcaddon/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filters/package_mcaddon/main.js b/filters/package_mcaddon/main.js index 11a5925..17f09bf 100644 --- a/filters/package_mcaddon/main.js +++ b/filters/package_mcaddon/main.js @@ -9,7 +9,7 @@ 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 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'; } From 8771ca86e33f8ac24dec8518057d57ada828915a Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Wed, 6 May 2026 04:16:25 -0700 Subject: [PATCH 63/64] fix: wiki generation errors --- .gitignore | 4 +++- .../scripts/lib/canopy/rules/BooleanRule.js | 5 ++++- Canopy[BP]/scripts/lib/canopy/rules/FloatRule.js | 2 +- .../scripts/lib/canopy/rules/IntegerRule.js | 2 +- Canopy[BP]/scripts/lib/canopy/rules/Rules.js | 1 + .../classes/debugdisplay/ViewDirectionVector.js | 4 ++-- docs/scripts/generate-wiki.js | 8 ++++---- docs/scripts/generate-wiki.test.js | 9 ++------- vitest.wiki.config.js | 15 +++++++++++++-- 9 files changed, 31 insertions(+), 19 deletions(-) diff --git a/.gitignore b/.gitignore index daa9548..17328fc 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ node_modules/ coverage/ /build /.regolith -/docs/superpowers/ \ No newline at end of file +/docs/superpowers/ +.claude/ +.env \ No newline at end of file diff --git a/Canopy[BP]/scripts/lib/canopy/rules/BooleanRule.js b/Canopy[BP]/scripts/lib/canopy/rules/BooleanRule.js index 291edf4..10d7993 100644 --- a/Canopy[BP]/scripts/lib/canopy/rules/BooleanRule.js +++ b/Canopy[BP]/scripts/lib/canopy/rules/BooleanRule.js @@ -4,8 +4,11 @@ export class BooleanRule extends Rule { constructor(options) { options.suggestedOptions = options.suggestedOptions ?? [false, true]; options.defaultValue = options.defaultValue ?? false; - options.onModifyCallback = (value) => this.onModifyBool(value); + /* eslint-disable prefer-const*/ + let self; + options.onModifyCallback = (value) => self?.onModifyBool(value); super({ ...options }); + self = this; this.onEnable = options.onEnableCallback || (() => {}); this.onDisable = options.onDisableCallback || (() => {}); } diff --git a/Canopy[BP]/scripts/lib/canopy/rules/FloatRule.js b/Canopy[BP]/scripts/lib/canopy/rules/FloatRule.js index 021cd6f..527db59 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/IntegerRule.js b/Canopy[BP]/scripts/lib/canopy/rules/IntegerRule.js index d20b61e..013c59f 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/Rules.js b/Canopy[BP]/scripts/lib/canopy/rules/Rules.js index 16fc385..b0b07bb 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/src/classes/debugdisplay/ViewDirectionVector.js b/Canopy[BP]/scripts/src/classes/debugdisplay/ViewDirectionVector.js index 919e3b3..39b0a71 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() { @@ -17,7 +17,7 @@ export class ViewDirectionVector extends DebugDisplayShapeElement { 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; } diff --git a/docs/scripts/generate-wiki.js b/docs/scripts/generate-wiki.js index f18dcad..a0ef5ad 100644 --- a/docs/scripts/generate-wiki.js +++ b/docs/scripts/generate-wiki.js @@ -174,16 +174,16 @@ function injectCommandsPage(template, commandMap, lang) { export async function main(wikiPath) { await import('../../Canopy[BP]/scripts/main.js'); const lang = parseLangFile(path.join(projectRoot, 'Canopy[RP]/texts/en_US.lang')); - generateRulesPages(wikiPath, lang); - generateCommandsPage(wikiPath, 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: () => {} }); + new InfoDisplay({ id: 'wiki-gen-mock', setDynamicProperty: () => {}, getDynamicProperty: () => {} }); - const allRules = Rules.rulesToRegister; + const allRules = Rules.getAll(); const globalRules = allRules.filter(r => r.getCategory() === 'Rules'); const infoDisplayRules = allRules.filter(r => r.getCategory() === 'InfoDisplay'); diff --git a/docs/scripts/generate-wiki.test.js b/docs/scripts/generate-wiki.test.js index 98e5dde..beaced9 100644 --- a/docs/scripts/generate-wiki.test.js +++ b/docs/scripts/generate-wiki.test.js @@ -2,16 +2,11 @@ import { describe, it } from 'vitest'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; -// This file is the entry point for wiki generation. Run via: -// npm run generate-wiki ../Canopy.wiki -// -// Vitest's Vite aliases automatically resolve @minecraft/server to the mock, -// allowing Canopy source files to be imported in Node.js context. - describe('Wiki generator', () => { it('generates wiki pages', { timeout: 60000 }, async () => { const wikiPath = process.env.WIKI_PATH; - if (!wikiPath) throw new Error('WIKI_PATH environment variable is required.\nUsage: WIKI_PATH=../Canopy.wiki npm run generate-wiki'); + 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); diff --git a/vitest.wiki.config.js b/vitest.wiki.config.js index af35d5a..b87d530 100644 --- a/vitest.wiki.config.js +++ b/vitest.wiki.config.js @@ -1,7 +1,13 @@ import { defineConfig } from 'vite'; export default defineConfig({ + server: { + fs: { + strict: false + } + }, resolve: { + preserveSymlinks: true, alias: { '@minecraft/server': `@forestoflight/minecraft-vitest-mocks/server`, '@minecraft/server-ui': `@forestoflight/minecraft-vitest-mocks/server-ui`, @@ -11,12 +17,17 @@ export default defineConfig({ 'src/classes/Instaminable': `${__dirname}/Canopy[BP]/scripts/src/classes/Instaminable.js`, 'src/rules/durabilityNotifier': `${__dirname}/Canopy[BP]/scripts/src/rules/durabilityNotifier.js`, }, - setupFiles: ['@forestoflight/minecraft-vitest-mocks/setup'] }, test: { env: { NODE_ENV: 'test' }, - include: ['docs/scripts/generate-wiki.test.js'] + include: ['docs/scripts/generate-wiki.test.js'], + setupFiles: ['@forestoflight/minecraft-vitest-mocks/setup'], + server: { + deps: { + inline: ['@forestoflight/minecraft-vitest-mocks'] + } + }, } }); From 463fad228079c1a73e060f4009690522cbd329e4 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Wed, 6 May 2026 15:45:03 -0700 Subject: [PATCH 64/64] chore: fix linting and testing issues --- .../scripts/lib/canopy/rules/BooleanRule.js | 5 +---- .../scripts/lib/canopy/rules/AbilityRule.test.js | 15 +++++---------- __tests__/BP/scripts/src/rules/tntFuse.test.js | 7 +++---- vitest.config.js | 5 +++++ vitest.wiki.config.js | 1 - 5 files changed, 14 insertions(+), 19 deletions(-) diff --git a/Canopy[BP]/scripts/lib/canopy/rules/BooleanRule.js b/Canopy[BP]/scripts/lib/canopy/rules/BooleanRule.js index 10d7993..291edf4 100644 --- a/Canopy[BP]/scripts/lib/canopy/rules/BooleanRule.js +++ b/Canopy[BP]/scripts/lib/canopy/rules/BooleanRule.js @@ -4,11 +4,8 @@ export class BooleanRule extends Rule { constructor(options) { options.suggestedOptions = options.suggestedOptions ?? [false, true]; options.defaultValue = options.defaultValue ?? false; - /* eslint-disable prefer-const*/ - let self; - options.onModifyCallback = (value) => self?.onModifyBool(value); + options.onModifyCallback = (value) => this.onModifyBool(value); super({ ...options }); - self = this; this.onEnable = options.onEnableCallback || (() => {}); this.onDisable = options.onDisableCallback || (() => {}); } diff --git a/__tests__/BP/scripts/lib/canopy/rules/AbilityRule.test.js b/__tests__/BP/scripts/lib/canopy/rules/AbilityRule.test.js index 7b92b03..bd95d12 100644 --- a/__tests__/BP/scripts/lib/canopy/rules/AbilityRule.test.js +++ b/__tests__/BP/scripts/lib/canopy/rules/AbilityRule.test.js @@ -113,8 +113,7 @@ describe('AbilityRule', () => { expect(abilityRule.isActionItemInActionSlot(player)).toBe(false); }); - it('should enable players that have the action item in slot when refreshed', async () => { - const { world } = await import('@minecraft/server'); + it('should enable players that have the action item in slot when refreshed', () => { const player = { id: 'player1', getComponent: vi.fn().mockReturnValue({ @@ -128,8 +127,7 @@ describe('AbilityRule', () => { expect(onPlayerEnableCallback).toHaveBeenCalledWith(player); }); - it('should disable players that do not have the action item in slot when refreshed', async () => { - const { world } = await import('@minecraft/server'); + it('should disable players that do not have the action item in slot when refreshed', () => { const player = { id: 'player1', getComponent: vi.fn().mockReturnValue({ @@ -142,22 +140,19 @@ describe('AbilityRule', () => { expect(onPlayerDisableCallback).toHaveBeenCalledWith(player); }); - it('should skip null players during refresh', async () => { - const { world } = await import('@minecraft/server'); + 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', async () => { - const { world } = await import('@minecraft/server'); + 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', async () => { - const { world } = await import('@minecraft/server'); + it('should unsubscribe from events when onDisable is called', () => { abilityRule.onDisable(); expect(world.afterEvents.playerInventoryItemChange.unsubscribe).toHaveBeenCalled(); expect(world.afterEvents.playerJoin.unsubscribe).toHaveBeenCalled(); diff --git a/__tests__/BP/scripts/src/rules/tntFuse.test.js b/__tests__/BP/scripts/src/rules/tntFuse.test.js index 95e316d..ea67849 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 { world } from "@minecraft/server"; const tntEntity = { typeId: 'minecraft:tnt', @@ -31,9 +32,7 @@ vi.mock('@minecraft/server', async (importOriginal) => { afterEvents: { ...original.world.afterEvents, entityLoad: { subscribe: vi.fn() } - }, - setDynamicProperty: (identifier, ticks) => { tntFuseDP = ticks }, - getDynamicProperty: () => tntFuseDP + } } }; }); @@ -62,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); }); diff --git a/vitest.config.js b/vitest.config.js index 1d834e0..47eeb81 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -1,6 +1,11 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ + server: { + fs: { + strict: false + } + }, resolve: { alias: { '@minecraft/server': `@forestoflight/minecraft-vitest-mocks/server`, diff --git a/vitest.wiki.config.js b/vitest.wiki.config.js index b87d530..9c5388c 100644 --- a/vitest.wiki.config.js +++ b/vitest.wiki.config.js @@ -7,7 +7,6 @@ export default defineConfig({ } }, resolve: { - preserveSymlinks: true, alias: { '@minecraft/server': `@forestoflight/minecraft-vitest-mocks/server`, '@minecraft/server-ui': `@forestoflight/minecraft-vitest-mocks/server-ui`,