import { world, system, BlockPermutation } from "@minecraft/server";
/* =========================
📦 STORAGE
========================= */
const pos1 = new Map();
const pos2 = new Map();
const undoStack = new Map();
const redoStack = new Map();
const clipboard = new Map();
const particleToggle = new Map(); // ⭐ pa ON/OFF
const AXE = "minecraft:wooden_axe";
const COMPASS = "minecraft:compass";
/* =========================
🪓 START ITEMS
========================= */
function giveStartItems(p) {
system.run(() => {
p.runCommand("give @s wooden_axe 1");
p.runCommand("give @s compass 1");
});
}
world.afterEvents.playerSpawn.subscribe(ev => {
const p = ev.player;
system.run(() => {
giveStartItems(p);
particleToggle.set(p.id, true); // ⭐ 여기 추가
});
});
/* ============================
🪓 POS SYSTEM
========================= */
world.beforeEvents.playerBreakBlock.subscribe(ev => {
const p = ev.player;
const item = p.getComponent("inventory")?.container
.getItem(p.selectedSlotIndex);
if (!item || item.typeId !== AXE) return;
ev.cancel = true;
const loc = ev.block.location;
if (p.isSneaking) {
pos2.set(p.id, loc);
p.sendMessage(`§bPos2 §f${loc.x} ${loc.y} ${loc.z}`);
} else {
pos1.set(p.id, loc);
p.sendMessage(`§aPos1 §f${loc.x} ${loc.y} ${loc.z}`);
}
drawBox(p);
});
/* =========================
✨ PARTICLE BOX (OUTLINE ONLY)
========================= */
function drawBox(p) {
if (!particleToggle.get(p.id)) return;
const a = pos1.get(p.id);
const b = pos2.get(p.id);
if (!a || !b) return;
const dim = p.dimension;
const minX = Math.min(a.x, b.x);
const maxX = Math.max(a.x, b.x);
const minY = Math.min(a.y, b.y); // ✅ FIX
const maxY = Math.max(a.y, b.y);
const minZ = Math.min(a.z, b.z);
const maxZ = Math.max(a.z, b.z);
system.run(() => {
const spawn = (x, y, z, blockId) => {
const colorMap = {
"minecraft:stone": { r: 0.5, g: 0.5, b: 0.5 },
"minecraft:grass_block": { r: 0.3, g: 1, b: 0.3 },
"minecraft:dirt": { r: 0.5, g: 0.3, b: 0.1 },
"minecraft:water": { r: 0.2, g: 0.4, b: 1 }
};
const c = colorMap[blockId] || { r: 1, g: 0.6, b: 0.2 };
// 🔥 flame (basic_flame)
dim.spawnParticle("minecraft:basic_flame_particle", {
x: x + 0.5,
y: y + 0.5,
z: z + 0.5
});
// ✨ outline 느낌
dim.spawnParticle("minecraft:endrod", {
x: x + 0.5,
y: y + 0.5,
z: z + 0.5
});
};
// bottom / top
for (let x = minX; x <= maxX; x++) {
for (let z = minZ; z <= maxZ; z++) {
const b1 = dim.getBlock({ x, y: minY, z });
const b2 = dim.getBlock({ x, y: maxY, z });
spawn(x, minY, z, b1?.typeId);
spawn(x, maxY, z, b2?.typeId);
}
}
// vertical edges
for (let y = minY; y <= maxY; y++) {
for (let x of [minX, maxX]) {
for (let z of [minZ, maxZ]) {
const b = dim.getBlock({ x, y, z });
spawn(x, y, z, b?.typeId);
}
}
}
});
}
/*=========================*/
system.runInterval(() => {
for (const p of world.getAllPlayers()) {
drawBox(p);
}
}, 2); // 2틱마다 갱신 (성능 + 부드러움 밸런스)
/* =========================
🧱 SET
========================= */
function setBlocks(p, blockId) {
const a = pos1.get(p.id);
const b = pos2.get(p.id);
if (!a || !b) return;
const dim = p.dimension;
const type = blockId.startsWith("minecraft:") ? blockId : "minecraft:" + blockId;
let changes = [];
system.run(() => {
for (let x = Math.min(a.x, b.x); x <= Math.max(a.x, b.x); x++)
for (let y = Math.min(a.y, b.y); y <= Math.max(a.y, b.y); y++)
for (let z = Math.min(a.z, b.z); z <= Math.max(a.z, b.z); z++) {
const pos = { x, y, z };
const block = dim.getBlock(pos);
if (!block) continue;
changes.push({
pos,
old: block.typeId,
new: type
});
block.setPermutation(BlockPermutation.resolve(type));
}
pushUndo(p, changes);
});
}
/* =========================
🔁 REPLACE
========================= */
function replaceBlocks(p, from, to) {
const a = pos1.get(p.id);
const b = pos2.get(p.id);
if (!a || !b) return;
const dim = p.dimension;
from = from.startsWith("minecraft:") ? from : "minecraft:" + from;
to = to.startsWith("minecraft:") ? to : "minecraft:" + to;
let changes = [];
system.run(() => {
for (let x = Math.min(a.x, b.x); x <= Math.max(a.x, b.x); x++)
for (let y = Math.min(a.y, b.y); y <= Math.max(a.y, b.y); y++)
for (let z = Math.min(a.z, b.z); z <= Math.max(a.z, b.z); z++) {
const pos = { x, y, z };
const block = dim.getBlock(pos);
if (!block) continue;
if (block.typeId !== from) continue;
changes.push({
pos,
old: block.typeId,
new: to
});
block.setPermutation(BlockPermutation.resolve(to));
}
pushUndo(p, changes);
});
}
/* =========================
📋 COPY / PASTE
========================= */
function copy(p) {
const a = pos1.get(p.id);
const b = pos2.get(p.id);
if (!a || !b) return;
const dim = p.dimension;
let data = [];
for (let x = Math.min(a.x, b.x); x <= Math.max(a.x, b.x); x++)
for (let y = Math.min(a.y, b.y); y <= Math.max(a.y, b.y); y++)
for (let z = Math.min(a.z, b.z); z <= Math.max(a.z, b.z); z++) {
const block = dim.getBlock({ x, y, z });
if (!block) continue;
data.push({
dx: x - a.x,
dy: y - a.y,
dz: z - a.z,
type: block.typeId
});
}
clipboard.set(p.id, data);
}
function paste(p) {
const data = clipboard.get(p.id);
if (!data) return;
const dim = p.dimension;
const base = p.location;
let changes = [];
system.run(() => {
for (const b of data) {
const pos = {
x: Math.floor(base.x + b.dx),
y: Math.floor(base.y + b.dy),
z: Math.floor(base.z + b.dz)
};
const block = dim.getBlock(pos);
if (!block) continue;
changes.push({
pos,
old: block.typeId,
new: b.type
});
block.setPermutation(BlockPermutation.resolve(b.type));
}
pushUndo(p, changes);
});
}
/* =========================
🔄 ROTATE
========================= */
function rotateSelection(p, angle = 90) {
const a = pos1.get(p.id);
const b = pos2.get(p.id);
if (!a || !b) return;
const dim = p.dimension;
const minX = Math.min(a.x, b.x);
const minY = Math.min(a.y, b.y);
const minZ = Math.min(a.z, b.z);
const maxX = Math.max(a.x, b.x);
const maxY = Math.max(a.y, b.y);
const maxZ = Math.max(a.z, b.z);
let blocks = [];
for (let x = minX; x <= maxX; x++)
for (let y = minY; y <= maxY; y++)
for (let z = minZ; z <= maxZ; z++) {
const b = dim.getBlock({ x, y, z });
if (!b) continue;
blocks.push({
x: x - minX,
y: y - minY,
z: z - minZ,
type: b.typeId
});
}
const times = angle === 90 ? 1 : angle === 180 ? 2 : 3;
for (let i = 0; i < times; i++) {
blocks = blocks.map(b => ({
x: b.z,
y: b.y,
z: -b.x,
type: b.type
}));
}
system.run(() => {
for (const b of blocks) {
const pos = {
x: minX + b.x,
y: minY + b.y,
z: minZ + b.z
};
const block = dim.getBlock(pos);
if (block) block.setPermutation(BlockPermutation.resolve(b.type));
}
});
}
/* =========================
🔁 UNDO / REDO
========================= */
function pushUndo(p, changes) {
const u = undoStack.get(p.id) || [];
u.push(JSON.parse(JSON.stringify(changes)));
undoStack.set(p.id, u);
redoStack.set(p.id, []);
}
function undo(p) {
const stack = undoStack.get(p.id);
if (!stack?.length) return;
const changes = stack.pop();
undoStack.set(p.id, stack);
system.run(() => {
for (const c of changes) {
const b = p.dimension.getBlock(c.pos);
if (b) b.setPermutation(BlockPermutation.resolve(c.old));
}
});
const r = redoStack.get(p.id) || [];
r.push(JSON.parse(JSON.stringify(changes)));
redoStack.set(p.id, r);
}
function redo(p) {
const stack = redoStack.get(p.id);
if (!stack?.length) return;
const changes = stack.pop();
redoStack.set(p.id, stack);
system.run(() => {
for (const c of changes) {
const b = p.dimension.getBlock(c.pos);
if (b) b.setPermutation(BlockPermutation.resolve(c.new));
}
});
const u = undoStack.get(p.id) || [];
u.push(JSON.parse(JSON.stringify(changes)));
undoStack.set(p.id, u);
}
/* =========================
🧭 COMPASS TP (FIXED)
========================= */
world.beforeEvents.itemUse.subscribe(ev => {
const p = ev.source;
const item = ev.itemStack;
if (!item || item.typeId !== "minecraft:compass") return;
ev.cancel = true;
system.run(() => {
const dim = p.dimension;
const start = p.getHeadLocation ? p.getHeadLocation() : p.location;
const dir = p.getViewDirection();
let hit = null;
for (let d = 0; d < 128; d += 0.25) {
const pos = {
x: Math.floor(start.x + dir.x * d),
y: Math.floor(start.y + dir.y * d),
z: Math.floor(start.z + dir.z * d)
};
const block = dim.getBlock(pos);
if (block && block.typeId !== "minecraft:air") {
hit = pos;
break;
}
}
if (!hit) return p.sendMessage("§c앞에 블록 없음!");
p.teleport(
{ x: hit.x + 0.5, y: hit.y + 1, z: hit.z + 0.5 },
{ dimension: dim }
);
});
});
/* =========================
💬 COMMANDS
========================= */
world.beforeEvents.chatSend.subscribe(ev => {
const p = ev.sender;
const msg = ev.message;
ev.cancel = true;
if (msg === ";undo") undo(p);
if (msg === ";redo") redo(p);
if (msg === ";copy") copy(p);
if (msg === ";paste") paste(p);
if (msg.startsWith(";set ")) setBlocks(p, msg.split(" ")[1]);
if (msg.startsWith(";replace ")) {
const a = msg.split(" ");
replaceBlocks(p, a[1], a[2]);
}
if (msg.startsWith(";rotate ")) {
rotateSelection(p, parseInt(msg.split(" ")[1]) || 90);
}
if (msg === ";pa") {
const v = particleToggle.get(p.id) ?? true;
particleToggle.set(p.id, !v);
p.sendMessage("§aParticle: " + (!v ? "ON" : "OFF"));
}
if (msg === ";help") {
p.sendMessage(`§6===== PRO WE =====
;set ;replace
;copy ;paste
;undo ;redo
;rotate
;pa (toggle particle)
==================`);
}
});
To embed this project on your website, copy the following code and paste it into your website's HTML: