MURS:Специальные скрипты для особых задач: различия между версиями
Tringoli (обсуждение | вклад) мНет описания правки |
Tringoli (обсуждение | вклад) Доработки кода |
||
| Строка 35: | Строка 35: | ||
<syntaxhighlight lang="javascript"> | <syntaxhighlight lang="javascript"> | ||
// Умное освещение окон | // ========================================================================= | ||
// Умное автоматическое освещение окон (JS Macros 1.19.4) | |||
// ========================================================================= | |||
const radius = 15; // Радиус сканирования вокруг игрока | const radius = 15; // Радиус сканирования вокруг игрока | ||
const lightRatio = 0.3; // 30% свет, 70% тьма | const lightRatio = 0.3; // Шанс горящего окна (30% свет, 70% тьма) | ||
const blocksPerTick = 4; // Ускорение | const blocksPerTick = 4; // Ускорение (4 блока за 1 тик = 80 блоков/сек) | ||
// Идентификаторы блоков света и тьмы | // Идентификаторы блоков света и тьмы | ||
| Строка 46: | Строка 49: | ||
const player = Player.getPlayer(); | const player = Player.getPlayer(); | ||
// Главный вектор взгляда игрока вглубь | // Главный вектор взгляда игрока вглубь здания | ||
function getPlayerInwardVector() { | function getPlayerInwardVector() { | ||
let yaw = player.getYaw() % 360; | let yaw = player.getYaw() % 360; | ||
| Строка 64: | Строка 67: | ||
let lookZ = Math.cos(yawRad); | let lookZ = Math.cos(yawRad); | ||
// Проверка: находится ли окно перед игроком | // Проверка: находится ли окно перед игроком | ||
function isInFront(x, z, px, pz) { | function isInFront(x, z, px, pz) { | ||
let dirX = x - px; | let dirX = x - px; | ||
| Строка 73: | Строка 76: | ||
} | } | ||
// | // 🔍 ПРОВЕРКА: является ли блок окном (с фильтром внешних рамок) | ||
function isWindow(id) { | function isWindow(id) { | ||
let s = String(id).toLowerCase(); | let s = String(id).toLowerCase(); | ||
// 🚫 Исключаем парапеты, перила и | // 🚫 Исключаем внешние парапеты, перила и декоративные планки | ||
if (s.includes("parapet") || s.includes("railing") || s.includes(" | if (s.includes("sideway_frame") || | ||
s.includes("parapet") || | |||
s.includes("railing") || | |||
s.includes("balcony") || | |||
s.includes("mullion") || | |||
s.includes("window_sill")) { | |||
return false; | return false; | ||
} | } | ||
| Строка 90: | Строка 98: | ||
} | } | ||
// | // 🚫 ПРОВЕРКА КРАЙНИХ ЧАСТЕЙ (Подоконники и верхние карнизы) | ||
function | function isEdgePart(id) { | ||
let s = String(id).toLowerCase(); | let s = String(id).toLowerCase(); | ||
return s.includes("_bottom") || | return s.includes("_bottom") || | ||
s.includes("_top") || | |||
s.includes("window_bottom") || | s.includes("window_bottom") || | ||
s.includes("window_top") || | |||
s.includes("sill") || | s.includes("sill") || | ||
s.includes("_lower") || | s.includes("_lower") || | ||
s.includes(" | s.includes("_upper") || | ||
s.includes("parapet"); | s.includes("parapet"); | ||
} | } | ||
// | // 🧬 УМНОЕ ОПРЕДЕЛЕНИЕ СЕМЕЙСТВА ОКНА (Нормализация для разных модов) | ||
function | function getWindowFamily(id) { | ||
let | let s = String(id).toLowerCase(); | ||
{ | // 1. Мод Chisel (chisel:light/glass, chisel:shale/glass, chisel:noborder/glass) | ||
if (s.startsWith("chisel:")) { | |||
let colorMatch = s.match(/(white|orange|magenta|light_blue|yellow|lime|pink|gray|light_gray|cyan|purple|blue|brown|green|red|black)_glass/); | |||
if (colorMatch) { | |||
{ | return `chisel:${colorMatch[1]}_glass`; | ||
} | |||
return "chisel:glass"; | |||
} | |||
// 2. Мод Macaw's Windows (mcwwindows:spruce_plank_window, mcwwindows:stripped_spruce_log_window) | |||
if (s.startsWith("mcwwindows:")) { | |||
let materials = [ | |||
"spruce", "oak", "birch", "dark_oak", "jungle", "acacia", | |||
"mangrove", "cherry", "bamboo", "crimson", "warped", "iron", | |||
"quartz", "stone", "granite", "diorite", "andesite", "brick" | |||
]; | |||
for (let i = 0; i < materials.length; i++) { | |||
let mat = materials[i]; | |||
if (s.includes(mat)) { | |||
return `mcwwindows:${mat}`; | |||
} | |||
} | |||
s = s.replace(/_(plank|stripped|log|pane|window|tall|gothic|parapet|four|two|single|double)$/g, ""); | |||
return s; | |||
} | |||
// 3. Мод Yuushya (yuushya:white_windows_0/1/2, classical_white_window_top) | |||
if (s.startsWith("yuushya:")) { | |||
s = s.replace(/_(\d+|bottom|middle|top|upper|lower|left|right|center|corner|diagonal|part\d*|sideway_frame)$/g, ""); | |||
s = s.replace(/_(\d+|bottom|middle|top|upper|lower|left|right|center|corner|diagonal|part\d*|sideway_frame)$/g, ""); | |||
return s; | |||
} | |||
// 4. Ванилла и Chipped | |||
s = s.replace(/_(\d+|bottom|middle|top|upper|lower|left|right|center|corner|diagonal|part\d*)$/g, ""); | |||
s = s.replace(/_(\d+|bottom|middle|top|upper|lower|left|right|center|corner|diagonal|part\d*)$/g, ""); | |||
return s; | |||
} | |||
// 📐 СТРОГОЕ ВЕКТОРНОЕ ОПРЕДЕЛЕНИЕ ТОЧКИ ИНТЕРЬЕРА | |||
function getExactInteriorTarget(x, y, z, px, pz) { | |||
let block = World.getBlock(x, y, z); | |||
let blockFacing = null; | |||
let | try { | ||
let state = block.getBlockState(); | |||
if (state) { | |||
try { | |||
let f = state.get("facing"); | |||
if (f) blockFacing = String(f).toLowerCase(); | |||
} catch(e) {} | |||
if (!blockFacing) { | |||
let str = String(state); | |||
let match = str.match(/facing[=:]\s*([a-z_]+)/i); | |||
if (match) blockFacing = match[1].toLowerCase(); | |||
} | } | ||
} | } | ||
} catch(e) {} | |||
let dx = 0, dz = 0; | |||
if (blockFacing) { | |||
if (blockFacing.includes("south")) dz = -1; | |||
if ( | else if (blockFacing.includes("north")) dz = 1; | ||
if ( | else if (blockFacing.includes("east")) dx = -1; | ||
if ( | else if (blockFacing.includes("west")) dx = 1; | ||
} | |||
if (dx === 0 && dz === 0) { | |||
dx = mainInward.dx; | |||
dz = mainInward.dz; | |||
} | } | ||
let winDistSq = (x - px) * (x - px) + (z - pz) * (z - pz); | |||
if ( | let targetDistSq = ((x + dx) - px) * ((x + dx) - px) + ((z + dz) - pz) * ((z + dz) - pz); | ||
if (targetDistSq < winDistSq) { | |||
dx = -dx; | |||
dz = -dz; | |||
} | } | ||
// | return { x: x + dx, y: y, z: z + dz }; | ||
} | |||
// 🛡 ЛОКАЛЬНАЯ ПРОВЕРКА СУЩЕСТВУЮЩИХ БЛОКОВ | |||
function getLocalExistingBlock(targetX, targetY, targetZ) { | |||
let offsets = [ | |||
{x: 0, y: 1, z: 0}, {x: 0, y: -1, z: 0}, | |||
{x: 1, y: 0, z: 0}, {x: -1, y: 0, z: 0}, | |||
{x: 0, y: 0, z: 1}, {x: 0, y: 0, z: -1} | |||
]; | |||
for (let i = 0; i < offsets.length; i++) { | |||
let o = offsets[i]; | |||
let bId = World.getBlock(targetX + o.x, targetY + o.y, targetZ + o.z).getId(); | |||
if (bId === blockLight || bId === blockDark) { | |||
return bId; | |||
} | |||
} | |||
return null; | |||
} | } | ||
| Строка 176: | Строка 223: | ||
let visited = new Set(); | let visited = new Set(); | ||
let | let tasksToBuild = []; | ||
function getPosKey(x, y, z) { | function getPosKey(x, y, z) { | ||
| Строка 182: | Строка 229: | ||
} | } | ||
Chat.log(`§e[Урбанист] §fСканирую фасад ( | Chat.log(`§e[Урбанист] §fСканирую фасад (Нормализация модов Macaw's / Chisel / Yuushya: §aАКТИВНА§f)...`); | ||
// 1. Поиск и группировка окон (Flood Fill) | // 1. Поиск и группировка окон (Flood Fill) | ||
| Строка 200: | Строка 247: | ||
} | } | ||
let startFamily = getWindowFamily(blockId); | |||
let stack = [{x: x, y: y, z: z}]; | let stack = [{x: x, y: y, z: z}]; | ||
visited.add(key); | visited.add(key); | ||
let | let groupTargets = []; | ||
while (stack.length > 0) { | while (stack.length > 0) { | ||
| Строка 210: | Строка 257: | ||
let currId = World.getBlock(curr.x, curr.y, curr.z).getId(); | let currId = World.getBlock(curr.x, curr.y, curr.z).getId(); | ||
if (! | if (!isEdgePart(currId)) { | ||
let target = | let target = getExactInteriorTarget(curr.x, curr.y, curr.z, px, pz); | ||
let behindId = World.getBlock(target.x, target.y, target.z).getId(); | let behindId = World.getBlock(target.x, target.y, target.z).getId(); | ||
if (behindId === "minecraft:air" || behindId === "minecraft:cave_air") { | |||
groupTargets.push({ targetX: target.x, targetY: target.y, targetZ: target.z }); | |||
} | } | ||
} | } | ||
| Строка 237: | Строка 282: | ||
if (Math.abs(n.x - px) <= radius && Math.abs(n.y - py) <= radius && Math.abs(n.z - pz) <= radius) { | if (Math.abs(n.x - px) <= radius && Math.abs(n.y - py) <= radius && Math.abs(n.z - pz) <= radius) { | ||
let nId = World.getBlock(n.x, n.y, n.z).getId(); | let nId = World.getBlock(n.x, n.y, n.z).getId(); | ||
if (isWindow(nId)) { | |||
// 🌟 Собираем соседние блоки, если они из одного семейства мода | |||
if (isWindow(nId) && getWindowFamily(nId) === startFamily) { | |||
visited.add(nKey); | visited.add(nKey); | ||
stack.push(n); | stack.push(n); | ||
| Строка 246: | Строка 293: | ||
} | } | ||
if ( | if (groupTargets.length > 0) { | ||
let | let defaultGroupBlock = (Math.random() <= lightRatio) ? blockLight : blockDark; | ||
for (let k = 0; k < groupTargets.length; k++) { | |||
let t = groupTargets[k]; | |||
let localExist = getLocalExistingBlock(t.targetX, t.targetY, t.targetZ); | |||
tasksToBuild.push({ | |||
targetX: t.targetX, | |||
targetY: t.targetY, | |||
targetZ: t.targetZ, | |||
blockToPlace: localExist ? localExist : defaultGroupBlock | |||
}); | |||
} | |||
} | } | ||
} | } | ||
| Строка 259: | Строка 313: | ||
} | } | ||
Chat.log(`§e[Урбанист] §fСформировано | Chat.log(`§e[Урбанист] §fСформировано корректных окон: §a${tasksToBuild.length}§f. Начинаю стройку!`); | ||
let changed = 0; | let changed = 0; | ||
// 2. | // 2. Ускоренная застройка через /setblock | ||
for (let i = 0; i < | for (let i = 0; i < tasksToBuild.length; i++) { | ||
let | let task = tasksToBuild[i]; | ||
Chat.say(`/setblock ${task.targetX} ${task.targetY} ${task.targetZ} ${task.blockToPlace}`); | |||
changed++; | |||
if (changed % blocksPerTick === 0) { | |||
Client.waitTick(1); | |||
} | } | ||
} | } | ||
Текущая версия от 11:33, 31 июля 2026
← Вернуться к разделу «MURS:Строителям»
Специальные скрипты для особых задач — подборка клиентских и серверных автоматизированных сценариев на базе JavaScript для ускорения рутинных процессов строительства в Муроморске и Муроморской области.
Иногда возникают ситуации, когда возможностей стандартизированных инструментов вроде WorldEdit или Axiom не хватает для решения специфических задач. Например:
- Автоматическая высадка сложного случайного распределения растительности и деревьев;
- Масштабное и затяжное терраформирование со сложными масками;
- Проработка интерьеров и освещения в фасадах тысяч окон спальных районов.
В таких случаях на помощь приходят индивидуальные клиентские скрипты, выполняемые на стороне игрока и не требующие установки дополнительных плагинов на сервер.
Быстрый алгоритм настройки
[править код]Для запуска клиентских скриптов используется модификация JsMacros (или аналогичные моды на CraftScripts для WorldEdit).
- Скачайте и установите мод JsMacros в сборку (совместимую с версией 1.19.4).
- Подготовьте или напишите готовый файл скрипта с расширением `.js`.
- Поместите файл в папку сборки:
.minecraft/config/jsMacros/Macros/
- Откройте меню мода в игре (по умолчанию клавиша G или через настройки управления).
- Назначьте вызов нужного скрипта на удобную горячую клавишу (Keybinding) или событие.
- Переместитесь на нужный участок застройки и нажмите горячую клавишу.
Пример: автоматизация расстановки освещения внутри зданий
[править код]В Муроморске насчитываются сотни многоэтажных жилых домов и промышленных зданий с тысячами оконных проёмов. Ручная расстановка блоков света внутри каждого окна занимает десятки часов, а массовое копирование командой //copy + //paste создаёт неестественный повтор рисунка светящихся окон.
Ниже представлен пример скрипта от Tringoli для автоматической случайной расстановки блоков света и темноты за блоками стёкол. Он применяется снаружи здания.
📖 Код скрипта
// =========================================================================
// Умное автоматическое освещение окон (JS Macros 1.19.4)
// =========================================================================
const radius = 15; // Радиус сканирования вокруг игрока
const lightRatio = 0.3; // Шанс горящего окна (30% свет, 70% тьма)
const blocksPerTick = 4; // Ускорение (4 блока за 1 тик = 80 блоков/сек)
// Идентификаторы блоков света и тьмы
const blockLight = "chisel:large_tile/glowstone";
const blockDark = "minecraft:black_concrete";
const player = Player.getPlayer();
// Главный вектор взгляда игрока вглубь здания
function getPlayerInwardVector() {
let yaw = player.getYaw() % 360;
if (yaw < 0) yaw += 360;
if (yaw >= 315 || yaw < 45) return { dx: 0, dz: 1 }; // Взгляд на Юг -> комната на Юге (+Z)
if (yaw >= 45 && yaw < 135) return { dx: -1, dz: 0 }; // Взгляд на Запад -> комната на Западе (-X)
if (yaw >= 135 && yaw < 225) return { dx: 0, dz: -1 }; // Взгляд на Север -> комната на Севере (-Z)
if (yaw >= 225 && yaw < 315) return { dx: 1, dz: 0 }; // Взгляд на Восток -> комната на Востоке (+X)
return { dx: 0, dz: -1 };
}
const mainInward = getPlayerInwardVector();
let yawRad = (player.getYaw() % 360) * Math.PI / 180;
let lookX = -Math.sin(yawRad);
let lookZ = Math.cos(yawRad);
// Проверка: находится ли окно перед игроком
function isInFront(x, z, px, pz) {
let dirX = x - px;
let dirZ = z - pz;
if (dirX === 0 && dirZ === 0) return true;
let dot = dirX * lookX + dirZ * lookZ;
return dot > -1.5;
}
// 🔍 ПРОВЕРКА: является ли блок окном (с фильтром внешних рамок)
function isWindow(id) {
let s = String(id).toLowerCase();
// 🚫 Исключаем внешние парапеты, перила и декоративные планки
if (s.includes("sideway_frame") ||
s.includes("parapet") ||
s.includes("railing") ||
s.includes("balcony") ||
s.includes("mullion") ||
s.includes("window_sill")) {
return false;
}
return s.match(/yuushya:.*(window|glass|diagonal).*/) ||
s.match(/chipped:.*(glass|pane|window).*/) ||
s.match(/mcwwindows:.*(window|glass|pane).*/) ||
s.match(/chisel:.*glass.*/) ||
s.match(/minecraft:.*glass.*/) ||
s.match(/.*:.*(window|stained_glass|glass_pane).*/);
}
// 🚫 ПРОВЕРКА КРАЙНИХ ЧАСТЕЙ (Подоконники и верхние карнизы)
function isEdgePart(id) {
let s = String(id).toLowerCase();
return s.includes("_bottom") ||
s.includes("_top") ||
s.includes("window_bottom") ||
s.includes("window_top") ||
s.includes("sill") ||
s.includes("_lower") ||
s.includes("_upper") ||
s.includes("parapet");
}
// 🧬 УМНОЕ ОПРЕДЕЛЕНИЕ СЕМЕЙСТВА ОКНА (Нормализация для разных модов)
function getWindowFamily(id) {
let s = String(id).toLowerCase();
// 1. Мод Chisel (chisel:light/glass, chisel:shale/glass, chisel:noborder/glass)
if (s.startsWith("chisel:")) {
let colorMatch = s.match(/(white|orange|magenta|light_blue|yellow|lime|pink|gray|light_gray|cyan|purple|blue|brown|green|red|black)_glass/);
if (colorMatch) {
return `chisel:${colorMatch[1]}_glass`;
}
return "chisel:glass";
}
// 2. Мод Macaw's Windows (mcwwindows:spruce_plank_window, mcwwindows:stripped_spruce_log_window)
if (s.startsWith("mcwwindows:")) {
let materials = [
"spruce", "oak", "birch", "dark_oak", "jungle", "acacia",
"mangrove", "cherry", "bamboo", "crimson", "warped", "iron",
"quartz", "stone", "granite", "diorite", "andesite", "brick"
];
for (let i = 0; i < materials.length; i++) {
let mat = materials[i];
if (s.includes(mat)) {
return `mcwwindows:${mat}`;
}
}
s = s.replace(/_(plank|stripped|log|pane|window|tall|gothic|parapet|four|two|single|double)$/g, "");
return s;
}
// 3. Мод Yuushya (yuushya:white_windows_0/1/2, classical_white_window_top)
if (s.startsWith("yuushya:")) {
s = s.replace(/_(\d+|bottom|middle|top|upper|lower|left|right|center|corner|diagonal|part\d*|sideway_frame)$/g, "");
s = s.replace(/_(\d+|bottom|middle|top|upper|lower|left|right|center|corner|diagonal|part\d*|sideway_frame)$/g, "");
return s;
}
// 4. Ванилла и Chipped
s = s.replace(/_(\d+|bottom|middle|top|upper|lower|left|right|center|corner|diagonal|part\d*)$/g, "");
s = s.replace(/_(\d+|bottom|middle|top|upper|lower|left|right|center|corner|diagonal|part\d*)$/g, "");
return s;
}
// 📐 СТРОГОЕ ВЕКТОРНОЕ ОПРЕДЕЛЕНИЕ ТОЧКИ ИНТЕРЬЕРА
function getExactInteriorTarget(x, y, z, px, pz) {
let block = World.getBlock(x, y, z);
let blockFacing = null;
try {
let state = block.getBlockState();
if (state) {
try {
let f = state.get("facing");
if (f) blockFacing = String(f).toLowerCase();
} catch(e) {}
if (!blockFacing) {
let str = String(state);
let match = str.match(/facing[=:]\s*([a-z_]+)/i);
if (match) blockFacing = match[1].toLowerCase();
}
}
} catch(e) {}
let dx = 0, dz = 0;
if (blockFacing) {
if (blockFacing.includes("south")) dz = -1;
else if (blockFacing.includes("north")) dz = 1;
else if (blockFacing.includes("east")) dx = -1;
else if (blockFacing.includes("west")) dx = 1;
}
if (dx === 0 && dz === 0) {
dx = mainInward.dx;
dz = mainInward.dz;
}
let winDistSq = (x - px) * (x - px) + (z - pz) * (z - pz);
let targetDistSq = ((x + dx) - px) * ((x + dx) - px) + ((z + dz) - pz) * ((z + dz) - pz);
if (targetDistSq < winDistSq) {
dx = -dx;
dz = -dz;
}
return { x: x + dx, y: y, z: z + dz };
}
// 🛡 ЛОКАЛЬНАЯ ПРОВЕРКА СУЩЕСТВУЮЩИХ БЛОКОВ
function getLocalExistingBlock(targetX, targetY, targetZ) {
let offsets = [
{x: 0, y: 1, z: 0}, {x: 0, y: -1, z: 0},
{x: 1, y: 0, z: 0}, {x: -1, y: 0, z: 0},
{x: 0, y: 0, z: 1}, {x: 0, y: 0, z: -1}
];
for (let i = 0; i < offsets.length; i++) {
let o = offsets[i];
let bId = World.getBlock(targetX + o.x, targetY + o.y, targetZ + o.z).getId();
if (bId === blockLight || bId === blockDark) {
return bId;
}
}
return null;
}
let px = Math.floor(player.getX());
let py = Math.floor(player.getY());
let pz = Math.floor(player.getZ());
let visited = new Set();
let tasksToBuild = [];
function getPosKey(x, y, z) {
return `${x},${y},${z}`;
}
Chat.log(`§e[Урбанист] §fСканирую фасад (Нормализация модов Macaw's / Chisel / Yuushya: §aАКТИВНА§f)...`);
// 1. Поиск и группировка окон (Flood Fill)
for (let x = px - radius; x <= px + radius; x++) {
for (let y = py - radius; y <= py + radius; y++) {
for (let z = pz - radius; z <= pz + radius; z++) {
let key = getPosKey(x, y, z);
if (visited.has(key)) continue;
let blockId = World.getBlock(x, y, z).getId();
if (isWindow(blockId)) {
if (!isInFront(x, z, px, pz)) {
visited.add(key);
continue;
}
let startFamily = getWindowFamily(blockId);
let stack = [{x: x, y: y, z: z}];
visited.add(key);
let groupTargets = [];
while (stack.length > 0) {
let curr = stack.pop();
let currId = World.getBlock(curr.x, curr.y, curr.z).getId();
if (!isEdgePart(currId)) {
let target = getExactInteriorTarget(curr.x, curr.y, curr.z, px, pz);
let behindId = World.getBlock(target.x, target.y, target.z).getId();
if (behindId === "minecraft:air" || behindId === "minecraft:cave_air") {
groupTargets.push({ targetX: target.x, targetY: target.y, targetZ: target.z });
}
}
let neighbors = [
{x: curr.x + 1, y: curr.y, z: curr.z},
{x: curr.x - 1, y: curr.y, z: curr.z},
{x: curr.x, y: curr.y + 1, z: curr.z},
{x: curr.x, y: curr.y - 1, z: curr.z},
{x: curr.x, y: curr.y, z: curr.z + 1},
{x: curr.x, y: curr.y, z: curr.z - 1}
];
for (let i = 0; i < neighbors.length; i++) {
let n = neighbors[i];
let nKey = getPosKey(n.x, n.y, n.z);
if (!visited.has(nKey)) {
if (Math.abs(n.x - px) <= radius && Math.abs(n.y - py) <= radius && Math.abs(n.z - pz) <= radius) {
let nId = World.getBlock(n.x, n.y, n.z).getId();
// 🌟 Собираем соседние блоки, если они из одного семейства мода
if (isWindow(nId) && getWindowFamily(nId) === startFamily) {
visited.add(nKey);
stack.push(n);
}
}
}
}
}
if (groupTargets.length > 0) {
let defaultGroupBlock = (Math.random() <= lightRatio) ? blockLight : blockDark;
for (let k = 0; k < groupTargets.length; k++) {
let t = groupTargets[k];
let localExist = getLocalExistingBlock(t.targetX, t.targetY, t.targetZ);
tasksToBuild.push({
targetX: t.targetX,
targetY: t.targetY,
targetZ: t.targetZ,
blockToPlace: localExist ? localExist : defaultGroupBlock
});
}
}
}
}
}
}
Chat.log(`§e[Урбанист] §fСформировано корректных окон: §a${tasksToBuild.length}§f. Начинаю стройку!`);
let changed = 0;
// 2. Ускоренная застройка через /setblock
for (let i = 0; i < tasksToBuild.length; i++) {
let task = tasksToBuild[i];
Chat.say(`/setblock ${task.targetX} ${task.targetY} ${task.targetZ} ${task.blockToPlace}`);
changed++;
if (changed % blocksPerTick === 0) {
Client.waitTick(1);
}
}
Chat.log(`§e[Урбанист] §fУспешно застроено блоков за стёклами: §a${changed}`);|
Полезный совет
Вы можете варьировать параметры в коде скрипта: например, устанавливать значение |
Преимущества метода
[править код]- Высокая скорость. Обработка фасада крупного 16-этажного дома занимает считанные секунды.
- Уникальность. Генератор случайных чисел исключает повторяющийся паттерн «включённых» окон.
- Автономность. Метод работает через клиентский мод и не требует прав администратора сервера на установку плагинов.
- Гибкость. По аналогичной схеме можно заменять блоки, расставлять мелкий декор или автоматизировать маски для WorldEdit.