import { world } from "@minecraft/server"; // Read-only peek into the private-chest addon's world dynamic property. const PRIVATE_CHESTS_PROP = "private_chests_v1"; function loadRegistry() { try { const raw = world.getDynamicProperty(PRIVATE_CHESTS_PROP); if (raw && typeof raw === "string") return JSON.parse(raw); } catch (_) {} return {}; } // Returns [{ container, label }, ...] for every reachable private chest owned by player. // Chests in unloaded chunks or that no longer exist are silently skipped. export function readOwnedChests(player) { const registry = loadRegistry(); const sources = []; const skipped = []; for (const [key, entry] of Object.entries(registry)) { if (!entry || entry.ownerId !== player.id) continue; const parts = key.split(","); if (parts.length < 4) continue; const x = parseInt(parts[0], 10); const y = parseInt(parts[1], 10); const z = parseInt(parts[2], 10); const dimId = parts.slice(3).join(","); let dim; try { dim = world.getDimension(dimId); } catch (_) { continue; } let block; try { block = dim.getBlock({ x, y, z }); } catch (_) { block = null; } if (!block) { skipped.push({ x, y, z, dim: dimId }); continue; } if (block.typeId !== "minecraft:chest") continue; let inv; try { inv = block.getComponent("inventory"); } catch (_) { continue; } const container = inv?.container; if (!container) continue; sources.push({ container, label: `${x},${y},${z}` }); } return { sources, skipped }; } // Build a Map across the player inventory and chest containers. export function tallyItems(playerInv, chestSources) { const totals = new Map(); const addContainer = (c) => { if (!c) return; for (let i = 0; i < c.size; i++) { const item = c.getItem(i); if (!item) continue; totals.set(item.typeId, (totals.get(item.typeId) || 0) + item.amount); } }; addContainer(playerInv); for (const src of chestSources) addContainer(src.container); return totals; } // How many times can a recipe be crafted given the totals map. export function craftableCount(recipe, totals) { let min = Infinity; for (const ing of recipe.ingredients) { let available = 0; for (const typeId of ing.any) available += totals.get(typeId) || 0; const times = Math.floor(available / ing.count); if (times < min) min = times; if (min === 0) return 0; } return min === Infinity ? 0 : min; } // Remove `count` items matching any of `typeIds` from the sources in order. // Returns the number actually removed (caller should verify == count). // `sources` is an ordered list of containers: typically [playerInv, ...chestContainers]. function drain(typeIds, count, sources) { let remaining = count; for (const container of sources) { if (!container) continue; for (let i = 0; i < container.size && remaining > 0; i++) { const item = container.getItem(i); if (!item) continue; if (!typeIds.includes(item.typeId)) continue; if (item.amount <= remaining) { remaining -= item.amount; container.setItem(i, undefined); } else { const clone = item.clone(); clone.amount = item.amount - remaining; container.setItem(i, clone); remaining = 0; } } if (remaining === 0) break; } return count - remaining; } // Consume ingredients for a single craft. Returns true on success, false if anything // was short (in which case nothing has been consumed — caller should pre-verify). export function consumeIngredients(recipe, playerInv, chestSources) { const containers = [playerInv, ...chestSources.map((s) => s.container)]; for (const ing of recipe.ingredients) { const removed = drain(ing.any, ing.count, containers); if (removed < ing.count) return false; } return true; }