import { world, system } from "@minecraft/server"; // ─── Configuration ──────────────────────────────────────────────────────────── const SCAN_INTERVAL_TICKS = 6000; // How often to scan for villages (5 min) const CLUSTER_RADIUS = 48; // Max distance (blocks) between villagers in same village const MIN_VILLAGERS_TO_TRIGGER = 3; // Minimum villagers before any expansion happens const BUILD_QUEUE_RATE = 20; // Max blocks placed per tick during construction const MAX_PLOT_SEARCH_RADIUS = 80; // How far to search for a valid building plot const PLOT_CLEAR_SIZE = 7; // Square size to check for obstructions const DYNAMIC_PROP_KEY = "village_state"; // Key for world dynamic property persistence const DYNAMIC_PROP_MAX = 32000; // Max chars for a single dynamic property value // ─── Building Templates ─────────────────────────────────────────────────────── // Each template is an array of [dx, dy, dz, blockId, blockStates?] // dx/dz are relative to plot origin (northwest corner at ground level), dy is height offset const TEMPLATES = { LAMP_POST: { name: "lamp post", footprint: 1, blocks: [ [0, 0, 0, "oak_fence"], [0, 1, 0, "oak_fence"], [0, 2, 0, "lantern", '["hanging":false]'], ], }, WELL: { name: "well", footprint: 3, blocks: [ // Base ring of cobblestone [0, 0, 0, "cobblestone"], [1, 0, 0, "cobblestone"], [2, 0, 0, "cobblestone"], [0, 0, 1, "cobblestone"], [2, 0, 1, "cobblestone"], [0, 0, 2, "cobblestone"], [1, 0, 2, "cobblestone"], [2, 0, 2, "cobblestone"], // Water in the center [1, 0, 1, "water"], // Raised walls (level 1) [0, 1, 0, "cobblestone_wall"], [1, 1, 0, "cobblestone_wall"], [2, 1, 0, "cobblestone_wall"], [0, 1, 1, "cobblestone_wall"], [2, 1, 1, "cobblestone_wall"], [0, 1, 2, "cobblestone_wall"], [1, 1, 2, "cobblestone_wall"], [2, 1, 2, "cobblestone_wall"], // Roof supports [0, 2, 0, "oak_fence"], [2, 2, 0, "oak_fence"], [0, 2, 2, "oak_fence"], [2, 2, 2, "oak_fence"], // Roof ridge [0, 3, 0, "oak_slab"], [1, 3, 0, "oak_slab"], [2, 3, 0, "oak_slab"], [0, 3, 1, "oak_slab"], [1, 3, 1, "oak_slab"], [2, 3, 1, "oak_slab"], ], }, SMALL_HOUSE: { name: "small house", footprint: 7, blocks: [ // Floor [0,0,0,"oak_planks"],[1,0,0,"oak_planks"],[2,0,0,"oak_planks"],[3,0,0,"oak_planks"],[4,0,0,"oak_planks"], [0,0,1,"oak_planks"],[1,0,1,"oak_planks"],[2,0,1,"oak_planks"],[3,0,1,"oak_planks"],[4,0,1,"oak_planks"], [0,0,2,"oak_planks"],[1,0,2,"oak_planks"],[2,0,2,"oak_planks"],[3,0,2,"oak_planks"],[4,0,2,"oak_planks"], [0,0,3,"oak_planks"],[1,0,3,"oak_planks"],[2,0,3,"oak_planks"],[3,0,3,"oak_planks"],[4,0,3,"oak_planks"], [0,0,4,"oak_planks"],[1,0,4,"oak_planks"],[2,0,4,"oak_planks"],[3,0,4,"oak_planks"],[4,0,4,"oak_planks"], // Walls - south face (z=0) [0,1,0,"oak_log"],[1,1,0,"oak_planks"],[2,1,0,"oak_planks"],[3,1,0,"oak_planks"],[4,1,0,"oak_log"], [0,2,0,"oak_log"],[1,2,0,"glass_pane"],[2,2,0,"oak_planks"],[3,2,0,"glass_pane"],[4,2,0,"oak_log"], [0,3,0,"oak_log"],[1,3,0,"oak_planks"],[2,3,0,"oak_planks"],[3,3,0,"oak_planks"],[4,3,0,"oak_log"], // Walls - north face (z=4) [0,1,4,"oak_log"],[1,1,4,"oak_planks"],[2,1,4,"oak_planks"],[3,1,4,"oak_planks"],[4,1,4,"oak_log"], [0,2,4,"oak_log"],[1,2,4,"glass_pane"],[2,2,4,"oak_planks"],[3,2,4,"glass_pane"],[4,2,4,"oak_log"], [0,3,4,"oak_log"],[1,3,4,"oak_planks"],[2,3,4,"oak_planks"],[3,3,4,"oak_planks"],[4,3,4,"oak_log"], // Walls - west face (x=0) [0,1,1,"oak_planks"],[0,1,2,"oak_planks"],[0,1,3,"oak_planks"], [0,2,1,"oak_planks"],[0,2,2,"glass_pane"],[0,2,3,"oak_planks"], [0,3,1,"oak_planks"],[0,3,2,"oak_planks"],[0,3,3,"oak_planks"], // Walls - east face (x=4) with door gap at y=1,2 z=2 [4,1,1,"oak_planks"],[4,1,3,"oak_planks"], [4,2,1,"oak_planks"],[4,2,3,"oak_planks"], [4,3,1,"oak_planks"],[4,3,2,"oak_planks"],[4,3,3,"oak_planks"], // Door (east face, z=2) - oak door bottom then top [4,1,2,"oak_door",'["door_hinge_bit":false,"open_bit":false,"upper_block_bit":false,"direction":0]'], [4,2,2,"oak_door",'["door_hinge_bit":false,"open_bit":false,"upper_block_bit":true,"direction":0]'], // Roof (spruce slabs) [0,4,0,"spruce_slab"],[1,4,0,"spruce_slab"],[2,4,0,"spruce_slab"],[3,4,0,"spruce_slab"],[4,4,0,"spruce_slab"], [0,4,1,"spruce_slab"],[1,4,1,"spruce_slab"],[2,4,1,"spruce_slab"],[3,4,1,"spruce_slab"],[4,4,1,"spruce_slab"], [0,4,2,"spruce_slab"],[1,4,2,"spruce_slab"],[2,4,2,"spruce_slab"],[3,4,2,"spruce_slab"],[4,4,2,"spruce_slab"], [0,4,3,"spruce_slab"],[1,4,3,"spruce_slab"],[2,4,3,"spruce_slab"],[3,4,3,"spruce_slab"],[4,4,3,"spruce_slab"], [0,4,4,"spruce_slab"],[1,4,4,"spruce_slab"],[2,4,4,"spruce_slab"],[3,4,4,"spruce_slab"],[4,4,4,"spruce_slab"], // Interior torch [2,2,2,"torch"], ], }, FARM_PLOT: { name: "farm", footprint: 9, blocks: [ // Farmland rows (8x1 strips with water channel in middle) // Water channel at x=4 [4,0,0,"water"],[4,0,1,"water"],[4,0,2,"water"],[4,0,3,"water"], [4,0,4,"water"],[4,0,5,"water"],[4,0,6,"water"],[4,0,7,"water"], // Farmland west of channel [0,0,0,"farmland"],[1,0,0,"farmland"],[2,0,0,"farmland"],[3,0,0,"farmland"], [0,0,1,"farmland"],[1,0,1,"farmland"],[2,0,1,"farmland"],[3,0,1,"farmland"], [0,0,2,"farmland"],[1,0,2,"farmland"],[2,0,2,"farmland"],[3,0,2,"farmland"], [0,0,3,"farmland"],[1,0,3,"farmland"],[2,0,3,"farmland"],[3,0,3,"farmland"], [0,0,4,"farmland"],[1,0,4,"farmland"],[2,0,4,"farmland"],[3,0,4,"farmland"], [0,0,5,"farmland"],[1,0,5,"farmland"],[2,0,5,"farmland"],[3,0,5,"farmland"], [0,0,6,"farmland"],[1,0,6,"farmland"],[2,0,6,"farmland"],[3,0,6,"farmland"], [0,0,7,"farmland"],[1,0,7,"farmland"],[2,0,7,"farmland"],[3,0,7,"farmland"], // Farmland east of channel [5,0,0,"farmland"],[6,0,0,"farmland"],[7,0,0,"farmland"],[8,0,0,"farmland"], [5,0,1,"farmland"],[6,0,1,"farmland"],[7,0,1,"farmland"],[8,0,1,"farmland"], [5,0,2,"farmland"],[6,0,2,"farmland"],[7,0,2,"farmland"],[8,0,2,"farmland"], [5,0,3,"farmland"],[6,0,3,"farmland"],[7,0,3,"farmland"],[8,0,3,"farmland"], [5,0,4,"farmland"],[6,0,4,"farmland"],[7,0,4,"farmland"],[8,0,4,"farmland"], [5,0,5,"farmland"],[6,0,5,"farmland"],[7,0,5,"farmland"],[8,0,5,"farmland"], [5,0,6,"farmland"],[6,0,6,"farmland"],[7,0,6,"farmland"],[8,0,6,"farmland"], [5,0,7,"farmland"],[6,0,7,"farmland"],[7,0,7,"farmland"],[8,0,7,"farmland"], // Crops on farmland [0,1,0,"wheat"],[2,1,0,"wheat"],[6,1,0,"carrots"],[8,1,0,"potatoes"], [0,1,2,"wheat"],[2,1,2,"wheat"],[6,1,2,"carrots"],[8,1,2,"potatoes"], [0,1,4,"wheat"],[2,1,4,"wheat"],[6,1,4,"carrots"],[8,1,4,"potatoes"], [0,1,6,"wheat"],[2,1,6,"wheat"],[6,1,6,"carrots"],[8,1,6,"potatoes"], // Fence border [0,-1,0,"oak_fence"],[1,-1,0,"oak_fence"],[2,-1,0,"oak_fence"],[3,-1,0,"oak_fence"], [4,-1,0,"oak_fence"],[5,-1,0,"oak_fence"],[6,-1,0,"oak_fence"],[7,-1,0,"oak_fence"],[8,-1,0,"oak_fence"], [0,-1,7,"oak_fence"],[1,-1,7,"oak_fence"],[2,-1,7,"oak_fence"],[3,-1,7,"oak_fence"], [4,-1,7,"oak_fence"],[5,-1,7,"oak_fence"],[6,-1,7,"oak_fence"],[7,-1,7,"oak_fence"],[8,-1,7,"oak_fence"], [0,-1,1,"oak_fence"],[0,-1,2,"oak_fence"],[0,-1,3,"oak_fence"],[0,-1,4,"oak_fence"],[0,-1,5,"oak_fence"],[0,-1,6,"oak_fence"], [8,-1,1,"oak_fence"],[8,-1,2,"oak_fence"],[8,-1,3,"oak_fence"],[8,-1,4,"oak_fence"],[8,-1,5,"oak_fence"],[8,-1,6,"oak_fence"], // Gate on south side [4,-1,0,"oak_fence_gate",'["direction":0,"open_bit":false,"in_wall_bit":false]'], ], }, BLACKSMITH: { name: "blacksmith", footprint: 9, blocks: [ // Foundation [0,0,0,"cobblestone"],[1,0,0,"cobblestone"],[2,0,0,"cobblestone"],[3,0,0,"cobblestone"],[4,0,0,"cobblestone"],[5,0,0,"cobblestone"],[6,0,0,"cobblestone"], [0,0,1,"cobblestone"],[1,0,1,"stone_bricks"],[2,0,1,"stone_bricks"],[3,0,1,"stone_bricks"],[4,0,1,"stone_bricks"],[5,0,1,"stone_bricks"],[6,0,1,"cobblestone"], [0,0,2,"cobblestone"],[1,0,2,"stone_bricks"],[2,0,2,"stone_bricks"],[3,0,2,"stone_bricks"],[4,0,2,"stone_bricks"],[5,0,2,"stone_bricks"],[6,0,2,"cobblestone"], [0,0,3,"cobblestone"],[1,0,3,"stone_bricks"],[2,0,3,"stone_bricks"],[3,0,3,"stone_bricks"],[4,0,3,"stone_bricks"],[5,0,3,"stone_bricks"],[6,0,3,"cobblestone"], [0,0,4,"cobblestone"],[1,0,4,"cobblestone"],[2,0,4,"cobblestone"],[3,0,4,"cobblestone"],[4,0,4,"cobblestone"],[5,0,4,"cobblestone"],[6,0,4,"cobblestone"], // Walls level 1 [0,1,0,"stone_brick_wall"],[6,1,0,"stone_brick_wall"], [0,1,1,"stone_brick_wall"],[6,1,1,"stone_brick_wall"], [0,1,2,"stone_brick_wall"],[6,1,2,"stone_brick_wall"], [0,1,3,"stone_brick_wall"],[6,1,3,"stone_brick_wall"], [0,1,4,"stone_brick_wall"],[1,1,4,"stone_brick_wall"],[2,1,4,"stone_brick_wall"],[3,1,4,"stone_brick_wall"],[4,1,4,"stone_brick_wall"],[5,1,4,"stone_brick_wall"],[6,1,4,"stone_brick_wall"], // Back wall with window [0,1,0,"stone_bricks"],[1,1,0,"stone_bricks"],[2,1,0,"stone_bricks"],[3,1,0,"stone_bricks"],[4,1,0,"stone_bricks"],[5,1,0,"stone_bricks"],[6,1,0,"stone_bricks"], [0,2,0,"stone_bricks"],[1,2,0,"stone_bricks"],[2,2,0,"glass_pane"],[3,2,0,"glass_pane"],[4,2,0,"glass_pane"],[5,2,0,"stone_bricks"],[6,2,0,"stone_bricks"], [0,3,0,"stone_bricks"],[1,3,0,"stone_bricks"],[2,3,0,"stone_bricks"],[3,3,0,"stone_bricks"],[4,3,0,"stone_bricks"],[5,3,0,"stone_bricks"],[6,3,0,"stone_bricks"], // Side walls [0,2,1,"stone_bricks"],[0,2,2,"stone_bricks"],[0,2,3,"stone_bricks"], [0,3,1,"stone_bricks"],[0,3,2,"stone_bricks"],[0,3,3,"stone_bricks"], [6,2,1,"stone_bricks"],[6,2,2,"stone_bricks"],[6,2,3,"stone_bricks"], [6,3,1,"stone_bricks"],[6,3,2,"stone_bricks"],[6,3,3,"stone_bricks"], // Front open face (partial) - pillars only [0,2,4,"stone_bricks"],[0,3,4,"stone_bricks"], [6,2,4,"stone_bricks"],[6,3,4,"stone_bricks"], // Roof slabs [0,4,0,"stone_brick_slab"],[1,4,0,"stone_brick_slab"],[2,4,0,"stone_brick_slab"],[3,4,0,"stone_brick_slab"],[4,4,0,"stone_brick_slab"],[5,4,0,"stone_brick_slab"],[6,4,0,"stone_brick_slab"], [0,4,1,"stone_brick_slab"],[1,4,1,"stone_brick_slab"],[2,4,1,"stone_brick_slab"],[3,4,1,"stone_brick_slab"],[4,4,1,"stone_brick_slab"],[5,4,1,"stone_brick_slab"],[6,4,1,"stone_brick_slab"], [0,4,2,"stone_brick_slab"],[1,4,2,"stone_brick_slab"],[2,4,2,"stone_brick_slab"],[3,4,2,"stone_brick_slab"],[4,4,2,"stone_brick_slab"],[5,4,2,"stone_brick_slab"],[6,4,2,"stone_brick_slab"], [0,4,3,"stone_brick_slab"],[1,4,3,"stone_brick_slab"],[2,4,3,"stone_brick_slab"],[3,4,3,"stone_brick_slab"],[4,4,3,"stone_brick_slab"],[5,4,3,"stone_brick_slab"],[6,4,3,"stone_brick_slab"], // Interior furnace and crafting table [1,1,1,"furnace",'["facing_direction":3]'], [2,1,1,"furnace",'["facing_direction":3]'], [4,1,1,"crafting_table"], [5,1,1,"chest",'["facing_direction":3]'], // Lava source for forge effect (sunken into floor, covered by grate effect) [2,0,2,"lava"], // Lava light [3,1,1,"torch"], ], }, }; // ─── Tier → building sequence ───────────────────────────────────────────────── // Each tier entry: what to build when village reaches that population const TIER_BUILDS = [ { minPop: 3, tier: 1, template: "LAMP_POST", label: "a lamp post" }, { minPop: 3, tier: 1, template: "WELL", label: "a well" }, { minPop: 6, tier: 2, template: "SMALL_HOUSE", label: "a house" }, { minPop: 8, tier: 2, template: "SMALL_HOUSE", label: "another house" }, { minPop: 10, tier: 3, template: "FARM_PLOT", label: "a farm" }, { minPop: 12, tier: 3, template: "SMALL_HOUSE", label: "a third house" }, { minPop: 15, tier: 4, template: "BLACKSMITH", label: "a blacksmith" }, ]; // ─── State Management ───────────────────────────────────────────────────────── /** @type {{ [villageId: string]: { center: {x:number,y:number,z:number}, buildIndex: number, builtPlots: [number,number][], lastScanTick: number } }} */ let villageRegistry = {}; function loadState() { try { const raw = world.getDynamicProperty(DYNAMIC_PROP_KEY); if (raw && typeof raw === "string") { villageRegistry = JSON.parse(raw); } } catch (e) { world.sendMessage("§c[Village Evolution] Failed to load state: " + e.message); villageRegistry = {}; } } function saveState() { try { const serialized = JSON.stringify(villageRegistry); if (serialized.length > DYNAMIC_PROP_MAX) { // Trim oldest built plots to stay under limit for (const id of Object.keys(villageRegistry)) { if (villageRegistry[id].builtPlots.length > 5) { villageRegistry[id].builtPlots = villageRegistry[id].builtPlots.slice(-5); } } } world.setDynamicProperty(DYNAMIC_PROP_KEY, JSON.stringify(villageRegistry)); } catch (e) { world.sendMessage("§c[Village Evolution] Failed to save state: " + e.message); } } // ─── Village Detection ──────────────────────────────────────────────────────── function dist2D(a, b) { const dx = a.x - b.x; const dz = a.z - b.z; return Math.sqrt(dx * dx + dz * dz); } /** * Cluster villager locations into groups using simple single-linkage clustering. * Returns array of clusters, each cluster is array of locations. */ function clusterVillagers(locations) { const clusters = []; const assigned = new Array(locations.length).fill(false); for (let i = 0; i < locations.length; i++) { if (assigned[i]) continue; const cluster = [locations[i]]; assigned[i] = true; // Grow cluster by proximity let changed = true; while (changed) { changed = false; for (let j = 0; j < locations.length; j++) { if (assigned[j]) continue; for (const member of cluster) { if (dist2D(locations[j], member) <= CLUSTER_RADIUS) { cluster.push(locations[j]); assigned[j] = true; changed = true; break; } } } } clusters.push(cluster); } return clusters; } /** * Compute centroid of a cluster and snap to a stable ID grid (32-block grid). */ function clusterToVillage(cluster) { const cx = cluster.reduce((s, v) => s + v.x, 0) / cluster.length; const cy = cluster.reduce((s, v) => s + v.y, 0) / cluster.length; const cz = cluster.reduce((s, v) => s + v.z, 0) / cluster.length; // Snap to 32-block grid for stable ID const gridX = Math.round(cx / 32) * 32; const gridZ = Math.round(cz / 32) * 32; const id = `${gridX}_${gridZ}`; return { id, center: { x: Math.floor(cx), y: Math.floor(cy), z: Math.floor(cz) }, population: cluster.length, }; } // ─── Plot Finding ───────────────────────────────────────────────────────────── const SAFE_GROUND_BLOCKS = new Set([ "minecraft:grass_block", "minecraft:dirt", "minecraft:coarse_dirt", "minecraft:podzol", "minecraft:grass_path", "minecraft:farmland", "minecraft:sand", "minecraft:gravel", "minecraft:stone", "minecraft:cobblestone", "minecraft:oak_planks", "minecraft:spruce_planks", ]); const UNSAFE_BLOCKS = new Set([ "minecraft:water", "minecraft:flowing_water", "minecraft:lava", "minecraft:flowing_lava", "minecraft:bedrock", "minecraft:obsidian", ]); /** * Check whether a ground block exists at (x, y, z) and the space above is clear. */ function isPlotClear(dimension, ox, oy, oz, size) { for (let dx = 0; dx < size; dx++) { for (let dz = 0; dz < size; dz++) { const gx = ox + dx; const gz = oz + dz; // Ground block check const ground = dimension.getBlock({ x: gx, y: oy - 1, z: gz }); if (!ground) return false; if (UNSAFE_BLOCKS.has(ground.typeId)) return false; // Space above (2 blocks) must be air or leaves for (let dy = 0; dy <= 3; dy++) { const above = dimension.getBlock({ x: gx, y: oy + dy, z: gz }); if (!above) return false; const tid = above.typeId; if (tid !== "minecraft:air" && !tid.includes("leaves") && !tid.includes("grass") && !tid.includes("flower") && !tid.includes("tallgrass")) { return false; } } } } return true; } /** * Find a valid plot near the village center using an outward spiral search. * Returns {x, y, z} origin of plot or null if none found. */ function findPlot(dimension, center, footprint, builtPlots) { const halfSize = Math.ceil(footprint / 2); const ground = center.y; // Spiral outward from center let x = 0, z = 0; let step = 1, turn = 0, stepCount = 0, totalSteps = 0; while (totalSteps < 400) { const wx = center.x + x * 8 - halfSize; const wz = center.z + z * 8 - halfSize; // Skip if too close to an existing built plot const tooClose = builtPlots.some(([px, pz]) => { return Math.abs(px - wx) < footprint + 4 && Math.abs(pz - wz) < footprint + 4; }); if (!tooClose) { // Search vertically for the right ground level for (let dy = -5; dy <= 5; dy++) { const testY = ground + dy; if (isPlotClear(dimension, wx, testY, wz, footprint)) { const dist = Math.sqrt(x * x + z * z) * 8; if (dist <= MAX_PLOT_SEARCH_RADIUS) { return { x: wx, y: testY, z: wz }; } } } } // Advance spiral if (x === 0 && z === 0) { x = 1; totalSteps++; continue; } stepCount++; if (turn === 0) z--; else if (turn === 1) x--; else if (turn === 2) z++; else if (turn === 3) x++; if (stepCount >= step) { stepCount = 0; turn = (turn + 1) % 4; if (turn === 0 || turn === 2) step++; } totalSteps++; } return null; } // ─── Block Placement Queue ──────────────────────────────────────────────────── /** @type {Array<{dimension: any, cmd: string}>} */ let buildQueue = []; let buildQueueInterval = null; function enqueueBuild(dimension, template, ox, oy, oz) { for (const [dx, dy, dz, blockId, states] of template.blocks) { const x = ox + dx; const y = oy + dy; const z = oz + dz; const stateStr = states ? ` ${states}` : ""; buildQueue.push({ dimension, cmd: `setblock ${x} ${y} ${z} ${blockId}${stateStr} replace` }); } } function startBuildQueue() { if (buildQueueInterval !== null) return; buildQueueInterval = system.runInterval(() => { if (buildQueue.length === 0) { system.clearRun(buildQueueInterval); buildQueueInterval = null; return; } const batch = buildQueue.splice(0, BUILD_QUEUE_RATE); for (const { dimension, cmd } of batch) { try { dimension.runCommand(cmd); } catch (_) { // Non-fatal — chunk may not be loaded } } }, 1); } // ─── Village Expansion Logic ────────────────────────────────────────────────── function runVillageScan() { const overworld = world.getDimension("overworld"); let allVillagers; try { allVillagers = overworld.getEntities({ type: "minecraft:villager" }); } catch (e) { return; // World not ready } if (allVillagers.length === 0) return; const locations = allVillagers.map(v => v.location); const clusters = clusterVillagers(locations); for (const cluster of clusters) { if (cluster.length < MIN_VILLAGERS_TO_TRIGGER) continue; const village = clusterToVillage(cluster); const id = village.id; // Initialize registry entry if new village if (!villageRegistry[id]) { villageRegistry[id] = { center: village.center, buildIndex: 0, builtPlots: [], lastScanTick: 0, }; } const state = villageRegistry[id]; // Update center (villages drift slightly as villagers move) state.center = village.center; const buildIndex = state.buildIndex; if (buildIndex >= TIER_BUILDS.length) continue; // Village fully developed const nextBuild = TIER_BUILDS[buildIndex]; if (village.population < nextBuild.minPop) continue; // Not enough villagers yet // Find a valid plot const template = TEMPLATES[nextBuild.template]; const plot = findPlot(overworld, village.center, template.footprint, state.builtPlots); if (!plot) { // No valid plot found — skip this cycle, try again next scan continue; } // Clear the plot (remove grass/flowers above) for (let dx = 0; dx < template.footprint; dx++) { for (let dz = 0; dz < template.footprint; dz++) { for (let dy = 0; dy <= 3; dy++) { buildQueue.push({ dimension: overworld, cmd: `setblock ${plot.x + dx} ${plot.y + dy} ${plot.z + dz} air replace`, }); } } } // Enqueue the building enqueueBuild(overworld, template, plot.x, plot.y, plot.z); startBuildQueue(); // Update state state.builtPlots.push([plot.x, plot.z]); state.buildIndex++; state.lastScanTick = system.currentTick; saveState(); // Announce world.sendMessage( `§a[Village] §7A village near §e(${village.center.x}, ${village.center.z})§7 has grown — ${nextBuild.label} has been built!` ); } } // ─── Startup ────────────────────────────────────────────────────────────────── // Register dynamic property schema (max string length) world.afterEvents.worldInitialize.subscribe((event) => { try { event.propertyRegistry.defineWorldProperty({ id: DYNAMIC_PROP_KEY, type: "string", maxLength: DYNAMIC_PROP_MAX, }); } catch (_) { // Already registered (e.g. pack was loaded before) } }); system.run(() => { loadState(); world.sendMessage("§a[Village Evolution] §7Village growth system active."); // Main scan loop system.runInterval(() => { runVillageScan(); }, SCAN_INTERVAL_TICKS); });