From 8b83e324f09d0559f9de8d193a85d404db1a242a Mon Sep 17 00:00:00 2001 From: SysAdmin Date: Thu, 23 Apr 2026 23:10:21 +0100 Subject: [PATCH] feat(camping): add craftable tent and hammock addon 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) --- .../blocks/hammock_cloth.json | 33 ++ .../blocks/tent_canvas.json | 24 + .../camping_supplies_BP/items/hammock.json | 19 + .../camping_supplies_BP/items/tent.json | 19 + .../camping_supplies_BP/manifest.json | 34 ++ .../camping_supplies_BP/pack_icon.png | Bin 0 -> 1143 bytes .../camping_supplies_BP/recipes/hammock.json | 22 + .../camping_supplies_BP/recipes/tent.json | 23 + .../camping_supplies_BP/scripts/main.js | 442 ++++++++++++++++++ .../camping_supplies_RP/blocks.json | 5 + .../camping_supplies_RP/manifest.json | 17 + .../camping_supplies_RP/pack_icon.png | Bin 0 -> 1143 bytes .../camping_supplies_RP/texts/en_US.lang | 4 + .../textures/blocks/hammock_cloth.png | Bin 0 -> 772 bytes .../textures/blocks/tent_canvas.png | Bin 0 -> 773 bytes .../textures/item_texture.json | 12 + .../textures/items/hammock.png | Bin 0 -> 211 bytes .../textures/items/tent.png | Bin 0 -> 180 bytes .../textures/terrain_texture.json | 14 + docker-compose.yml | 8 + 20 files changed, 676 insertions(+) create mode 100644 camping-supplies-addon/camping_supplies_BP/blocks/hammock_cloth.json create mode 100644 camping-supplies-addon/camping_supplies_BP/blocks/tent_canvas.json create mode 100644 camping-supplies-addon/camping_supplies_BP/items/hammock.json create mode 100644 camping-supplies-addon/camping_supplies_BP/items/tent.json create mode 100644 camping-supplies-addon/camping_supplies_BP/manifest.json create mode 100644 camping-supplies-addon/camping_supplies_BP/pack_icon.png create mode 100644 camping-supplies-addon/camping_supplies_BP/recipes/hammock.json create mode 100644 camping-supplies-addon/camping_supplies_BP/recipes/tent.json create mode 100644 camping-supplies-addon/camping_supplies_BP/scripts/main.js create mode 100644 camping-supplies-addon/camping_supplies_RP/blocks.json create mode 100644 camping-supplies-addon/camping_supplies_RP/manifest.json create mode 100644 camping-supplies-addon/camping_supplies_RP/pack_icon.png create mode 100644 camping-supplies-addon/camping_supplies_RP/texts/en_US.lang create mode 100644 camping-supplies-addon/camping_supplies_RP/textures/blocks/hammock_cloth.png create mode 100644 camping-supplies-addon/camping_supplies_RP/textures/blocks/tent_canvas.png create mode 100644 camping-supplies-addon/camping_supplies_RP/textures/item_texture.json create mode 100644 camping-supplies-addon/camping_supplies_RP/textures/items/hammock.png create mode 100644 camping-supplies-addon/camping_supplies_RP/textures/items/tent.png create mode 100644 camping-supplies-addon/camping_supplies_RP/textures/terrain_texture.json diff --git a/camping-supplies-addon/camping_supplies_BP/blocks/hammock_cloth.json b/camping-supplies-addon/camping_supplies_BP/blocks/hammock_cloth.json new file mode 100644 index 0000000..7a11607 --- /dev/null +++ b/camping-supplies-addon/camping_supplies_BP/blocks/hammock_cloth.json @@ -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 + } + } +} diff --git a/camping-supplies-addon/camping_supplies_BP/blocks/tent_canvas.json b/camping-supplies-addon/camping_supplies_BP/blocks/tent_canvas.json new file mode 100644 index 0000000..12998ba --- /dev/null +++ b/camping-supplies-addon/camping_supplies_BP/blocks/tent_canvas.json @@ -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 + } + } +} diff --git a/camping-supplies-addon/camping_supplies_BP/items/hammock.json b/camping-supplies-addon/camping_supplies_BP/items/hammock.json new file mode 100644 index 0000000..a8d0e44 --- /dev/null +++ b/camping-supplies-addon/camping_supplies_BP/items/hammock.json @@ -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 + } + } +} diff --git a/camping-supplies-addon/camping_supplies_BP/items/tent.json b/camping-supplies-addon/camping_supplies_BP/items/tent.json new file mode 100644 index 0000000..b1351f1 --- /dev/null +++ b/camping-supplies-addon/camping_supplies_BP/items/tent.json @@ -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 + } + } +} diff --git a/camping-supplies-addon/camping_supplies_BP/manifest.json b/camping-supplies-addon/camping_supplies_BP/manifest.json new file mode 100644 index 0000000..a0eea08 --- /dev/null +++ b/camping-supplies-addon/camping_supplies_BP/manifest.json @@ -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] + } + ] +} diff --git a/camping-supplies-addon/camping_supplies_BP/pack_icon.png b/camping-supplies-addon/camping_supplies_BP/pack_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..53ec9da9233cf642cac486e36c50a0a9ebd7c915 GIT binary patch literal 1143 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrV6pRbaSW-L^LFm~Vqr&#w*6&Y zohv&JiZp66v55+3h#1wo^D?m-htAdHYJ7QLu+yo0kpxSq!DY#`q^ztg0fk8h)1OUx z)1P0y|L*tNn9292FhSM0D=V=(h!D9~WA z5MhwvV&G$Cc%E=s8Yubc#Yyk?-%UJh>vU`W|EhdkeLik~=uLKgX5LM?kKPMwGetKT z)~MM(`Tm|+zhS9$#lp#w%8bzsf=SwuDr;m9s5h{hsM(jyS;o3SLuWx!k}AjDc}x;YB|^zh7BU?BF!#)jCl3xQ9R6V3sA73hx8XbAt2RysKJmr} z0S#Rq7xQj19Y|zqaArJ^8Pt%wchdB=s|$D;{3dfc+}WslAR+7*$Ab;Z4C?ziZnECk z#v#48(ofK6a%{uB^Rj$tpHHga+<2Ku%z*V%P#i;8ec&nX3(Ua^@~$jD_6wwZ6<^-L zpjY2;NaTd`PO%FaU#C}H6WDdX=sc4Q>x{aeYDM1-em?wRR9Cb8+pQIM$MfQ3|?0Srfj_K2*chVZa(RDZD}U>i^fr-`5;>a#=EoMIe!DiPC(AzN_a& zPI#MgDjX8=aFk=*@yXmf6vVlpwwHnLYW@EA#j+FnUkN$1XfE)KV!ZMDY_JWZ+0no2 z%npcLP@K!qaMgb9oN0C}`CaM`JYvBQl4hja%iGn4NUHY&-9&yE^Pn%bNBbcX1xq0Mwd5B8a(#1EEK(C{i=A|ZxgI6OZ}Q(KG_m3 zb@rrqrUs)+&Qg#%rO&J4{5}MAygNTz<)%p-j}=oz*#7+Z^|`VajxJ4#4N^S%q}yZ~ zgVMJk2Nk<*Ta)*D?_qfJ*RLh4;M7TR%?S((3*T*?dsIYVruPG@g^NE%Zo2WlNMNh! zYWt4K(t~Rp}7q%6M&Kl$rt{|IN8cYRpaWZ_rL;$!PC{x JWt~$(69CPA=0E@d literal 0 HcmV?d00001 diff --git a/camping-supplies-addon/camping_supplies_BP/recipes/hammock.json b/camping-supplies-addon/camping_supplies_BP/recipes/hammock.json new file mode 100644 index 0000000..cbab03a --- /dev/null +++ b/camping-supplies-addon/camping_supplies_BP/recipes/hammock.json @@ -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 + } + } +} diff --git a/camping-supplies-addon/camping_supplies_BP/recipes/tent.json b/camping-supplies-addon/camping_supplies_BP/recipes/tent.json new file mode 100644 index 0000000..e257c44 --- /dev/null +++ b/camping-supplies-addon/camping_supplies_BP/recipes/tent.json @@ -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 + } + } +} diff --git a/camping-supplies-addon/camping_supplies_BP/scripts/main.js b/camping-supplies-addon/camping_supplies_BP/scripts/main.js new file mode 100644 index 0000000..a338096 --- /dev/null +++ b/camping-supplies-addon/camping_supplies_BP/scripts/main.js @@ -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."); +}); diff --git a/camping-supplies-addon/camping_supplies_RP/blocks.json b/camping-supplies-addon/camping_supplies_RP/blocks.json new file mode 100644 index 0000000..6488a1e --- /dev/null +++ b/camping-supplies-addon/camping_supplies_RP/blocks.json @@ -0,0 +1,5 @@ +{ + "format_version": [1, 1, 0], + "silverlabs:tent_canvas": { "sound": "cloth" }, + "silverlabs:hammock_cloth": { "sound": "cloth" } +} diff --git a/camping-supplies-addon/camping_supplies_RP/manifest.json b/camping-supplies-addon/camping_supplies_RP/manifest.json new file mode 100644 index 0000000..08bf333 --- /dev/null +++ b/camping-supplies-addon/camping_supplies_RP/manifest.json @@ -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] + } + ] +} diff --git a/camping-supplies-addon/camping_supplies_RP/pack_icon.png b/camping-supplies-addon/camping_supplies_RP/pack_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..53ec9da9233cf642cac486e36c50a0a9ebd7c915 GIT binary patch literal 1143 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrV6pRbaSW-L^LFm~Vqr&#w*6&Y zohv&JiZp66v55+3h#1wo^D?m-htAdHYJ7QLu+yo0kpxSq!DY#`q^ztg0fk8h)1OUx z)1P0y|L*tNn9292FhSM0D=V=(h!D9~WA z5MhwvV&G$Cc%E=s8Yubc#Yyk?-%UJh>vU`W|EhdkeLik~=uLKgX5LM?kKPMwGetKT z)~MM(`Tm|+zhS9$#lp#w%8bzsf=SwuDr;m9s5h{hsM(jyS;o3SLuWx!k}AjDc}x;YB|^zh7BU?BF!#)jCl3xQ9R6V3sA73hx8XbAt2RysKJmr} z0S#Rq7xQj19Y|zqaArJ^8Pt%wchdB=s|$D;{3dfc+}WslAR+7*$Ab;Z4C?ziZnECk z#v#48(ofK6a%{uB^Rj$tpHHga+<2Ku%z*V%P#i;8ec&nX3(Ua^@~$jD_6wwZ6<^-L zpjY2;NaTd`PO%FaU#C}H6WDdX=sc4Q>x{aeYDM1-em?wRR9Cb8+pQIM$MfQ3|?0Srfj_K2*chVZa(RDZD}U>i^fr-`5;>a#=EoMIe!DiPC(AzN_a& zPI#MgDjX8=aFk=*@yXmf6vVlpwwHnLYW@EA#j+FnUkN$1XfE)KV!ZMDY_JWZ+0no2 z%npcLP@K!qaMgb9oN0C}`CaM`JYvBQl4hja%iGn4NUHY&-9&yE^Pn%bNBbcX1xq0Mwd5B8a(#1EEK(C{i=A|ZxgI6OZ}Q(KG_m3 zb@rrqrUs)+&Qg#%rO&J4{5}MAygNTz<)%p-j}=oz*#7+Z^|`VajxJ4#4N^S%q}yZ~ zgVMJk2Nk<*Ta)*D?_qfJ*RLh4;M7TR%?S((3*T*?dsIYVruPG@g^NE%Zo2WlNMNh! zYWt4K(t~Rp}7q%6M&Kl$rt{|IN8cYRpaWZ_rL;$!PC{x JWt~$(69CPA=0E@d literal 0 HcmV?d00001 diff --git a/camping-supplies-addon/camping_supplies_RP/texts/en_US.lang b/camping-supplies-addon/camping_supplies_RP/texts/en_US.lang new file mode 100644 index 0000000..63dfe57 --- /dev/null +++ b/camping-supplies-addon/camping_supplies_RP/texts/en_US.lang @@ -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 diff --git a/camping-supplies-addon/camping_supplies_RP/textures/blocks/hammock_cloth.png b/camping-supplies-addon/camping_supplies_RP/textures/blocks/hammock_cloth.png new file mode 100644 index 0000000000000000000000000000000000000000..6c3e46eccb071fee5690a2804c5633072a5ef5d8 GIT binary patch literal 772 zcmV+f1N;1mP)TGVJzFMIKLW6w&wENkI7Q-&c8c+?)dV_ z1=Ro_Twh~k;^NV#wB>~Kn-#%V)TzUc2_F`E9bxL3CZMonr`QX}p2VOv-YJrn__;+d zLZH-FpbrHqOra}@YRQ{|2(4dhNSenu4+=4@r9>W7U>*v_D7nS(b};TE3}B56}K2A8IMq=EoHVSS1^={EEmMsVpD}I z6uLB;ECk*btkW&gd4kUx;HH7f6z_6sZNk-yztQ&aQvH_sU4DSy0uLNEhA zKK>p#z{|H9&g_n74_jng0PbyKir%LUcs{0000I4Dgrq34ot!wQFYN-lj&7o>?n91aTedDp2r*m-==WLw{QLLpNSDH- zm?iAYiQ3Fuw8V`Uyyw68Yx@U7cVmC=Co_u6lad&)npOQTEc=B$DL(>tADNJW-yFk@8M@!;#0r|bF7O)SU3^23R$=YWl1D6qHmG1#uGa}ubSr14PKuUbsOSXGl#G<6G#_9 zuTb`HF2+XQGvaN@;EoK(FZ4>|+M4FY!nYJw_{vZvd>$;Gwh{S$kLbP&hW&Ta?Fl~) zIp00PMM1X>@W&IbjM>>M?rJ8SUE%=xrlaU@tkx;QdU>Nx*67!o$G*c@noFVFHN4jef!Bf)6BdGYbwPB=GucAW4TfqxBFv0u8CI3c6#KmWHf-$;=Xv61 zoKt!cT{Mu4Jw?4jj803xukhoDp&zlX4UIjJFH^2%$+>dzbc5>loazVktz|SGjg_-; zTf|zFSWbj39YotvKRytaC3dsmo9vjMFASbdHipd3j6K<`n~;;Aa(9;4x|^sMBe%B> z#s>LjD4H8ex7=?9`qmSb2Rb()M^nd&|LO}yYRb<&V%cseEC=s#W<=8t0Z$Jh!}|iU z6v&Sf?I%1Y0kzX3yP9n%aIEiq-8KkaAMmY=sdmuAKoH!??=EqqDbHuZSz~!;^togR zUARW{S!7?})sK zgE=9V0kJc4abp(t1+yI(KNL=qvGcFo^*59}VhhFpw8nmw8j}U100000NkvXXu0mjf DyytEI literal 0 HcmV?d00001 diff --git a/camping-supplies-addon/camping_supplies_RP/textures/item_texture.json b/camping-supplies-addon/camping_supplies_RP/textures/item_texture.json new file mode 100644 index 0000000..2a790b9 --- /dev/null +++ b/camping-supplies-addon/camping_supplies_RP/textures/item_texture.json @@ -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" + } + } +} diff --git a/camping-supplies-addon/camping_supplies_RP/textures/items/hammock.png b/camping-supplies-addon/camping_supplies_RP/textures/items/hammock.png new file mode 100644 index 0000000000000000000000000000000000000000..d42ffc809b4c34e5b8781be10e9a3788e5cbde85 GIT binary patch literal 211 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`vpiiKLn`JZCrH>SoR9u)Z<#f7 z#j_^i=&Ft~#;~oG_jdj7G<UTAly^ literal 0 HcmV?d00001 diff --git a/camping-supplies-addon/camping_supplies_RP/textures/items/tent.png b/camping-supplies-addon/camping_supplies_RP/textures/items/tent.png new file mode 100644 index 0000000000000000000000000000000000000000..c81df68260db04067fadcf441f40ced4b167b470 GIT binary patch literal 180 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`)t)YnAr*6y6D0mQ9CrI@Z&#`2 zG^_N@mXA_R*BO!+{FoEA&A(OH$T(j+<1tgpZPzC*A9gVvW|)zd!!TR%%2y`4+l4!s zmUAckOHW8>XlFJ%aN@v$0|HN1eEfgs{e2Cg`}_VL3pjK1XxE|rg-`U9=P0YIx0|0_ f#2j?>F$+VSyuP9Neua-fXE1oW`njxgN@xNAqZ&ut literal 0 HcmV?d00001 diff --git a/camping-supplies-addon/camping_supplies_RP/textures/terrain_texture.json b/camping-supplies-addon/camping_supplies_RP/textures/terrain_texture.json new file mode 100644 index 0000000..736de7d --- /dev/null +++ b/camping-supplies-addon/camping_supplies_RP/textures/terrain_texture.json @@ -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" + } + } +} diff --git a/docker-compose.yml b/docker-compose.yml index e3a1760..63af563 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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: