feat(smart-crafting): add smart crafting table using private chest inventory
All checks were successful
Deploy Addons / deploy (push) Successful in 14s
All checks were successful
Deploy Addons / deploy (push) Successful in 14s
Adds an upgraded crafting block that scans the player's owned private chests and aggregates their contents with the personal inventory when deciding which recipes are craftable. Ingredients are consumed from the player first then from chests; the result goes to the player (or drops at their feet). Also redraws the post_office and mailbox block textures via a new scripts/build-textures.py generator. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
110
smart-crafting-addon/smart_crafting_BP/scripts/main.js
Normal file
110
smart-crafting-addon/smart_crafting_BP/scripts/main.js
Normal file
@@ -0,0 +1,110 @@
|
||||
import { world, system, ItemStack } from "@minecraft/server";
|
||||
import { ActionFormData } from "@minecraft/server-ui";
|
||||
import { RECIPES } from "./recipes.js";
|
||||
import { readOwnedChests, tallyItems, craftableCount, consumeIngredients } from "./chest-scan.js";
|
||||
|
||||
const SMART_TABLE = "silverlabs:smart_crafting_table";
|
||||
|
||||
function getPlayerInventory(player) {
|
||||
try { return player.getComponent("inventory")?.container ?? null; }
|
||||
catch (_) { return null; }
|
||||
}
|
||||
|
||||
function giveOrDrop(player, itemStack) {
|
||||
const inv = getPlayerInventory(player);
|
||||
if (inv) {
|
||||
const leftover = inv.addItem(itemStack);
|
||||
if (!leftover) return;
|
||||
// inventory full — drop whatever didn't fit at player's feet
|
||||
try { player.dimension.spawnItem(leftover, player.location); } catch (_) {}
|
||||
return;
|
||||
}
|
||||
try { player.dimension.spawnItem(itemStack, player.location); } catch (_) {}
|
||||
}
|
||||
|
||||
async function openCraftingUI(player) {
|
||||
const playerInv = getPlayerInventory(player);
|
||||
if (!playerInv) {
|
||||
player.sendMessage(`§c[Smart Crafting] §7Couldn't access your inventory.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const { sources, skipped } = readOwnedChests(player);
|
||||
const totals = tallyItems(playerInv, sources);
|
||||
|
||||
const candidates = [];
|
||||
for (const recipe of RECIPES) {
|
||||
const count = craftableCount(recipe, totals);
|
||||
if (count > 0) candidates.push({ recipe, count });
|
||||
}
|
||||
|
||||
const bodyLines = [
|
||||
`§7Scanned §f${sources.length}§7 of your chests.`,
|
||||
];
|
||||
if (skipped.length > 0) bodyLines.push(`§8(${skipped.length} chest${skipped.length === 1 ? "" : "s"} unreachable — chunk not loaded)`);
|
||||
if (candidates.length === 0) {
|
||||
bodyLines.push("", "§cNo recipes available with your current items.");
|
||||
} else {
|
||||
bodyLines.push(`§7§o${candidates.length} recipe${candidates.length === 1 ? "" : "s"} craftable.`);
|
||||
}
|
||||
|
||||
const form = new ActionFormData()
|
||||
.title("§6Smart Crafting")
|
||||
.body(bodyLines.join("\n"));
|
||||
|
||||
for (const c of candidates) {
|
||||
form.button(`${c.recipe.name} §7(x${c.count})`);
|
||||
}
|
||||
form.button("§cClose");
|
||||
|
||||
let response;
|
||||
try { response = await form.show(player); }
|
||||
catch (_) { return; }
|
||||
if (response.canceled || response.selection === undefined) return;
|
||||
if (response.selection >= candidates.length) return; // Close button
|
||||
|
||||
const chosen = candidates[response.selection].recipe;
|
||||
|
||||
// Re-scan to ensure items didn't change while the form was open.
|
||||
const freshInv = getPlayerInventory(player);
|
||||
if (!freshInv) { player.sendMessage(`§c[Smart Crafting] §7Inventory became unavailable.`); return; }
|
||||
const fresh = readOwnedChests(player);
|
||||
const freshTotals = tallyItems(freshInv, fresh.sources);
|
||||
if (craftableCount(chosen, freshTotals) < 1) {
|
||||
player.sendMessage(`§c[Smart Crafting] §7Ingredients for §f${chosen.name}§7 are no longer available.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const ok = consumeIngredients(chosen, freshInv, fresh.sources);
|
||||
if (!ok) {
|
||||
player.sendMessage(`§c[Smart Crafting] §7Something went wrong consuming ingredients.`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = new ItemStack(chosen.result.item, chosen.result.count);
|
||||
giveOrDrop(player, result);
|
||||
} catch (e) {
|
||||
player.sendMessage(`§c[Smart Crafting] §7Couldn't create result item: ${e.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
player.sendMessage(`§6[Smart Crafting] §7Crafted §f${chosen.name}§7.`);
|
||||
}
|
||||
|
||||
// ─── Interact: open the crafting UI ─────────────────────────
|
||||
try {
|
||||
world.beforeEvents.playerInteractWithBlock.subscribe((event) => {
|
||||
const block = event.block;
|
||||
if (!block || block.typeId !== SMART_TABLE) return;
|
||||
event.cancel = true;
|
||||
const playerRef = event.player;
|
||||
system.run(() => openCraftingUI(playerRef));
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn(`[Smart Crafting] beforeEvents.playerInteractWithBlock unavailable: ${e}`);
|
||||
}
|
||||
|
||||
system.run(() => {
|
||||
world.sendMessage("§6[Smart Crafting] §7Smart crafting table loaded.");
|
||||
});
|
||||
Reference in New Issue
Block a user