feat(camping): add craftable tent and hammock addon
All checks were successful
Deploy Addons / deploy (push) Successful in 13s
All checks were successful
Deploy Addons / deploy (push) Successful in 13s
Tent pitches over a 2x3 flat footprint and lets players skip to dawn without touching their spawn point. Hammock strings between two posts 3-6 blocks apart (straight or diagonal, +/-1 block height) and keeps hostile mobs at bay while occupied. Both are craftable (wool/sticks and wool/string) and mounted in all four worlds. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"format_version": "1.21.0",
|
||||
"minecraft:block": {
|
||||
"description": {
|
||||
"identifier": "silverlabs:hammock_cloth"
|
||||
},
|
||||
"components": {
|
||||
"minecraft:destructible_by_mining": {
|
||||
"seconds_to_destroy": 0.3
|
||||
},
|
||||
"minecraft:destructible_by_explosion": {
|
||||
"explosion_resistance": 0.5
|
||||
},
|
||||
"minecraft:map_color": "#B43C37",
|
||||
"minecraft:material_instances": {
|
||||
"*": {
|
||||
"texture": "hammock_cloth",
|
||||
"render_method": "alpha_test"
|
||||
}
|
||||
},
|
||||
"minecraft:collision_box": {
|
||||
"origin": [-8, 0, -8],
|
||||
"size": [16, 4, 16]
|
||||
},
|
||||
"minecraft:selection_box": {
|
||||
"origin": [-8, 0, -8],
|
||||
"size": [16, 4, 16]
|
||||
},
|
||||
"minecraft:geometry": "minecraft:geometry.full_block",
|
||||
"minecraft:light_dampening": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"format_version": "1.21.0",
|
||||
"minecraft:block": {
|
||||
"description": {
|
||||
"identifier": "silverlabs:tent_canvas"
|
||||
},
|
||||
"components": {
|
||||
"minecraft:destructible_by_mining": {
|
||||
"seconds_to_destroy": 0.4
|
||||
},
|
||||
"minecraft:destructible_by_explosion": {
|
||||
"explosion_resistance": 1.0
|
||||
},
|
||||
"minecraft:map_color": "#547A4E",
|
||||
"minecraft:material_instances": {
|
||||
"*": {
|
||||
"texture": "tent_canvas",
|
||||
"render_method": "alpha_test"
|
||||
}
|
||||
},
|
||||
"minecraft:light_dampening": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"format_version": "1.21.0",
|
||||
"minecraft:item": {
|
||||
"description": {
|
||||
"identifier": "silverlabs:hammock",
|
||||
"menu_category": {
|
||||
"category": "equipment",
|
||||
"group": "itemGroup.name.miscellaneous"
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"minecraft:max_stack_size": 16,
|
||||
"minecraft:icon": {
|
||||
"texture": "hammock_item"
|
||||
},
|
||||
"minecraft:hand_equipped": true
|
||||
}
|
||||
}
|
||||
}
|
||||
19
camping-supplies-addon/camping_supplies_BP/items/tent.json
Normal file
19
camping-supplies-addon/camping_supplies_BP/items/tent.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"format_version": "1.21.0",
|
||||
"minecraft:item": {
|
||||
"description": {
|
||||
"identifier": "silverlabs:tent",
|
||||
"menu_category": {
|
||||
"category": "equipment",
|
||||
"group": "itemGroup.name.miscellaneous"
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"minecraft:max_stack_size": 16,
|
||||
"minecraft:icon": {
|
||||
"texture": "tent_item"
|
||||
},
|
||||
"minecraft:hand_equipped": true
|
||||
}
|
||||
}
|
||||
}
|
||||
34
camping-supplies-addon/camping_supplies_BP/manifest.json
Normal file
34
camping-supplies-addon/camping_supplies_BP/manifest.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"format_version": 2,
|
||||
"header": {
|
||||
"name": "Camping Supplies",
|
||||
"description": "Craftable tent and hammock for overnight camping without setting your spawn point",
|
||||
"uuid": "bcf569fa-8b2c-403e-9f75-6b405132c5cd",
|
||||
"version": [1, 0, 0],
|
||||
"min_engine_version": [1, 21, 0]
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
"type": "data",
|
||||
"uuid": "f306e1d8-3c13-4554-9715-4799ce6d41d8",
|
||||
"version": [1, 0, 0]
|
||||
},
|
||||
{
|
||||
"type": "script",
|
||||
"language": "javascript",
|
||||
"uuid": "1e496657-0c83-4707-a1e8-29b757dcce79",
|
||||
"version": [1, 0, 0],
|
||||
"entry": "scripts/main.js"
|
||||
}
|
||||
],
|
||||
"dependencies": [
|
||||
{
|
||||
"module_name": "@minecraft/server",
|
||||
"version": "1.17.0"
|
||||
},
|
||||
{
|
||||
"uuid": "36f12107-10c6-484c-a0f2-b5dd88cd5baa",
|
||||
"version": [1, 0, 0]
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
camping-supplies-addon/camping_supplies_BP/pack_icon.png
Normal file
BIN
camping-supplies-addon/camping_supplies_BP/pack_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"format_version": "1.21.0",
|
||||
"minecraft:recipe_shaped": {
|
||||
"description": {
|
||||
"identifier": "silverlabs:hammock_recipe"
|
||||
},
|
||||
"tags": ["crafting_table"],
|
||||
"unlock": { "context": "AlwaysUnlocked" },
|
||||
"pattern": [
|
||||
"T T",
|
||||
"TWWWT"
|
||||
],
|
||||
"key": {
|
||||
"W": { "item": "minecraft:white_wool" },
|
||||
"T": { "item": "minecraft:string" }
|
||||
},
|
||||
"result": {
|
||||
"item": "silverlabs:hammock",
|
||||
"count": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
23
camping-supplies-addon/camping_supplies_BP/recipes/tent.json
Normal file
23
camping-supplies-addon/camping_supplies_BP/recipes/tent.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"format_version": "1.21.0",
|
||||
"minecraft:recipe_shaped": {
|
||||
"description": {
|
||||
"identifier": "silverlabs:tent_recipe"
|
||||
},
|
||||
"tags": ["crafting_table"],
|
||||
"unlock": { "context": "AlwaysUnlocked" },
|
||||
"pattern": [
|
||||
" W ",
|
||||
"WWW",
|
||||
"S S"
|
||||
],
|
||||
"key": {
|
||||
"W": { "item": "minecraft:white_wool" },
|
||||
"S": { "item": "minecraft:stick" }
|
||||
},
|
||||
"result": {
|
||||
"item": "silverlabs:tent",
|
||||
"count": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
442
camping-supplies-addon/camping_supplies_BP/scripts/main.js
Normal file
442
camping-supplies-addon/camping_supplies_BP/scripts/main.js
Normal file
@@ -0,0 +1,442 @@
|
||||
import { world, system, ItemStack } from "@minecraft/server";
|
||||
|
||||
// ─── Constants ──────────────────────────────────────────────
|
||||
const TENT_ITEM = "silverlabs:tent";
|
||||
const HAMMOCK_ITEM = "silverlabs:hammock";
|
||||
const TENT_BLOCK = "silverlabs:tent_canvas";
|
||||
const HAMMOCK_BLOCK = "silverlabs:hammock_cloth";
|
||||
const STATE_PROP = "camping_state_v1";
|
||||
const HAMMOCK_TAG = "camping_hammock";
|
||||
|
||||
// ─── State ──────────────────────────────────────────────────
|
||||
// tents: key = "ox,oy,oz,dim" -> { ownerId, ownerName, cells: [[x,y,z]...] }
|
||||
// hammocks: key = "ax,ay,az->bx,by,bz,dim" -> { ownerId, ownerName, anchorA, anchorB, cells }
|
||||
let state = { tents: {}, hammocks: {} };
|
||||
|
||||
function loadState() {
|
||||
try {
|
||||
const raw = world.getDynamicProperty(STATE_PROP);
|
||||
if (raw && typeof raw === "string") {
|
||||
const parsed = JSON.parse(raw);
|
||||
state = {
|
||||
tents: parsed.tents || {},
|
||||
hammocks: parsed.hammocks || {},
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
world.sendMessage(`§c[Camping] state load failed: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function saveState() {
|
||||
try {
|
||||
world.setDynamicProperty(STATE_PROP, JSON.stringify(state));
|
||||
} catch (e) {
|
||||
world.sendMessage(`§c[Camping] state save failed: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function keyOf(x, y, z, dimId) {
|
||||
return `${x},${y},${z},${dimId}`;
|
||||
}
|
||||
|
||||
// ─── Orientation helpers ────────────────────────────────────
|
||||
function cardinalFacing(yaw) {
|
||||
let y = yaw;
|
||||
while (y > 180) y -= 360;
|
||||
while (y < -180) y += 360;
|
||||
if (y >= -45 && y < 45) return "south";
|
||||
if (y >= 45 && y < 135) return "west";
|
||||
if (y >= -135 && y < -45) return "east";
|
||||
return "north";
|
||||
}
|
||||
|
||||
function vecsForFacing(facing) {
|
||||
switch (facing) {
|
||||
case "north": return { fx: 0, fz: -1, rx: 1, rz: 0 };
|
||||
case "south": return { fx: 0, fz: 1, rx: -1, rz: 0 };
|
||||
case "east": return { fx: 1, fz: 0, rx: 0, rz: -1 };
|
||||
case "west": return { fx: -1, fz: 0, rx: 0, rz: 1 };
|
||||
}
|
||||
return { fx: 0, fz: 1, rx: -1, rz: 0 };
|
||||
}
|
||||
|
||||
// ─── Inventory helpers ──────────────────────────────────────
|
||||
function consumeOneOfType(player, typeId) {
|
||||
const inv = player.getComponent("inventory")?.container;
|
||||
if (!inv) return false;
|
||||
const slot = player.selectedSlotIndex;
|
||||
const item = inv.getItem(slot);
|
||||
if (item && item.typeId === typeId) {
|
||||
if (item.amount > 1) {
|
||||
item.amount -= 1;
|
||||
inv.setItem(slot, item);
|
||||
} else {
|
||||
inv.setItem(slot, undefined);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
for (let i = 0; i < inv.size; i++) {
|
||||
const it = inv.getItem(i);
|
||||
if (it && it.typeId === typeId) {
|
||||
if (it.amount > 1) {
|
||||
it.amount -= 1;
|
||||
inv.setItem(i, it);
|
||||
} else {
|
||||
inv.setItem(i, undefined);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ─── Tent placement (2×3 footprint, ridge-tunnel shape) ─────
|
||||
function tryPlaceTent(player) {
|
||||
const dim = player.dimension;
|
||||
const facing = cardinalFacing(player.getRotation().y);
|
||||
const { fx, fz, rx, rz } = vecsForFacing(facing);
|
||||
const feet = {
|
||||
x: Math.floor(player.location.x),
|
||||
y: Math.floor(player.location.y),
|
||||
z: Math.floor(player.location.z),
|
||||
};
|
||||
const ox = feet.x + fx;
|
||||
const oy = feet.y;
|
||||
const oz = feet.z + fz;
|
||||
|
||||
const groundCells = [];
|
||||
const clearCells = [];
|
||||
for (let l = 0; l < 3; l++) {
|
||||
for (let w = 0; w < 2; w++) {
|
||||
const cx = ox + l * fx + w * rx;
|
||||
const cz = oz + l * fz + w * rz;
|
||||
groundCells.push({ x: cx, y: oy - 1, z: cz });
|
||||
for (let h = 0; h <= 2; h++) clearCells.push({ x: cx, y: oy + h, z: cz });
|
||||
}
|
||||
}
|
||||
|
||||
for (const g of groundCells) {
|
||||
const b = dim.getBlock(g);
|
||||
if (!b || !b.isSolid) {
|
||||
player.sendMessage("§c[Camping] §7Need a flat 2×3 patch of solid ground in front of you.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const c of clearCells) {
|
||||
const b = dim.getBlock(c);
|
||||
if (!b) {
|
||||
player.sendMessage("§c[Camping] §7Can't reach that area (chunk unloaded).");
|
||||
return false;
|
||||
}
|
||||
if (!b.isAir && !b.isLiquid) {
|
||||
player.sendMessage("§c[Camping] §7The space above the tent footprint isn't clear.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const canvasCells = [];
|
||||
for (let w = 0; w < 2; w++) {
|
||||
// Back wall at l=0 (Y=0 and Y=1)
|
||||
canvasCells.push({ x: ox + w * rx, y: oy + 0, z: oz + w * rz });
|
||||
canvasCells.push({ x: ox + w * rx, y: oy + 1, z: oz + w * rz });
|
||||
// Roof at l=1 and l=2 (Y=1)
|
||||
canvasCells.push({ x: ox + 1 * fx + w * rx, y: oy + 1, z: oz + 1 * fz + w * rz });
|
||||
canvasCells.push({ x: ox + 2 * fx + w * rx, y: oy + 1, z: oz + 2 * fz + w * rz });
|
||||
}
|
||||
|
||||
for (const c of canvasCells) {
|
||||
try {
|
||||
dim.runCommand(`setblock ${c.x} ${c.y} ${c.z} ${TENT_BLOCK}`);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
const key = keyOf(ox, oy, oz, dim.id);
|
||||
state.tents[key] = {
|
||||
ownerId: player.id,
|
||||
ownerName: player.name,
|
||||
cells: canvasCells.map((c) => [c.x, c.y, c.z]),
|
||||
};
|
||||
saveState();
|
||||
return true;
|
||||
}
|
||||
|
||||
// ─── Hammock placement ──────────────────────────────────────
|
||||
function isPostBlock(block) {
|
||||
if (!block) return false;
|
||||
const id = block.typeId;
|
||||
return (
|
||||
id.endsWith("_fence") ||
|
||||
id.endsWith("_log") ||
|
||||
id.endsWith("_wood") ||
|
||||
id.includes("stripped_") ||
|
||||
id.endsWith("_wall")
|
||||
);
|
||||
}
|
||||
|
||||
function findPartnerPost(dim, anchor) {
|
||||
const candidates = [];
|
||||
for (let dx = -6; dx <= 6; dx++) {
|
||||
for (let dz = -6; dz <= 6; dz++) {
|
||||
if (dx === 0 && dz === 0) continue;
|
||||
const aligned = dx === 0 || dz === 0 || Math.abs(dx) === Math.abs(dz);
|
||||
if (!aligned) continue;
|
||||
const dist = Math.max(Math.abs(dx), Math.abs(dz));
|
||||
if (dist < 3 || dist > 6) continue;
|
||||
for (let dy = -1; dy <= 1; dy++) {
|
||||
const pos = { x: anchor.x + dx, y: anchor.y + dy, z: anchor.z + dz };
|
||||
let blk;
|
||||
try { blk = dim.getBlock(pos); } catch (_) { continue; }
|
||||
if (!blk || !isPostBlock(blk)) continue;
|
||||
candidates.push({ pos, dist, dy });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (candidates.length === 0) return null;
|
||||
candidates.sort((a, b) => (Math.abs(a.dy) - Math.abs(b.dy)) || (a.dist - b.dist));
|
||||
return candidates[0].pos;
|
||||
}
|
||||
|
||||
function computeHammockCells(a, b) {
|
||||
const dx = b.x - a.x;
|
||||
const dy = b.y - a.y;
|
||||
const dz = b.z - a.z;
|
||||
const steps = Math.max(Math.abs(dx), Math.abs(dz));
|
||||
const cells = [];
|
||||
for (let t = 1; t < steps; t++) {
|
||||
const cx = a.x + Math.round((dx * t) / steps);
|
||||
const cz = a.z + Math.round((dz * t) / steps);
|
||||
let cy = a.y + Math.round((dy * t) / steps);
|
||||
const rel = t / steps;
|
||||
if (steps >= 4 && rel > 0.25 && rel < 0.75) cy -= 1;
|
||||
cells.push({ x: cx, y: cy, z: cz });
|
||||
}
|
||||
return cells;
|
||||
}
|
||||
|
||||
function tryPlaceHammock(player, anchorBlock) {
|
||||
const dim = player.dimension;
|
||||
const a = {
|
||||
x: anchorBlock.location.x,
|
||||
y: anchorBlock.location.y,
|
||||
z: anchorBlock.location.z,
|
||||
};
|
||||
const b = findPartnerPost(dim, a);
|
||||
if (!b) {
|
||||
player.sendMessage("§c[Camping] §7Need a second post 3–6 blocks away (straight line or diagonal, ±1 block in height).");
|
||||
return false;
|
||||
}
|
||||
const cells = computeHammockCells(a, b);
|
||||
for (const c of cells) {
|
||||
let blk;
|
||||
try { blk = dim.getBlock(c); } catch (_) { return false; }
|
||||
if (!blk || (!blk.isAir && !blk.isLiquid)) {
|
||||
player.sendMessage("§c[Camping] §7The space between the posts isn't clear.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const c of cells) {
|
||||
try { dim.runCommand(`setblock ${c.x} ${c.y} ${c.z} ${HAMMOCK_BLOCK}`); } catch (_) {}
|
||||
}
|
||||
const key = `${a.x},${a.y},${a.z}->${b.x},${b.y},${b.z},${dim.id}`;
|
||||
state.hammocks[key] = {
|
||||
ownerId: player.id,
|
||||
ownerName: player.name,
|
||||
anchorA: [a.x, a.y, a.z],
|
||||
anchorB: [b.x, b.y, b.z],
|
||||
cells: cells.map((c) => [c.x, c.y, c.z]),
|
||||
};
|
||||
saveState();
|
||||
return true;
|
||||
}
|
||||
|
||||
// ─── Item use handler ───────────────────────────────────────
|
||||
world.afterEvents.itemUse.subscribe((event) => {
|
||||
const player = event.source;
|
||||
const stack = event.itemStack;
|
||||
if (!stack || !player) return;
|
||||
if (stack.typeId === TENT_ITEM) {
|
||||
system.run(() => {
|
||||
if (tryPlaceTent(player)) {
|
||||
consumeOneOfType(player, TENT_ITEM);
|
||||
player.sendMessage("§a[Camping] §7Tent pitched. Right-click the canvas to rest until dawn.");
|
||||
}
|
||||
});
|
||||
} else if (stack.typeId === HAMMOCK_ITEM) {
|
||||
system.run(() => {
|
||||
const looking = player.getBlockFromViewDirection({ maxDistance: 6 });
|
||||
const block = looking?.block;
|
||||
if (!block || !isPostBlock(block)) {
|
||||
player.sendMessage("§c[Camping] §7Aim at a fence, log, or wooden post to anchor the hammock.");
|
||||
return;
|
||||
}
|
||||
if (tryPlaceHammock(player, block)) {
|
||||
consumeOneOfType(player, HAMMOCK_ITEM);
|
||||
player.sendMessage("§a[Camping] §7Hammock strung. Right-click the cloth to climb in.");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ─── Interact: tent rest + hammock toggle ───────────────────
|
||||
try {
|
||||
world.beforeEvents.playerInteractWithBlock.subscribe((event) => {
|
||||
const block = event.block;
|
||||
if (!block) return;
|
||||
if (block.typeId === TENT_BLOCK) {
|
||||
event.cancel = true;
|
||||
const player = event.player;
|
||||
system.run(() => sleepInTent(player));
|
||||
} else if (block.typeId === HAMMOCK_BLOCK) {
|
||||
event.cancel = true;
|
||||
const player = event.player;
|
||||
const loc = block.location;
|
||||
system.run(() => toggleHammock(player, loc));
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn(`[Camping] playerInteractWithBlock unavailable: ${e}`);
|
||||
}
|
||||
|
||||
function sleepInTent(player) {
|
||||
const tod = world.getTimeOfDay();
|
||||
if (tod < 12500 && tod > 500) {
|
||||
player.sendMessage("§e[Camping] §7It's still daylight — nothing to sleep off.");
|
||||
return;
|
||||
}
|
||||
player.addEffect("regeneration", 200, { amplifier: 1, showParticles: false });
|
||||
player.addEffect("saturation", 40, { amplifier: 0, showParticles: false });
|
||||
world.setTimeOfDay(0);
|
||||
player.sendMessage("§a[Camping] §7You rest until dawn. §8Spawn point unchanged.");
|
||||
}
|
||||
|
||||
function toggleHammock(player, loc) {
|
||||
if (player.hasTag(HAMMOCK_TAG)) {
|
||||
player.removeTag(HAMMOCK_TAG);
|
||||
player.sendMessage("§7[Camping] You climb out of the hammock.");
|
||||
return;
|
||||
}
|
||||
player.addTag(HAMMOCK_TAG);
|
||||
try {
|
||||
player.teleport(
|
||||
{ x: loc.x + 0.5, y: loc.y + 0.4, z: loc.z + 0.5 },
|
||||
{ dimension: player.dimension }
|
||||
);
|
||||
} catch (_) {}
|
||||
player.sendMessage("§a[Camping] §7You settle into the hammock. Wild creatures don't notice you. §8(Sneak to climb out.)");
|
||||
}
|
||||
|
||||
// ─── Hammock upkeep loop: mob repulsion + sneak-exit ────────
|
||||
system.runInterval(() => {
|
||||
for (const player of world.getAllPlayers()) {
|
||||
if (!player.hasTag(HAMMOCK_TAG)) continue;
|
||||
if (player.isSneaking) {
|
||||
player.removeTag(HAMMOCK_TAG);
|
||||
player.sendMessage("§7[Camping] You climb out of the hammock.");
|
||||
continue;
|
||||
}
|
||||
let hostiles = [];
|
||||
try {
|
||||
hostiles = player.dimension.getEntities({
|
||||
families: ["monster"],
|
||||
location: player.location,
|
||||
maxDistance: 14,
|
||||
});
|
||||
} catch (_) {}
|
||||
for (const m of hostiles) {
|
||||
const dx = m.location.x - player.location.x;
|
||||
const dy = m.location.y - player.location.y;
|
||||
const dz = m.location.z - player.location.z;
|
||||
const d = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
if (d > 0.01 && d < 6) {
|
||||
const scale = 9 / d;
|
||||
const target = {
|
||||
x: player.location.x + dx * scale,
|
||||
y: m.location.y,
|
||||
z: player.location.z + dz * scale,
|
||||
};
|
||||
try { m.tryTeleport(target, { checkForBlocks: true }); } catch (_) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 10);
|
||||
|
||||
// ─── Break cleanup: break one = pack up the whole structure ─
|
||||
try {
|
||||
world.beforeEvents.playerBreakBlock.subscribe((event) => {
|
||||
const block = event.block;
|
||||
if (!block) return;
|
||||
const id = block.typeId;
|
||||
if (id !== TENT_BLOCK && id !== HAMMOCK_BLOCK) return;
|
||||
event.cancel = true;
|
||||
const loc = { x: block.location.x, y: block.location.y, z: block.location.z };
|
||||
const dimId = block.dimension.id;
|
||||
const player = event.player;
|
||||
if (id === TENT_BLOCK) {
|
||||
system.run(() => dismantleTentAt(loc, dimId, player));
|
||||
} else {
|
||||
system.run(() => dismantleHammockAt(loc, dimId, player));
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn(`[Camping] playerBreakBlock unavailable: ${e}`);
|
||||
}
|
||||
|
||||
function dismantleTentAt(loc, dimId, player) {
|
||||
const dim = world.getDimension(dimId);
|
||||
let matchedKey = null;
|
||||
for (const [k, tent] of Object.entries(state.tents)) {
|
||||
const parts = k.split(",");
|
||||
if (parts[parts.length - 1] !== dimId) continue;
|
||||
if (tent.cells.some(([x, y, z]) => x === loc.x && y === loc.y && z === loc.z)) {
|
||||
matchedKey = k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matchedKey) {
|
||||
try { dim.runCommand(`setblock ${loc.x} ${loc.y} ${loc.z} air`); } catch (_) {}
|
||||
return;
|
||||
}
|
||||
const tent = state.tents[matchedKey];
|
||||
for (const [x, y, z] of tent.cells) {
|
||||
try { dim.runCommand(`setblock ${x} ${y} ${z} air`); } catch (_) {}
|
||||
}
|
||||
delete state.tents[matchedKey];
|
||||
saveState();
|
||||
try {
|
||||
dim.spawnItem(new ItemStack(TENT_ITEM, 1), { x: loc.x + 0.5, y: loc.y + 0.5, z: loc.z + 0.5 });
|
||||
} catch (_) {}
|
||||
if (player) player.sendMessage("§7[Camping] Tent packed up.");
|
||||
}
|
||||
|
||||
function dismantleHammockAt(loc, dimId, player) {
|
||||
const dim = world.getDimension(dimId);
|
||||
let matchedKey = null;
|
||||
for (const [k, h] of Object.entries(state.hammocks)) {
|
||||
if (!k.endsWith("," + dimId)) continue;
|
||||
if (h.cells.some(([x, y, z]) => x === loc.x && y === loc.y && z === loc.z)) {
|
||||
matchedKey = k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matchedKey) {
|
||||
try { dim.runCommand(`setblock ${loc.x} ${loc.y} ${loc.z} air`); } catch (_) {}
|
||||
return;
|
||||
}
|
||||
const h = state.hammocks[matchedKey];
|
||||
for (const [x, y, z] of h.cells) {
|
||||
try { dim.runCommand(`setblock ${x} ${y} ${z} air`); } catch (_) {}
|
||||
}
|
||||
delete state.hammocks[matchedKey];
|
||||
saveState();
|
||||
try {
|
||||
dim.spawnItem(new ItemStack(HAMMOCK_ITEM, 1), { x: loc.x + 0.5, y: loc.y + 0.5, z: loc.z + 0.5 });
|
||||
} catch (_) {}
|
||||
if (player) player.sendMessage("§7[Camping] Hammock taken down.");
|
||||
}
|
||||
|
||||
// ─── Boot ───────────────────────────────────────────────────
|
||||
system.run(() => {
|
||||
loadState();
|
||||
world.sendMessage("§6[Camping] §7Camping Supplies loaded.");
|
||||
});
|
||||
5
camping-supplies-addon/camping_supplies_RP/blocks.json
Normal file
5
camping-supplies-addon/camping_supplies_RP/blocks.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"format_version": [1, 1, 0],
|
||||
"silverlabs:tent_canvas": { "sound": "cloth" },
|
||||
"silverlabs:hammock_cloth": { "sound": "cloth" }
|
||||
}
|
||||
17
camping-supplies-addon/camping_supplies_RP/manifest.json
Normal file
17
camping-supplies-addon/camping_supplies_RP/manifest.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"format_version": 2,
|
||||
"header": {
|
||||
"name": "Camping Supplies Resources",
|
||||
"description": "Textures and lang for camping supplies (tent, hammock, canvas, cloth)",
|
||||
"uuid": "36f12107-10c6-484c-a0f2-b5dd88cd5baa",
|
||||
"version": [1, 0, 0],
|
||||
"min_engine_version": [1, 21, 0]
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
"type": "resources",
|
||||
"uuid": "c9ee429f-9374-4083-843b-4b195e8db130",
|
||||
"version": [1, 0, 0]
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
camping-supplies-addon/camping_supplies_RP/pack_icon.png
Normal file
BIN
camping-supplies-addon/camping_supplies_RP/pack_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,4 @@
|
||||
item.silverlabs:tent.name=Tent
|
||||
item.silverlabs:hammock.name=Hammock
|
||||
tile.silverlabs:tent_canvas.name=Tent Canvas
|
||||
tile.silverlabs:hammock_cloth.name=Hammock Cloth
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 772 B |
Binary file not shown.
|
After Width: | Height: | Size: 773 B |
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"resource_pack_name": "camping_supplies_RP",
|
||||
"texture_name": "atlas.items",
|
||||
"texture_data": {
|
||||
"tent_item": {
|
||||
"textures": "textures/items/tent"
|
||||
},
|
||||
"hammock_item": {
|
||||
"textures": "textures/items/hammock"
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 211 B |
Binary file not shown.
|
After Width: | Height: | Size: 180 B |
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"resource_pack_name": "camping_supplies_RP",
|
||||
"texture_name": "atlas.terrain",
|
||||
"padding": 8,
|
||||
"num_mip_levels": 4,
|
||||
"texture_data": {
|
||||
"tent_canvas": {
|
||||
"textures": "textures/blocks/tent_canvas"
|
||||
},
|
||||
"hammock_cloth": {
|
||||
"textures": "textures/blocks/hammock_cloth"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,8 @@ services:
|
||||
- ./keep-inventory-addon/keep_inventory_BP:/data/behavior_packs/keep_inventory_BP
|
||||
- ./postal-service-addon/postal_service_BP:/data/behavior_packs/postal_service_BP
|
||||
- ./postal-service-addon/postal_service_RP:/data/resource_packs/postal_service_RP
|
||||
- ./camping-supplies-addon/camping_supplies_BP:/data/behavior_packs/camping_supplies_BP
|
||||
- ./camping-supplies-addon/camping_supplies_RP:/data/resource_packs/camping_supplies_RP
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- mc-network
|
||||
@@ -59,6 +61,8 @@ services:
|
||||
- ./keep-inventory-addon/keep_inventory_BP:/data/behavior_packs/keep_inventory_BP
|
||||
- ./postal-service-addon/postal_service_BP:/data/behavior_packs/postal_service_BP
|
||||
- ./postal-service-addon/postal_service_RP:/data/resource_packs/postal_service_RP
|
||||
- ./camping-supplies-addon/camping_supplies_BP:/data/behavior_packs/camping_supplies_BP
|
||||
- ./camping-supplies-addon/camping_supplies_RP:/data/resource_packs/camping_supplies_RP
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- mc-network
|
||||
@@ -91,6 +95,8 @@ services:
|
||||
- ./keep-inventory-addon/keep_inventory_BP:/data/behavior_packs/keep_inventory_BP
|
||||
- ./postal-service-addon/postal_service_BP:/data/behavior_packs/postal_service_BP
|
||||
- ./postal-service-addon/postal_service_RP:/data/resource_packs/postal_service_RP
|
||||
- ./camping-supplies-addon/camping_supplies_BP:/data/behavior_packs/camping_supplies_BP
|
||||
- ./camping-supplies-addon/camping_supplies_RP:/data/resource_packs/camping_supplies_RP
|
||||
- ./village-evolution-addon/enabled_packs.json:/data/config/default/enabled_packs.json
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
@@ -124,6 +130,8 @@ services:
|
||||
- ./keep-inventory-addon/keep_inventory_BP:/data/behavior_packs/keep_inventory_BP
|
||||
- ./postal-service-addon/postal_service_BP:/data/behavior_packs/postal_service_BP
|
||||
- ./postal-service-addon/postal_service_RP:/data/resource_packs/postal_service_RP
|
||||
- ./camping-supplies-addon/camping_supplies_BP:/data/behavior_packs/camping_supplies_BP
|
||||
- ./camping-supplies-addon/camping_supplies_RP:/data/resource_packs/camping_supplies_RP
|
||||
- ./village-evolution-addon/enabled_packs.json:/data/config/default/enabled_packs.json
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
|
||||
Reference in New Issue
Block a user