Following the notice that block events will be removed in a future update, I have been implementing custom block mechanics (such as crop growth and groundcover spreading) using server-side scripts instead. This particular script accompanies a custom block called 'rhizome' which randomly spreads to adjacent blocks, and decays if covered by a solid block, in the same way as vanilla grass/mycelium blocks do.
It works exactly as intended, and doesn't seem to be causing perceptible lag, but occasionally displays warnings of the type
[Scripting][warning]-[Watchdog] 180.289 ms script spike detected in behavior pack 'PackName'
which leads me to believe it may be unreasonably resource intensive. (For comparison, I have implemented multiple other custom features using scripts, including some that run many sequential setblock
and fill
commands, and have never gotten this kind of warning before.)
The problem seems to be the nested for
loops to check for all blocks of a certain type within 16 blocks of the player: reducing the radius from 20 down to the current 16 blocks decreased the number of milliseconds in the warning from ~340 to ~180, indicating that this is indeed the bottleneck.
It doesn't seem like there is a built-in API method for getting all blocks of a certain type within an area, like there is for entities (with dimension.getEntities()
). No such method is listed in the dimension
documentation. So, I'm not sure that there's an alternative to using nested loops like this, but would like to know if there are any performance improvements possible without decreasing the radius even further.
While 180 milliseconds doesn't seem like much of an issue, I have only tested my addon in singleplayer, and anticipate it could be a much larger problem on a multiplayer server where allPlayers.length
> 1. I would like the addon to be compatible with multiplayer as well as singleplayer worlds.
import { world, system } from "@minecraft/server"
const worldHeightLimit = 320;
const worldDepthLimit = -64;
/* Within 16 blocks of a player, check for rhizome spreading to adjacent end stone blocks, or decaying if there is a solid block directly on top of it */
function processRhizome(player) {
var centerX = player.location.x;
var centerY = player.location.y;
var centerZ = player.location.z;
var dim = player.dimension;
var rB = []; // Will store all rhizome blocks
var block;
for (var y = -16; y <= 16; y++) {
// If layer is below bottom bedrock level, skip to next layer
if ((centerY + y) < worldDepthLimit) {
continue;
} else if ((centerY + y) > worldHeightLimit) { // If layer is above height limit, exit loop
break;
} else {
for (var x = -16; x <= 16; x++) {
for (var z = -16; z <= 16; z++) {
block = dim.getBlock({x: centerX + x, y: centerY + y, z: centerZ + z});
if (block.typeId === "end:rhizome") {
rB.push(block);
}
}
}
}
}
/* If a rhizome block has adjacent end stone block neighbors (which do not have a solid block directly above them), generate a random number to test whether the rhizome should spread.
If yes, then choose an end stone block from among the neighboring blocks at random and convert it to another rhizome block.
If the rhizome block has a solid block directly above it, generate a different random number to test whether the rhizome block should decay into end stone. */
var b;
for (var i = 0; i < rB.length; i++) {
b = rB[i];
// Decay chance = 25% per random tick
if (!b.above(1).isAir && !b.above(1).isLiquid && (Math.random() < 0.25)) {
(async () => {
await dim.runCommandAsync("setblock " + b.x + " " + b.y + " " + b.z + " minecraft:end_stone replace");
})();
} else if (Math.random() < 0.05) { // Spread chance = 5% per random tick
var neighbors = [];
for (var y = -1; y <= 1; y++) {
if ((b.y + y) < worldDepthLimit) {
continue;
} else if ((b.y + y) > worldHeightLimit) {
break;
} else {
for (var x = -1; x <= 1; x++) {
for (var z = -1; z <= 1; z++) {
block = dim.getBlock({x: b.x + x, y: b.y + y, z: b.z + z});
// Do not add block to neighbors array if it is directly under a solid block, or if it is directly above the current rhizome block
if (block.typeId === "minecraft:end_stone" && (block.above(1).isAir || block.above(1).isLiquid) && ([x, y, z].toString() !== "0,1,0")) {
neighbors.push(block);
}
}
}
}
}
if (neighbors.length > 0) {
var spreadToBlock = neighbors[Math.floor(Math.random()*neighbors.length)];
(async () => {
await dim.runCommandAsync("setblock " + spreadToBlock.x + " " + spreadToBlock.y + " " + spreadToBlock.z + " end:rhizome replace");
})();
}
}
}
}
function doRandomTickEventsForEachPlayer() {
const allPlayers = world.getAllPlayers();
for (var p = 0; p < allPlayers.length; p++) {
processRhizome(allPlayers[p]);
}
var nextInterval = 60 + Math.round(Math.random()*40); // Between 3 and 5 seconds
system.runTimeout(doRandomTickEventsForEachPlayer, nextInterval);
}
doRandomTickEventsForEachPlayer();