Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
ad6a6fd
Update ja_JP.lang
cayduck Aug 31, 2026
519aa6f
refactor: autoItemPickup & carefulBreak
ForestOfLight Sep 7, 2026
69d542b
feat: instaminable quicker rising edge and refactor
ForestOfLight Sep 7, 2026
cad2cf9
refactor: autoItemPickup & carefulBreak
ForestOfLight Sep 7, 2026
114c66e
feat: add fastMineGlass
ForestOfLight Sep 7, 2026
a0fbade
fix: glass break sound for fastMineGlass
ForestOfLight Sep 7, 2026
82911a0
fix: add missing description for fastMineGlass
ForestOfLight Sep 7, 2026
751c4a2
feat: translated entity list has a count of names
ForestOfLight Sep 8, 2026
6f99c3c
feat: bump to v1.6.2 and MC 26.50 preview
ForestOfLight Sep 14, 2026
4c35cd4
feat: isTamed works on pre-canopy entities
ForestOfLight Sep 14, 2026
29e304a
Merge pull request #106 from cayduck/dev
ForestOfLight Sep 15, 2026
1d9bb32
fix: remove unused imports in utils test
ForestOfLight Sep 15, 2026
d116d7e
fix: bump RP/BP dependency version
ForestOfLight Sep 15, 2026
ea64a41
fix: bump Minecraft version badge in README
ForestOfLight Sep 15, 2026
0760e63
refactor: getColoredDimensionName & getColorByDimension
ForestOfLight Sep 15, 2026
649a45f
chore: bump dependencies
ForestOfLight Sep 16, 2026
3db9e4a
fix: handle custom dimensions in various features
ForestOfLight Sep 16, 2026
f0859dd
feat: /debugentity tame fully rely upon is_tamed component
ForestOfLight Sep 16, 2026
398761f
fix: slashes appearing in /data output
ForestOfLight Sep 16, 2026
3a1cc78
fix: blocks and entities show as this in their own /data output
ForestOfLight Sep 16, 2026
56c7289
chore: add BuyMeACoffee support and remove Patreon
ForestOfLight Sep 16, 2026
aaa7d7b
feat: set proper bedrock-samples MC version
ForestOfLight Sep 16, 2026
6acfe70
fix: initialize light level renderers correctly
blackman-eric Sep 15, 2026
1cc3216
test: cover render light level initialization
blackman-eric Sep 15, 2026
2e27fdf
ci: add Github token to ci
ForestOfLight Sep 16, 2026
a1eed26
Merge pull request #107 from blackman-eric/fix/render-light-level-init
ForestOfLight Sep 16, 2026
1fac5e1
fix: update hud title text for 26.50
blackman-eric Sep 15, 2026
b353c01
fix: offset away from the edge of the screen
ForestOfLight Sep 16, 2026
59e92c8
Merge pull request #108 from blackman-eric/fix/26.50-hud-title-text
ForestOfLight Sep 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# These are supported funding model platforms

github: ForestOfLight
patreon: ForestOfLight
patreon:
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
Expand All @@ -10,5 +10,5 @@ liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
buy_me_a_coffee: ForestOfLight
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ jobs:

- name: Run tests with coverage
run: npm test
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Upload coverage report
uses: actions/upload-artifact@v4
Expand Down
12 changes: 6 additions & 6 deletions Canopy[BP]/manifest.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
{
"format_version": 2,
"header": {
"name": "Canopy [BP] v1.6.1",
"name": "Canopy [BP] v1.6.2",
"description": "Technical informatics & features addon by §aForestOfLight§r.",
"uuid": "7f6b23df-a583-476b-b0e4-87457e65f7c0",
"version": [
1,
6,
1
2
],
"min_engine_version": [
1,
26,
40
50
]
},
"modules": [
Expand Down Expand Up @@ -42,11 +42,11 @@
"dependencies": [
{
"module_name": "@minecraft/server",
"version": "2.10.0-beta"
"version": "2.11.0-beta"
},
{
"module_name": "@minecraft/server-ui",
"version": "2.2.0-beta"
"version": "2.3.0-beta"
},
{
"module_name": "@minecraft/debug-utilities",
Expand All @@ -61,7 +61,7 @@
"version": [
1,
6,
1
2
]
}
],
Expand Down
4 changes: 2 additions & 2 deletions Canopy[BP]/scripts/constants.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const PACK_VERSION = '1.6.1';
const MC_VERSION = '1.26.40.05';
const PACK_VERSION = '1.6.2';
const MC_VERSION = '1.26.50.4';

export { PACK_VERSION, MC_VERSION };
80 changes: 29 additions & 51 deletions Canopy[BP]/scripts/include/utils.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { world, system, ItemStack, DimensionTypes } from '@minecraft/server';
import { world, system, DimensionTypes } from '@minecraft/server';
import { FormCancelationReason, uiManager } from '@minecraft/server-ui';
import { ProxyInventoryEntity } from '../src/classes/proxy/ProxyInventoryEntity';
import { PeekCaptureEntity } from '../src/classes/peek/PeekCaptureEntity';
Expand Down Expand Up @@ -79,30 +79,6 @@ export function wait(ms) {
return { startTime, endTime };
}

export function getInventory(block) {
const container = block.getComponent('inventory')?.container;
if (container === undefined) return {};
const items = {};
for (let i = 0; i < container.size; i++) {
const itemStack = container.getItem(i);
if (itemStack === undefined) continue;
items[i] = { typeId: itemStack.type.id, amount: itemStack.amount };
}
return items;
}

export function restoreInventory(block, items) {
const container = block.getComponent('inventory')?.container;
if (container === undefined)
return;
for (let i = 0; i < container.size; i++) {
const item = items[i];
if (item === undefined)
continue;
container.getSlot(i).setItem(new ItemStack(item.typeId, item.amount));
}
}

export function broadcastActionBar(message, sender) {
let players;
if (sender)
Expand All @@ -123,36 +99,30 @@ export function locationInArea(area, position) {
return inX && inY && inZ;
}

export function getColoredDimensionName(dimensionId) {
function getDimensionStyle(dimensionId) {
switch (dimensionId) {
case 'minecraft:overworld':
case 'overworld':
return '§aOverworld';
return { color: '§a', name: 'Overworld' };
case 'minecraft:nether':
case 'nether':
return '§cNether';
return { color: '§c', name: 'Nether' };
case 'minecraft:the_end':
case 'the_end':
return '§dEnd';
return { color: '§d', name: 'End' };
default:
return '§f' + dimensionId;
return { color: '§f', name: dimensionId };
}
}

export function getColoredDimensionName(dimensionId) {
const style = getDimensionStyle(dimensionId);
return style.color + style.name;
}

export function getColorByDimension(dimensionId) {
switch (dimensionId) {
case 'minecraft:overworld':
case 'overworld':
return '§a';
case 'minecraft:nether':
case 'nether':
return '§c';
case 'minecraft:the_end':
case 'the_end':
return '§d';
default:
return '§f';
}
const style = getDimensionStyle(dimensionId);
return style.color;
}

export function getScriptEventSourceName(event) {
Expand Down Expand Up @@ -257,15 +227,23 @@ export async function forceShow(player, form, { timeout = Infinity, showBusyMess

export function getTranslatedEntityList(entities) {
const message = { rawtext: [] };
for (let i = 0; i < entities.length; i++) {
const entity = entities[i];
if (entity.nameTag)
message.rawtext.push({ translate: entity.nameTag });
const groupedEntities = new Map();
for (const entity of entities) {
const name = entity.nameTag || entity.localizationKey;
const group = groupedEntities.get(name);
if (group)
group.count++;
else
message.rawtext.push({ translate: entities[i].localizationKey });
if (i !== entities.length - 1)
message.rawtext.push({ rawtext: [{ text: ', ' }] });
}
groupedEntities.set(name, { entity, count: 1 });
}

let index = 0;
for (const { entity, count } of groupedEntities.values()) {
message.rawtext.push({ translate: entity.nameTag || entity.localizationKey });
message.rawtext.push({ text: ` x${count}` });
if (index++ !== groupedEntities.size - 1)
message.rawtext.push({ text: ', ' });
}
return message;
}

Expand Down
2 changes: 2 additions & 0 deletions Canopy[BP]/scripts/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ import './src/rules/serverSideCollisionBoxes'
import './src/rules/entitySeparation'
import './src/rules/enderPearlChunkLoading'
import './src/rules/renderEndGatewayExits'
import './src/rules/carefulBreak'
import './src/rules/fastMineGlass'

// Simulated Player Rules
import './src/rules/simplayer/simplayerSaving'
Expand Down
7 changes: 4 additions & 3 deletions Canopy[BP]/scripts/src/classes/BlockRotator.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { world, StructureSaveMode, StructureRotation, BlockPermutation, StructureMirrorAxis } from "@minecraft/server";
import DirectionStateFinder from "../classes/DirectionState";
import { getInventory, restoreInventory } from "../../include/utils";
import { InventoryUtils } from "./InventoryUtils";

class BlockRotator {
static idPrefix = 'canopy:rotator-';
Expand Down Expand Up @@ -48,7 +48,7 @@ class BlockRotator {
if (!this.isValidId(structureId)) return console.warn('[BlockRotator] Invalid structure ID.');
const mirroredDirection = DirectionStateFinder.getMirroredDirection(block);
let axis;
const items = getInventory(block);
const items = InventoryUtils.getInventory(block);
if ([StructureMirrorAxis.X, StructureMirrorAxis.Z].includes(mirroredDirection)) {
axis = mirroredDirection;
} else { // block data has to be rebuilt manually 🎉
Expand All @@ -66,7 +66,8 @@ class BlockRotator {
mirror: axis
};
this.place(structureId, block, structurePlaceOptions);
if (Object.keys(items).length > 0) restoreInventory(block, items);
if (Object.keys(items).length > 0)
InventoryUtils.restoreInventory(block, items);
}

static place(structureId, block, structurePlaceOptions) {
Expand Down
5 changes: 3 additions & 2 deletions Canopy[BP]/scripts/src/classes/EntityMovementLog.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { EntityLog } from "./EntityLog";
import { world, system } from "@minecraft/server";
import { world, system, DimensionTypes } from "@minecraft/server";

class EntityMovementLog extends EntityLog {
validTypes = ['projectiles', 'falling_blocks'];
Expand Down Expand Up @@ -42,7 +42,8 @@ class EntityMovementLog extends EntityLog {
updateEntityLists() {
this.thisTickEntities = [];
this.movingEntities = [];
for (const dimensionId of ['overworld', 'nether', 'the_end']) {
const dimensionIds = DimensionTypes.getAll().map(dimensionType => dimensionType.typeId);
for (const dimensionId of dimensionIds) {
const dimEntities = world.getDimension(dimensionId).getEntities();
for (const entity of dimEntities) {
if (this.hasTrait(entity, this.type))
Expand Down
76 changes: 76 additions & 0 deletions Canopy[BP]/scripts/src/classes/FastMine.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { EntityComponentTypes, world } from "@minecraft/server";
import { autoItemPickup } from "../rules/autoItemPickup";
import { carefulBreak } from "../rules/carefulBreak";
import { InventoryUtils } from "./InventoryUtils";

export class FastMine {
#isBlockTypeCallback;
#playBreakSoundCallback;

constructor(blockTypeCallback, playBreakSoundCallback) {
this.#isBlockTypeCallback = blockTypeCallback;
this.#playBreakSoundCallback = playBreakSoundCallback;
this.onPlayerStartBreakingBlockBound = this.onPlayerStartBreakingBlock.bind(this);
}

subscribeToEvents() {
world.afterEvents.playerStartBreakingBlock.subscribe(this.onPlayerStartBreakingBlockBound);
}

unsubscribeFromEvents() {
world.afterEvents.playerStartBreakingBlock.unsubscribe(this.onPlayerStartBreakingBlockBound);
}

onPlayerStartBreakingBlock(event) {
if (!this.#isBlockTypeCallback(event.block.typeId))
return;
const player = event.player;
if (this.isEfficiencyFiveNetheritePick(event.heldItemStack))
this.breakBlock(player, event.block, event.heldItemStack);
}

isEfficiencyFiveNetheritePick(itemStack) {
if (itemStack && itemStack.typeId === 'minecraft:netherite_pickaxe') {
const enchants = itemStack.getComponent('minecraft:enchantable').getEnchantments();
return enchants.some(enchant => enchant.type.id === 'efficiency' && enchant.level === 5);
}
return false;
}

breakBlock(player, block, heldItemStack) {
const dimension = block.dimension;
const blockCenter = { x: block.x + 0.5, y: block.y + 0.5, z: block.z + 0.5 };
this.#playBreakSoundCallback(dimension, blockCenter);
this.createLoot(dimension, blockCenter, block, player, heldItemStack);
block.setType("minecraft:air");
}

createLoot(dimension, blockCenter, block, player, heldItemStack) {
const lootTableManager = world.getLootTableManager();
try {
const loot = lootTableManager.generateLootFromBlock(block, heldItemStack);
if (this.shouldAutoPickup(player))
this.addLootToPlayerInventory(player, loot);
else
this.spawnLoot(dimension, blockCenter, loot);
} catch (error) {
console.warn("[Canopy] Failed to generate and spawn loot for fast mine. Error", error, error.stack);
}
}

shouldAutoPickup(player) {
return (autoItemPickup.getNativeValue() && autoItemPickup.itemPickup.shouldPickup(player))
|| (carefulBreak.getNativeValue() && carefulBreak.itemPickup.shouldPickup(player));
}

spawnLoot(dimension, location, loot) {
for (const itemStack of loot)
dimension.spawnItem(itemStack, location);
}

addLootToPlayerInventory(player, loot) {
const playerInventory = player.getComponent(EntityComponentTypes.Inventory);
for (const itemStack of loot)
InventoryUtils.tryAddItemLikeVanilla(playerInventory?.container, itemStack);
}
}
42 changes: 23 additions & 19 deletions Canopy[BP]/scripts/src/classes/Instaminable.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
import { Rules} from "../../lib/canopy/Canopy";
import { system, world } from "@minecraft/server";

const beaconRefreshOffset = {};
const BEACON_REFRESH_RATE = 80;

class Instaminable {
constructor(litmusCallback, ruleId) {
this.litmusCallback = litmusCallback;
this.ruleId = ruleId;
this.initEvents();
export class Instaminable {
#isBlockTypeCallback;
#runner = void 0;

constructor(blockTypeCallback) {
this.#isBlockTypeCallback = blockTypeCallback;
this.onPlayerStartBreakingBlockBound = this.onPlayerStartBreakingBlock.bind(this);
this.onEffectAddBound = this.onEffectAdd.bind(this);
}

subscribeToEvents() {
this.#runner = system.runInterval(() => this.onTick());
world.afterEvents.playerStartBreakingBlock.subscribe(this.onPlayerStartBreakingBlockBound);
world.afterEvents.effectAdd.subscribe(this.onEffectAddBound);
}

initEvents() {
system.runInterval(this.onTick.bind(this), 1);
world.beforeEvents.playerBreakBlock.subscribe((event) => this.onPlayerBreakBlock(event));
world.afterEvents.effectAdd.subscribe((event) => this.onEffectAdd(event));
unsubscribeFromEvents() {
if (this.#runner !== void 0)
system.clearRun(this.#runner);
world.afterEvents.playerStartBreakingBlock.unsubscribe(this.onPlayerStartBreakingBlockBound);
world.afterEvents.effectAdd.unsubscribe(this.onEffectAddBound);
}

onTick() {
Expand All @@ -26,15 +35,12 @@ class Instaminable {
}
}

onPlayerBreakBlock(event) {
const blockId = event.block.typeId;
if (Rules.getNativeValue(this.ruleId) !== true)
return;
if (!this.litmusCallback(blockId))
onPlayerStartBreakingBlock(event) {
if (!this.#isBlockTypeCallback(event.block.typeId))
return;
const player = event.player;
if (this.isEfficiencyFiveNetheritePick(event.itemStack) && this.hasHasteTwo(player)) {
const duration = player.getEffect('haste')?.duration;
if (this.isEfficiencyFiveNetheritePick(event.heldItemStack) && this.hasHasteTwo(player)) {
const duration = player.getEffect("haste")?.duration;
if (duration > 0)
system.run(() => player.addEffect('haste', duration, { amplifier: 2 }));
}
Expand Down Expand Up @@ -63,5 +69,3 @@ class Instaminable {
return haste?.amplifier === 1;
}
}

export default Instaminable;
Loading
Loading