feat(camping): three-tier ore detector with private-chest faraday cage
New basic/improved/advanced detectors (8/16/32 block range). Aim and right-click to ping the nearest ore on the view ray; pitch-coded sound and action-bar text show distance and ore type. Any ore within 4 blocks of a silverlabs:private_chest is hidden — chests act as faraday cages so claimed bases stay private from neighbours' detectors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -703,6 +703,116 @@ function dismantleHammockAt(loc, dimId, player) {
|
||||
if (player) player.sendMessage("§7[Camping] Hammock taken down.");
|
||||
}
|
||||
|
||||
// ─── Ore Detector ───────────────────────────────────────────
|
||||
// Three tiered items share one scan handler. Tier → max scan range.
|
||||
// Faraday: any ore within 4 blocks of a silverlabs:private_chest is hidden
|
||||
// (mirrors the source DetectOre faraday-cage mechanic, but built into private chests).
|
||||
const DETECTOR_RANGES = {
|
||||
"silverlabs:ore_detector_basic": 8,
|
||||
"silverlabs:ore_detector_improved": 16,
|
||||
"silverlabs:ore_detector_advanced": 32,
|
||||
};
|
||||
const PRIVATE_CHEST_ID = "silverlabs:private_chest";
|
||||
const FARADAY_RADIUS = 4; // 9³ cube around each ore candidate
|
||||
|
||||
const ORE_IDS = new Set([
|
||||
"minecraft:coal_ore", "minecraft:deepslate_coal_ore",
|
||||
"minecraft:iron_ore", "minecraft:deepslate_iron_ore",
|
||||
"minecraft:copper_ore", "minecraft:deepslate_copper_ore",
|
||||
"minecraft:gold_ore", "minecraft:deepslate_gold_ore", "minecraft:nether_gold_ore",
|
||||
"minecraft:redstone_ore", "minecraft:deepslate_redstone_ore",
|
||||
"minecraft:lit_redstone_ore", "minecraft:lit_deepslate_redstone_ore",
|
||||
"minecraft:lapis_ore", "minecraft:deepslate_lapis_ore",
|
||||
"minecraft:emerald_ore", "minecraft:deepslate_emerald_ore",
|
||||
"minecraft:diamond_ore", "minecraft:deepslate_diamond_ore",
|
||||
"minecraft:nether_quartz_ore",
|
||||
"minecraft:ancient_debris",
|
||||
]);
|
||||
|
||||
function prettyOreName(typeId) {
|
||||
const bare = typeId.replace(/^minecraft:/, "")
|
||||
.replace(/^lit_/, "")
|
||||
.replace(/^deepslate_/, "Deepslate ")
|
||||
.replace(/^nether_/, "Nether ");
|
||||
return bare.split("_").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
||||
}
|
||||
|
||||
function isPrivateChestNearby(dim, cx, cy, cz) {
|
||||
for (let dx = -FARADAY_RADIUS; dx <= FARADAY_RADIUS; dx++) {
|
||||
for (let dy = -FARADAY_RADIUS; dy <= FARADAY_RADIUS; dy++) {
|
||||
for (let dz = -FARADAY_RADIUS; dz <= FARADAY_RADIUS; dz++) {
|
||||
const b = dim.getBlock({ x: cx + dx, y: cy + dy, z: cz + dz });
|
||||
if (b && b.typeId === PRIVATE_CHEST_ID) return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function runOreScan(player, range) {
|
||||
const dim = player.dimension;
|
||||
const head = player.getHeadLocation();
|
||||
const dir = player.getViewDirection();
|
||||
|
||||
// Walk integer-block steps along the view ray. Resolution 0.5 blocks
|
||||
// catches ores the cardinal-aligned steps would skip on diagonals.
|
||||
const stepCount = Math.floor(range * 2);
|
||||
const seen = new Set();
|
||||
let foundOre = null;
|
||||
let foundDist = 0;
|
||||
|
||||
for (let i = 1; i <= stepCount; i++) {
|
||||
const t = i * 0.5;
|
||||
const px = head.x + dir.x * t;
|
||||
const py = head.y + dir.y * t;
|
||||
const pz = head.z + dir.z * t;
|
||||
const bx = Math.floor(px);
|
||||
const by = Math.floor(py);
|
||||
const bz = Math.floor(pz);
|
||||
const key = `${bx},${by},${bz}`;
|
||||
if (seen.has(key)) continue;
|
||||
seen.add(key);
|
||||
|
||||
let block;
|
||||
try { block = dim.getBlock({ x: bx, y: by, z: bz }); } catch (_) { continue; }
|
||||
if (!block) continue;
|
||||
|
||||
if (ORE_IDS.has(block.typeId)) {
|
||||
if (isPrivateChestNearby(dim, bx, by, bz)) continue; // faraday
|
||||
foundOre = block.typeId;
|
||||
foundDist = t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundOre) {
|
||||
const distRounded = Math.round(foundDist * 10) / 10;
|
||||
const pitch = Math.max(0.8, Math.min(2.0, 2.0 - (foundDist / range)));
|
||||
try {
|
||||
player.playSound("random.orb", { pitch, volume: 0.7 });
|
||||
} catch (_) {}
|
||||
try {
|
||||
player.onScreenDisplay.setActionBar(`§a● §f${prettyOreName(foundOre)} §7at §b${distRounded}m`);
|
||||
} catch (_) {}
|
||||
} else {
|
||||
try {
|
||||
player.playSound("note.bass", { pitch: 0.7, volume: 0.5 });
|
||||
} catch (_) {}
|
||||
try {
|
||||
player.onScreenDisplay.setActionBar(`§7○ No ores within §f${range}m`);
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
world.afterEvents.itemUse.subscribe((event) => {
|
||||
const player = event.source;
|
||||
const stack = event.itemStack;
|
||||
if (!stack || !player) return;
|
||||
const range = DETECTOR_RANGES[stack.typeId];
|
||||
if (!range) return;
|
||||
system.run(() => runOreScan(player, range));
|
||||
});
|
||||
|
||||
// ─── Boot ───────────────────────────────────────────────────
|
||||
system.run(() => {
|
||||
loadState();
|
||||
|
||||
Reference in New Issue
Block a user