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."); });