feat(postal): add postal service addon and bundle pending addon work
All checks were successful
Deploy Addons / deploy (push) Successful in 16s
All checks were successful
Deploy Addons / deploy (push) Successful in 16s
- New postal-service-addon: per-player mailboxes + post-office send block (ActionForm recipient picker, offline notification queue, chunk-load retry via tickingarea) - Commit previously untracked private-chest, home-sign, keep-inventory addons and their docker-compose mounts - Deploy workflow: add postal + previously unwired addons to path filter and checkout list; drop easter-egg from deployment - enabled_packs.json: register postal UUIDs for Lyla + Mya Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"format_version": "1.21.0",
|
||||
"minecraft:block": {
|
||||
"description": {
|
||||
"identifier": "silverlabs:private_chest",
|
||||
"menu_category": {
|
||||
"category": "items",
|
||||
"group": "itemGroup.name.chest"
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"minecraft:destructible_by_mining": {
|
||||
"seconds_to_destroy": 2.5
|
||||
},
|
||||
"minecraft:destructible_by_explosion": {
|
||||
"explosion_resistance": 1200.0
|
||||
},
|
||||
"minecraft:map_color": "#B0B0B0",
|
||||
"minecraft:material_instances": {
|
||||
"*": {
|
||||
"texture": "private_chest",
|
||||
"render_method": "opaque"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
34
private-chest-addon/private_chest_BP/manifest.json
Normal file
34
private-chest-addon/private_chest_BP/manifest.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"format_version": 2,
|
||||
"header": {
|
||||
"name": "Private Chests",
|
||||
"description": "Owner-locked chests — only the placer can open or break them",
|
||||
"uuid": "9a3f8d2e-7c5b-4e1a-b9d2-1f6e3c4a8b50",
|
||||
"version": [1, 0, 0],
|
||||
"min_engine_version": [1, 21, 0]
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
"type": "data",
|
||||
"uuid": "9a3f8d2e-7c5b-4e1a-b9d2-1f6e3c4a8b51",
|
||||
"version": [1, 0, 0]
|
||||
},
|
||||
{
|
||||
"type": "script",
|
||||
"language": "javascript",
|
||||
"uuid": "9a3f8d2e-7c5b-4e1a-b9d2-1f6e3c4a8b52",
|
||||
"version": [1, 0, 0],
|
||||
"entry": "scripts/main.js"
|
||||
}
|
||||
],
|
||||
"dependencies": [
|
||||
{
|
||||
"module_name": "@minecraft/server",
|
||||
"version": "1.17.0"
|
||||
},
|
||||
{
|
||||
"uuid": "9a3f8d2e-7c5b-4e1a-b9d2-1f6e3c4a8b53",
|
||||
"version": [1, 0, 1]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"format_version": "1.21.0",
|
||||
"minecraft:recipe_shapeless": {
|
||||
"description": {
|
||||
"identifier": "silverlabs:private_chest_recipe"
|
||||
},
|
||||
"tags": ["crafting_table"],
|
||||
"unlock": { "context": "AlwaysUnlocked" },
|
||||
"ingredients": [
|
||||
{ "item": "minecraft:chest" },
|
||||
{ "item": "minecraft:iron_ingot" }
|
||||
],
|
||||
"result": {
|
||||
"item": "silverlabs:private_chest",
|
||||
"count": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
158
private-chest-addon/private_chest_BP/scripts/main.js
Normal file
158
private-chest-addon/private_chest_BP/scripts/main.js
Normal file
@@ -0,0 +1,158 @@
|
||||
import { world, system, ItemStack } from "@minecraft/server";
|
||||
|
||||
// ─── Constants ──────────────────────────────────────────────
|
||||
const PRIVATE_BLOCK = "silverlabs:private_chest";
|
||||
const VANILLA_CHEST = "minecraft:chest";
|
||||
const PROP_KEY = "private_chests_v1";
|
||||
|
||||
// ─── State ──────────────────────────────────────────────────
|
||||
// In-memory mirror of dynamic property: { "x,y,z,dim": { ownerId, ownerName } }
|
||||
let chests = {};
|
||||
|
||||
function loadState() {
|
||||
try {
|
||||
const raw = world.getDynamicProperty(PROP_KEY);
|
||||
if (raw && typeof raw === "string") {
|
||||
chests = JSON.parse(raw);
|
||||
}
|
||||
} catch (e) {
|
||||
world.sendMessage(`§c[Private Chest] Failed to load state: ${e.message}`);
|
||||
chests = {};
|
||||
}
|
||||
}
|
||||
|
||||
function saveState() {
|
||||
try {
|
||||
world.setDynamicProperty(PROP_KEY, JSON.stringify(chests));
|
||||
} catch (e) {
|
||||
world.sendMessage(`§c[Private Chest] Failed to save state: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function keyOf(loc, dimensionId) {
|
||||
return `${Math.floor(loc.x)},${Math.floor(loc.y)},${Math.floor(loc.z)},${dimensionId}`;
|
||||
}
|
||||
|
||||
function claim(loc, dimensionId, player) {
|
||||
chests[keyOf(loc, dimensionId)] = { ownerId: player.id, ownerName: player.name };
|
||||
saveState();
|
||||
}
|
||||
|
||||
function release(loc, dimensionId) {
|
||||
delete chests[keyOf(loc, dimensionId)];
|
||||
saveState();
|
||||
}
|
||||
|
||||
function getOwner(loc, dimensionId) {
|
||||
return chests[keyOf(loc, dimensionId)] || null;
|
||||
}
|
||||
|
||||
// ─── Chest facing from player rotation ──────────────────────
|
||||
// Bedrock yaw: 0=south, 90=west, 180=north, -90=east. Chest front faces the player.
|
||||
function chestFacing(yaw) {
|
||||
let y = yaw;
|
||||
while (y > 180) y -= 360;
|
||||
while (y < -180) y += 360;
|
||||
if (y >= -45 && y < 45) return "north"; // player facing south
|
||||
if (y >= 45 && y < 135) return "east"; // player facing west
|
||||
if (y >= -135 && y < -45) return "west"; // player facing east
|
||||
return "south"; // player facing north
|
||||
}
|
||||
|
||||
// ─── Placement: swap custom block → vanilla chest + claim ──
|
||||
world.afterEvents.playerPlaceBlock.subscribe((event) => {
|
||||
const block = event.block;
|
||||
if (block.typeId !== PRIVATE_BLOCK) return;
|
||||
|
||||
const player = event.player;
|
||||
const loc = block.location;
|
||||
const dim = block.dimension;
|
||||
const facing = chestFacing(player.getRotation().y);
|
||||
|
||||
try {
|
||||
dim.runCommand(
|
||||
`setblock ${loc.x} ${loc.y} ${loc.z} chest ["minecraft:cardinal_direction":"${facing}"]`
|
||||
);
|
||||
} catch (e) {
|
||||
// Older block-state syntax fallback (rare)
|
||||
try {
|
||||
dim.runCommand(`setblock ${loc.x} ${loc.y} ${loc.z} chest`);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
claim(loc, dim.id, player);
|
||||
player.sendMessage(`§6[Private Chest] §7Locked to you. Only you can open or break it.`);
|
||||
});
|
||||
|
||||
// ─── Interact: block opening for non-owners ─────────────────
|
||||
try {
|
||||
world.beforeEvents.playerInteractWithBlock.subscribe((event) => {
|
||||
const block = event.block;
|
||||
if (block.typeId !== VANILLA_CHEST) return;
|
||||
const owner = getOwner(block.location, block.dimension.id);
|
||||
if (!owner) return;
|
||||
if (owner.ownerId === event.player.id) return;
|
||||
event.cancel = true;
|
||||
const playerRef = event.player;
|
||||
system.run(() =>
|
||||
playerRef.sendMessage(`§c[Private Chest] §7This chest belongs to §f${owner.ownerName}§7.`)
|
||||
);
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn(`[Private Chest] beforeEvents.playerInteractWithBlock unavailable: ${e}`);
|
||||
}
|
||||
|
||||
// ─── Break: protect for non-owners; owner break drops the custom item back ──
|
||||
try {
|
||||
world.beforeEvents.playerBreakBlock.subscribe((event) => {
|
||||
const block = event.block;
|
||||
if (block.typeId !== VANILLA_CHEST) return;
|
||||
const owner = getOwner(block.location, block.dimension.id);
|
||||
if (!owner) return;
|
||||
|
||||
const player = event.player;
|
||||
|
||||
if (owner.ownerId !== player.id) {
|
||||
event.cancel = true;
|
||||
system.run(() =>
|
||||
player.sendMessage(`§c[Private Chest] §7This chest belongs to §f${owner.ownerName}§7. You can't break it.`)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Owner breaking — cancel default drop, manually eject contents + return the custom item.
|
||||
event.cancel = true;
|
||||
const loc = { x: block.location.x, y: block.location.y, z: block.location.z };
|
||||
const dim = block.dimension;
|
||||
|
||||
system.run(() => {
|
||||
try {
|
||||
const inv = dim.getBlock(loc)?.getComponent("inventory");
|
||||
const container = inv?.container;
|
||||
const dropPos = { x: loc.x + 0.5, y: loc.y + 0.5, z: loc.z + 0.5 };
|
||||
if (container) {
|
||||
for (let i = 0; i < container.size; i++) {
|
||||
const item = container.getItem(i);
|
||||
if (item) {
|
||||
dim.spawnItem(item, dropPos);
|
||||
container.setItem(i, undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
dim.spawnItem(new ItemStack(PRIVATE_BLOCK, 1), dropPos);
|
||||
dim.runCommand(`setblock ${loc.x} ${loc.y} ${loc.z} air`);
|
||||
} catch (e) {
|
||||
player.sendMessage(`§c[Private Chest] Error during break: ${e.message}`);
|
||||
}
|
||||
release(loc, dim.id);
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn(`[Private Chest] beforeEvents.playerBreakBlock unavailable: ${e}`);
|
||||
}
|
||||
|
||||
// ─── Boot ──────────────────────────────────────────────────
|
||||
system.run(() => {
|
||||
loadState();
|
||||
world.sendMessage("§6[Private Chest] §7Owner-locked chest system loaded.");
|
||||
});
|
||||
4
private-chest-addon/private_chest_RP/blocks.json
Normal file
4
private-chest-addon/private_chest_RP/blocks.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"format_version": [1, 1, 0],
|
||||
"silverlabs:private_chest": { "sound": "stone" }
|
||||
}
|
||||
17
private-chest-addon/private_chest_RP/manifest.json
Normal file
17
private-chest-addon/private_chest_RP/manifest.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"format_version": 2,
|
||||
"header": {
|
||||
"name": "Private Chests Resources",
|
||||
"description": "Textures and lang for the silverlabs:private_chest block",
|
||||
"uuid": "9a3f8d2e-7c5b-4e1a-b9d2-1f6e3c4a8b53",
|
||||
"version": [1, 0, 0],
|
||||
"min_engine_version": [1, 21, 0]
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
"type": "resources",
|
||||
"uuid": "9a3f8d2e-7c5b-4e1a-b9d2-1f6e3c4a8b54",
|
||||
"version": [1, 0, 1]
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
private-chest-addon/private_chest_RP/pack_icon.png
Normal file
BIN
private-chest-addon/private_chest_RP/pack_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 971 B |
1
private-chest-addon/private_chest_RP/texts/en_US.lang
Normal file
1
private-chest-addon/private_chest_RP/texts/en_US.lang
Normal file
@@ -0,0 +1 @@
|
||||
tile.silverlabs:private_chest.name=Private Chest
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 317 B |
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"resource_pack_name": "private_chest_RP",
|
||||
"texture_name": "atlas.terrain",
|
||||
"padding": 8,
|
||||
"num_mip_levels": 4,
|
||||
"texture_data": {
|
||||
"private_chest": {
|
||||
"textures": "textures/blocks/private_chest"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user