diff --git a/docker-compose.yml b/docker-compose.yml index 22b1f52..a265497 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -62,6 +62,8 @@ services: - ./hub-return-addon/hub_return_transfer_BP:/data/behavior_packs/hub_return_transfer_BP - ./private-chest-addon/private_chest_BP:/data/behavior_packs/private_chest_BP - ./private-chest-addon/private_chest_RP:/data/resource_packs/private_chest_RP + - ./smart-crafting-addon/smart_crafting_BP:/data/behavior_packs/smart_crafting_BP + - ./smart-crafting-addon/smart_crafting_RP:/data/resource_packs/smart_crafting_RP - ./home-sign-addon/home_sign_BP:/data/behavior_packs/home_sign_BP - ./home-sign-addon/home_sign_RP:/data/resource_packs/home_sign_RP - ./keep-inventory-addon/keep_inventory_BP:/data/behavior_packs/keep_inventory_BP @@ -100,6 +102,8 @@ services: - ./village-evolution-addon/village_evolution_BP:/data/behavior_packs/village_evolution_BP - ./private-chest-addon/private_chest_BP:/data/behavior_packs/private_chest_BP - ./private-chest-addon/private_chest_RP:/data/resource_packs/private_chest_RP + - ./smart-crafting-addon/smart_crafting_BP:/data/behavior_packs/smart_crafting_BP + - ./smart-crafting-addon/smart_crafting_RP:/data/resource_packs/smart_crafting_RP - ./home-sign-addon/home_sign_BP:/data/behavior_packs/home_sign_BP - ./home-sign-addon/home_sign_RP:/data/resource_packs/home_sign_RP - ./keep-inventory-addon/keep_inventory_BP:/data/behavior_packs/keep_inventory_BP @@ -139,6 +143,8 @@ services: - ./village-evolution-addon/village_evolution_BP:/data/behavior_packs/village_evolution_BP - ./private-chest-addon/private_chest_BP:/data/behavior_packs/private_chest_BP - ./private-chest-addon/private_chest_RP:/data/resource_packs/private_chest_RP + - ./smart-crafting-addon/smart_crafting_BP:/data/behavior_packs/smart_crafting_BP + - ./smart-crafting-addon/smart_crafting_RP:/data/resource_packs/smart_crafting_RP - ./home-sign-addon/home_sign_BP:/data/behavior_packs/home_sign_BP - ./home-sign-addon/home_sign_RP:/data/resource_packs/home_sign_RP - ./keep-inventory-addon/keep_inventory_BP:/data/behavior_packs/keep_inventory_BP diff --git a/postal-service-addon/postal_service_RP/textures/blocks/mailbox.png b/postal-service-addon/postal_service_RP/textures/blocks/mailbox.png index 6608d72..d2066ab 100644 Binary files a/postal-service-addon/postal_service_RP/textures/blocks/mailbox.png and b/postal-service-addon/postal_service_RP/textures/blocks/mailbox.png differ diff --git a/postal-service-addon/postal_service_RP/textures/blocks/post_office.png b/postal-service-addon/postal_service_RP/textures/blocks/post_office.png index 6608d72..3d5fe2d 100644 Binary files a/postal-service-addon/postal_service_RP/textures/blocks/post_office.png and b/postal-service-addon/postal_service_RP/textures/blocks/post_office.png differ diff --git a/scripts/build-textures.py b/scripts/build-textures.py new file mode 100644 index 0000000..ff4efe0 --- /dev/null +++ b/scripts/build-textures.py @@ -0,0 +1,238 @@ +#!/usr/bin/env python3 +"""Generate 16x16 block textures for smart_crafting_table, post_office, mailbox.""" + +from pathlib import Path +from PIL import Image + +ROOT = Path(__file__).resolve().parent.parent + +# ── Palette ───────────────────────────────────────────────── +OAK_LIGHT = (150, 110, 65) +OAK_DARK = (115, 80, 45) +OAK_SHADOW = (80, 55, 28) +GOLD = (218, 165, 32) +GOLD_BRIGHT = (255, 214, 85) +REDSTONE = (210, 40, 40) +REDSTONE_BRIGHT = (255, 90, 90) + +BRICK_RED = (138, 58, 48) +BRICK_DARK = (92, 38, 30) +MORTAR = (68, 52, 42) +SLATE = (58, 62, 70) +CREAM = (240, 225, 190) +ENVELOPE_LINE = (45, 30, 20) +STAMP_RED = (190, 50, 50) + +MB_RED = (175, 40, 40) +MB_RED_DARK = (115, 25, 25) +MB_RED_HL = (220, 75, 75) +MB_BLACK = (20, 20, 20) +MB_FLAG = (235, 190, 55) +MB_POST = (90, 60, 35) + + +def save(img: Image.Image, rel: str) -> None: + out = ROOT / rel + out.parent.mkdir(parents=True, exist_ok=True) + img.save(out) + print(f"wrote {rel}") + + +def px(img: Image.Image, x: int, y: int, color) -> None: + if 0 <= x < img.width and 0 <= y < img.height: + img.putpixel((x, y), color) + + +def rect(img: Image.Image, x0: int, y0: int, x1: int, y1: int, color) -> None: + for y in range(y0, y1 + 1): + for x in range(x0, x1 + 1): + px(img, x, y, color) + + +# ── Smart Crafting Table ─────────────────────────────────── +# Top-down style: oak planks, bold gold frame, 2x2 inlay with redstone + diamond. +def smart_crafting_table() -> Image.Image: + img = Image.new("RGBA", (16, 16), OAK_LIGHT) + + # Oak plank background with subtle grain + for y in range(16): + band = (y // 4) % 2 + color = OAK_LIGHT if band == 0 else OAK_DARK + rect(img, 0, y, 15, y, color) + # Knots / grain speckle + for (x, y) in [(3, 1), (11, 2), (6, 5), (13, 6), (2, 9), (9, 10), (5, 13), (12, 14)]: + px(img, x, y, OAK_SHADOW) + # Plank seams + for y in (3, 7, 11): + rect(img, 0, y, 15, y, OAK_SHADOW) + + # Bold gold frame (2px thick) + rect(img, 0, 0, 15, 1, GOLD) + rect(img, 0, 14, 15, 15, GOLD) + rect(img, 0, 0, 1, 15, GOLD) + rect(img, 14, 0, 15, 15, GOLD) + # Inner frame highlight + rect(img, 1, 1, 14, 1, GOLD_BRIGHT) + rect(img, 1, 14, 14, 14, (165, 120, 20)) + rect(img, 1, 2, 1, 13, GOLD_BRIGHT) + rect(img, 14, 2, 14, 13, (165, 120, 20)) + # Corner rivets + for (x, y) in [(0, 0), (15, 0), (0, 15), (15, 15)]: + px(img, x, y, (90, 65, 10)) + + # 2x2 inset panels with gold dividers at mid (x=7..8, y=7..8) + # Top-left = redstone, top-right = diamond, bottom-left = emerald, bottom-right = gold ingot + TL = (3, 3, 6, 6) # redstone area + TR = (9, 3, 12, 6) # diamond area + BL = (3, 9, 6, 12) # emerald area + BR = (9, 9, 12, 12) # gold area + + # Dark recessed wells + for (x0, y0, x1, y1) in (TL, TR, BL, BR): + rect(img, x0, y0, x1, y1, (40, 25, 12)) + + # Redstone gem (red) + rect(img, 4, 4, 5, 5, REDSTONE) + px(img, 4, 4, REDSTONE_BRIGHT) + + # Diamond (cyan) + rect(img, 10, 4, 11, 5, (120, 220, 235)) + px(img, 10, 4, (200, 250, 255)) + + # Emerald (green) + rect(img, 4, 10, 5, 11, (50, 170, 90)) + px(img, 4, 10, (120, 220, 150)) + + # Gold nugget + rect(img, 10, 10, 11, 11, GOLD) + px(img, 10, 10, GOLD_BRIGHT) + + # Gold cross dividers + rect(img, 7, 2, 8, 13, GOLD) + rect(img, 2, 7, 13, 8, GOLD) + # Center jewel + px(img, 7, 7, REDSTONE_BRIGHT) + px(img, 8, 7, REDSTONE_BRIGHT) + px(img, 7, 8, REDSTONE) + px(img, 8, 8, REDSTONE) + + return img + + +# ── Post Office ──────────────────────────────────────────── +# Big envelope front: cream background, bold dark fold lines, red stamp, "POST" bar. +def post_office() -> Image.Image: + img = Image.new("RGBA", (16, 16), CREAM) + + # Outer envelope border (1px) + rect(img, 0, 0, 15, 0, ENVELOPE_LINE) + rect(img, 0, 15, 15, 15, ENVELOPE_LINE) + rect(img, 0, 0, 0, 15, ENVELOPE_LINE) + rect(img, 15, 0, 15, 15, ENVELOPE_LINE) + + # Paper shade (slight gradient on bottom half) + for y in range(8, 15): + for x in range(1, 15): + if (x + y) % 3 == 0: + px(img, x, y, (225, 210, 175)) + + # Envelope fold: top-left and top-right diagonals meeting at center-top + # Lines go from (1,1) -> (7,7) and (14,1) -> (8,7) + for i in range(7): + px(img, 1 + i, 1 + i, ENVELOPE_LINE) + px(img, 14 - i, 1 + i, ENVELOPE_LINE) + # Second pass for thickness + for i in range(6): + px(img, 2 + i, 1 + i, (90, 70, 50)) + px(img, 13 - i, 1 + i, (90, 70, 50)) + + # Horizontal seam at y=8 (where flaps meet paper) + rect(img, 1, 8, 14, 8, ENVELOPE_LINE) + + # Red stamp: top-right square (3x3) with crosshatch + rect(img, 11, 2, 13, 4, STAMP_RED) + px(img, 12, 3, (255, 220, 220)) # stamp center highlight + # Stamp border + px(img, 10, 2, ENVELOPE_LINE) + px(img, 10, 3, ENVELOPE_LINE) + px(img, 10, 4, ENVELOPE_LINE) + px(img, 11, 5, ENVELOPE_LINE) + px(img, 12, 5, ENVELOPE_LINE) + px(img, 13, 5, ENVELOPE_LINE) + + # Address bar: dark rectangle bottom-center reading like lines of text + rect(img, 3, 11, 12, 12, (120, 100, 75)) + rect(img, 3, 13, 10, 13, (120, 100, 75)) + + # "POST" mark: a small red circle stamp bottom-right + rect(img, 11, 11, 13, 13, STAMP_RED) + px(img, 11, 11, (0, 0, 0, 0)) + px(img, 13, 11, (0, 0, 0, 0)) + px(img, 11, 13, (0, 0, 0, 0)) + px(img, 13, 13, (0, 0, 0, 0)) + px(img, 12, 12, (255, 230, 230)) + + return img + + +# ── Mailbox ─────────────────────────────────────────────── +# Classic red pillar-mailbox front: domed top, slot, base, flag. +def mailbox() -> Image.Image: + img = Image.new("RGBA", (16, 16), (0, 0, 0, 0)) + + # Post base (wooden) across bottom + rect(img, 6, 14, 9, 15, MB_POST) + px(img, 6, 14, (70, 45, 25)) + px(img, 9, 14, (70, 45, 25)) + + # Mailbox body (rounded: rows 2..13) + # Body rectangle x=2..13, y=4..13 + rect(img, 2, 4, 13, 13, MB_RED) + # Rounded top corners + px(img, 2, 4, (0, 0, 0, 0)) + px(img, 13, 4, (0, 0, 0, 0)) + # Add a dome/top-bump + rect(img, 3, 2, 12, 3, MB_RED) + px(img, 3, 2, (0, 0, 0, 0)) + px(img, 12, 2, (0, 0, 0, 0)) + rect(img, 4, 1, 11, 1, MB_RED) + rect(img, 5, 0, 10, 0, MB_RED_DARK) + + # Left-side highlight (lighter vertical stripe) + rect(img, 3, 5, 3, 12, MB_RED_HL) + px(img, 4, 2, MB_RED_HL) + + # Right-side shadow + rect(img, 12, 5, 12, 12, MB_RED_DARK) + rect(img, 11, 13, 13, 13, MB_RED_DARK) + + # Black letter slot (horizontal bar) + rect(img, 5, 6, 10, 7, MB_BLACK) + # Slot highlight + rect(img, 5, 6, 10, 6, (50, 50, 50)) + + # Circular latch below slot + px(img, 7, 10, MB_BLACK) + px(img, 8, 10, MB_BLACK) + px(img, 7, 11, MB_BLACK) + px(img, 8, 11, MB_BLACK) + px(img, 7, 10, (90, 90, 90)) + + # Yellow flag on the right side + rect(img, 14, 3, 15, 6, MB_FLAG) + px(img, 14, 3, (180, 140, 40)) + px(img, 15, 6, (180, 140, 40)) + # Flag pole + rect(img, 14, 7, 14, 10, (60, 60, 60)) + + return img + + +def main() -> None: + save(smart_crafting_table(), "smart-crafting-addon/smart_crafting_RP/textures/blocks/smart_crafting_table.png") + save(post_office(), "postal-service-addon/postal_service_RP/textures/blocks/post_office.png") + save(mailbox(), "postal-service-addon/postal_service_RP/textures/blocks/mailbox.png") + + +if __name__ == "__main__": + main() diff --git a/smart-crafting-addon/smart_crafting_BP/blocks/smart_crafting_table.json b/smart-crafting-addon/smart_crafting_BP/blocks/smart_crafting_table.json new file mode 100644 index 0000000..448bcea --- /dev/null +++ b/smart-crafting-addon/smart_crafting_BP/blocks/smart_crafting_table.json @@ -0,0 +1,27 @@ +{ + "format_version": "1.21.0", + "minecraft:block": { + "description": { + "identifier": "silverlabs:smart_crafting_table", + "menu_category": { + "category": "items", + "group": "itemGroup.name.crafting" + } + }, + "components": { + "minecraft:destructible_by_mining": { + "seconds_to_destroy": 2.5 + }, + "minecraft:destructible_by_explosion": { + "explosion_resistance": 20.0 + }, + "minecraft:map_color": "#D4AF37", + "minecraft:material_instances": { + "*": { + "texture": "smart_crafting_table", + "render_method": "opaque" + } + } + } + } +} diff --git a/smart-crafting-addon/smart_crafting_BP/manifest.json b/smart-crafting-addon/smart_crafting_BP/manifest.json new file mode 100644 index 0000000..2daa48a --- /dev/null +++ b/smart-crafting-addon/smart_crafting_BP/manifest.json @@ -0,0 +1,38 @@ +{ + "format_version": 2, + "header": { + "name": "Smart Crafting Table", + "description": "Upgraded crafting table that uses items stored in your private chests", + "uuid": "a4c2e1f8-3b7d-4f9e-a1c5-8d2e7b4f3a91", + "version": [1, 0, 0], + "min_engine_version": [1, 21, 0] + }, + "modules": [ + { + "type": "data", + "uuid": "a4c2e1f8-3b7d-4f9e-a1c5-8d2e7b4f3a92", + "version": [1, 0, 0] + }, + { + "type": "script", + "language": "javascript", + "uuid": "a4c2e1f8-3b7d-4f9e-a1c5-8d2e7b4f3a93", + "version": [1, 0, 0], + "entry": "scripts/main.js" + } + ], + "dependencies": [ + { + "module_name": "@minecraft/server", + "version": "1.17.0" + }, + { + "module_name": "@minecraft/server-ui", + "version": "1.3.0" + }, + { + "uuid": "a4c2e1f8-3b7d-4f9e-a1c5-8d2e7b4f3a94", + "version": [1, 0, 0] + } + ] +} diff --git a/smart-crafting-addon/smart_crafting_BP/pack_icon.png b/smart-crafting-addon/smart_crafting_BP/pack_icon.png new file mode 100644 index 0000000..a507642 Binary files /dev/null and b/smart-crafting-addon/smart_crafting_BP/pack_icon.png differ diff --git a/smart-crafting-addon/smart_crafting_BP/recipes/smart_crafting_table.json b/smart-crafting-addon/smart_crafting_BP/recipes/smart_crafting_table.json new file mode 100644 index 0000000..edfa62c --- /dev/null +++ b/smart-crafting-addon/smart_crafting_BP/recipes/smart_crafting_table.json @@ -0,0 +1,19 @@ +{ + "format_version": "1.21.0", + "minecraft:recipe_shapeless": { + "description": { + "identifier": "silverlabs:smart_crafting_table_recipe" + }, + "tags": ["crafting_table"], + "unlock": { "context": "AlwaysUnlocked" }, + "ingredients": [ + { "item": "minecraft:crafting_table" }, + { "item": "minecraft:gold_ingot" }, + { "item": "minecraft:redstone" } + ], + "result": { + "item": "silverlabs:smart_crafting_table", + "count": 1 + } + } +} diff --git a/smart-crafting-addon/smart_crafting_BP/scripts/chest-scan.js b/smart-crafting-addon/smart_crafting_BP/scripts/chest-scan.js new file mode 100644 index 0000000..3e89019 --- /dev/null +++ b/smart-crafting-addon/smart_crafting_BP/scripts/chest-scan.js @@ -0,0 +1,116 @@ +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; +} diff --git a/smart-crafting-addon/smart_crafting_BP/scripts/main.js b/smart-crafting-addon/smart_crafting_BP/scripts/main.js new file mode 100644 index 0000000..395ec50 --- /dev/null +++ b/smart-crafting-addon/smart_crafting_BP/scripts/main.js @@ -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."); +}); diff --git a/smart-crafting-addon/smart_crafting_BP/scripts/recipes.js b/smart-crafting-addon/smart_crafting_BP/scripts/recipes.js new file mode 100644 index 0000000..8129301 --- /dev/null +++ b/smart-crafting-addon/smart_crafting_BP/scripts/recipes.js @@ -0,0 +1,215 @@ +// Recipe catalogue for the Smart Crafting Table. +// Each recipe lists ingredients as { any: [...itemIds], count }. The "any" variant +// lets a single slot accept any matching wood-type etc. + +const PLANKS = [ + "minecraft:oak_planks", + "minecraft:spruce_planks", + "minecraft:birch_planks", + "minecraft:jungle_planks", + "minecraft:acacia_planks", + "minecraft:dark_oak_planks", + "minecraft:mangrove_planks", + "minecraft:cherry_planks", + "minecraft:bamboo_planks", + "minecraft:crimson_planks", + "minecraft:warped_planks", +]; + +const LOGS = [ + "minecraft:oak_log", + "minecraft:spruce_log", + "minecraft:birch_log", + "minecraft:jungle_log", + "minecraft:acacia_log", + "minecraft:dark_oak_log", + "minecraft:mangrove_log", + "minecraft:cherry_log", + "minecraft:crimson_stem", + "minecraft:warped_stem", +]; + +const WOOL = [ + "minecraft:white_wool", + "minecraft:orange_wool", + "minecraft:magenta_wool", + "minecraft:light_blue_wool", + "minecraft:yellow_wool", + "minecraft:lime_wool", + "minecraft:pink_wool", + "minecraft:gray_wool", + "minecraft:light_gray_wool", + "minecraft:cyan_wool", + "minecraft:purple_wool", + "minecraft:blue_wool", + "minecraft:brown_wool", + "minecraft:green_wool", + "minecraft:red_wool", + "minecraft:black_wool", +]; + +const STICK = ["minecraft:stick"]; +const COBBLE = ["minecraft:cobblestone"]; +const IRON = ["minecraft:iron_ingot"]; +const GOLD = ["minecraft:gold_ingot"]; +const DIAMOND = ["minecraft:diamond"]; + +export const RECIPES = [ + // ── Basics ───────────────────────────────────────────────── + { id: "stick", name: "Stick", + result: { item: "minecraft:stick", count: 4 }, + ingredients: [{ any: PLANKS, count: 2 }] }, + + { id: "planks", name: "Wooden Planks (oak)", + result: { item: "minecraft:oak_planks", count: 4 }, + ingredients: [{ any: ["minecraft:oak_log"], count: 1 }] }, + + { id: "crafting_table", name: "Crafting Table", + result: { item: "minecraft:crafting_table", count: 1 }, + ingredients: [{ any: PLANKS, count: 4 }] }, + + { id: "chest", name: "Chest", + result: { item: "minecraft:chest", count: 1 }, + ingredients: [{ any: PLANKS, count: 8 }] }, + + { id: "furnace", name: "Furnace", + result: { item: "minecraft:furnace", count: 1 }, + ingredients: [{ any: COBBLE, count: 8 }] }, + + { id: "torch", name: "Torch", + result: { item: "minecraft:torch", count: 4 }, + ingredients: [ + { any: ["minecraft:coal", "minecraft:charcoal"], count: 1 }, + { any: STICK, count: 1 }, + ] }, + + { id: "ladder", name: "Ladder", + result: { item: "minecraft:ladder", count: 3 }, + ingredients: [{ any: STICK, count: 7 }] }, + + { id: "bucket", name: "Bucket", + result: { item: "minecraft:bucket", count: 1 }, + ingredients: [{ any: IRON, count: 3 }] }, + + { id: "bed", name: "Bed (red)", + result: { item: "minecraft:red_bed", count: 1 }, + ingredients: [ + { any: WOOL, count: 3 }, + { any: PLANKS, count: 3 }, + ] }, + + // ── Tools: wood ──────────────────────────────────────────── + { id: "wood_pickaxe", name: "Wooden Pickaxe", + result: { item: "minecraft:wooden_pickaxe", count: 1 }, + ingredients: [{ any: PLANKS, count: 3 }, { any: STICK, count: 2 }] }, + { id: "wood_axe", name: "Wooden Axe", + result: { item: "minecraft:wooden_axe", count: 1 }, + ingredients: [{ any: PLANKS, count: 3 }, { any: STICK, count: 2 }] }, + { id: "wood_shovel", name: "Wooden Shovel", + result: { item: "minecraft:wooden_shovel", count: 1 }, + ingredients: [{ any: PLANKS, count: 1 }, { any: STICK, count: 2 }] }, + { id: "wood_sword", name: "Wooden Sword", + result: { item: "minecraft:wooden_sword", count: 1 }, + ingredients: [{ any: PLANKS, count: 2 }, { any: STICK, count: 1 }] }, + { id: "wood_hoe", name: "Wooden Hoe", + result: { item: "minecraft:wooden_hoe", count: 1 }, + ingredients: [{ any: PLANKS, count: 2 }, { any: STICK, count: 2 }] }, + + // ── Tools: stone ─────────────────────────────────────────── + { id: "stone_pickaxe", name: "Stone Pickaxe", + result: { item: "minecraft:stone_pickaxe", count: 1 }, + ingredients: [{ any: COBBLE, count: 3 }, { any: STICK, count: 2 }] }, + { id: "stone_axe", name: "Stone Axe", + result: { item: "minecraft:stone_axe", count: 1 }, + ingredients: [{ any: COBBLE, count: 3 }, { any: STICK, count: 2 }] }, + { id: "stone_shovel", name: "Stone Shovel", + result: { item: "minecraft:stone_shovel", count: 1 }, + ingredients: [{ any: COBBLE, count: 1 }, { any: STICK, count: 2 }] }, + { id: "stone_sword", name: "Stone Sword", + result: { item: "minecraft:stone_sword", count: 1 }, + ingredients: [{ any: COBBLE, count: 2 }, { any: STICK, count: 1 }] }, + { id: "stone_hoe", name: "Stone Hoe", + result: { item: "minecraft:stone_hoe", count: 1 }, + ingredients: [{ any: COBBLE, count: 2 }, { any: STICK, count: 2 }] }, + + // ── Tools: iron ──────────────────────────────────────────── + { id: "iron_pickaxe", name: "Iron Pickaxe", + result: { item: "minecraft:iron_pickaxe", count: 1 }, + ingredients: [{ any: IRON, count: 3 }, { any: STICK, count: 2 }] }, + { id: "iron_axe", name: "Iron Axe", + result: { item: "minecraft:iron_axe", count: 1 }, + ingredients: [{ any: IRON, count: 3 }, { any: STICK, count: 2 }] }, + { id: "iron_shovel", name: "Iron Shovel", + result: { item: "minecraft:iron_shovel", count: 1 }, + ingredients: [{ any: IRON, count: 1 }, { any: STICK, count: 2 }] }, + { id: "iron_sword", name: "Iron Sword", + result: { item: "minecraft:iron_sword", count: 1 }, + ingredients: [{ any: IRON, count: 2 }, { any: STICK, count: 1 }] }, + { id: "iron_hoe", name: "Iron Hoe", + result: { item: "minecraft:iron_hoe", count: 1 }, + ingredients: [{ any: IRON, count: 2 }, { any: STICK, count: 2 }] }, + + // ── Tools: gold ──────────────────────────────────────────── + { id: "gold_pickaxe", name: "Golden Pickaxe", + result: { item: "minecraft:golden_pickaxe", count: 1 }, + ingredients: [{ any: GOLD, count: 3 }, { any: STICK, count: 2 }] }, + { id: "gold_sword", name: "Golden Sword", + result: { item: "minecraft:golden_sword", count: 1 }, + ingredients: [{ any: GOLD, count: 2 }, { any: STICK, count: 1 }] }, + + // ── Tools: diamond ───────────────────────────────────────── + { id: "diamond_pickaxe", name: "Diamond Pickaxe", + result: { item: "minecraft:diamond_pickaxe", count: 1 }, + ingredients: [{ any: DIAMOND, count: 3 }, { any: STICK, count: 2 }] }, + { id: "diamond_axe", name: "Diamond Axe", + result: { item: "minecraft:diamond_axe", count: 1 }, + ingredients: [{ any: DIAMOND, count: 3 }, { any: STICK, count: 2 }] }, + { id: "diamond_shovel", name: "Diamond Shovel", + result: { item: "minecraft:diamond_shovel", count: 1 }, + ingredients: [{ any: DIAMOND, count: 1 }, { any: STICK, count: 2 }] }, + { id: "diamond_sword", name: "Diamond Sword", + result: { item: "minecraft:diamond_sword", count: 1 }, + ingredients: [{ any: DIAMOND, count: 2 }, { any: STICK, count: 1 }] }, + + // ── Armor: iron ──────────────────────────────────────────── + { id: "iron_helmet", name: "Iron Helmet", + result: { item: "minecraft:iron_helmet", count: 1 }, + ingredients: [{ any: IRON, count: 5 }] }, + { id: "iron_chestplate", name: "Iron Chestplate", + result: { item: "minecraft:iron_chestplate", count: 1 }, + ingredients: [{ any: IRON, count: 8 }] }, + { id: "iron_leggings", name: "Iron Leggings", + result: { item: "minecraft:iron_leggings", count: 1 }, + ingredients: [{ any: IRON, count: 7 }] }, + { id: "iron_boots", name: "Iron Boots", + result: { item: "minecraft:iron_boots", count: 1 }, + ingredients: [{ any: IRON, count: 4 }] }, + + // ── Armor: diamond ───────────────────────────────────────── + { id: "diamond_helmet", name: "Diamond Helmet", + result: { item: "minecraft:diamond_helmet", count: 1 }, + ingredients: [{ any: DIAMOND, count: 5 }] }, + { id: "diamond_chestplate", name: "Diamond Chestplate", + result: { item: "minecraft:diamond_chestplate", count: 1 }, + ingredients: [{ any: DIAMOND, count: 8 }] }, + { id: "diamond_leggings", name: "Diamond Leggings", + result: { item: "minecraft:diamond_leggings", count: 1 }, + ingredients: [{ any: DIAMOND, count: 7 }] }, + { id: "diamond_boots", name: "Diamond Boots", + result: { item: "minecraft:diamond_boots", count: 1 }, + ingredients: [{ any: DIAMOND, count: 4 }] }, + + // ── Utility ──────────────────────────────────────────────── + { id: "shears", name: "Shears", + result: { item: "minecraft:shears", count: 1 }, + ingredients: [{ any: IRON, count: 2 }] }, + { id: "flint_and_steel", name: "Flint and Steel", + result: { item: "minecraft:flint_and_steel", count: 1 }, + ingredients: [{ any: IRON, count: 1 }, { any: ["minecraft:flint"], count: 1 }] }, + { id: "compass", name: "Compass", + result: { item: "minecraft:compass", count: 1 }, + ingredients: [{ any: IRON, count: 4 }, { any: ["minecraft:redstone"], count: 1 }] }, + { id: "clock", name: "Clock", + result: { item: "minecraft:clock", count: 1 }, + ingredients: [{ any: GOLD, count: 4 }, { any: ["minecraft:redstone"], count: 1 }] }, +]; diff --git a/smart-crafting-addon/smart_crafting_RP/blocks.json b/smart-crafting-addon/smart_crafting_RP/blocks.json new file mode 100644 index 0000000..b904abd --- /dev/null +++ b/smart-crafting-addon/smart_crafting_RP/blocks.json @@ -0,0 +1,4 @@ +{ + "format_version": [1, 1, 0], + "silverlabs:smart_crafting_table": { "sound": "wood" } +} diff --git a/smart-crafting-addon/smart_crafting_RP/manifest.json b/smart-crafting-addon/smart_crafting_RP/manifest.json new file mode 100644 index 0000000..47e02e2 --- /dev/null +++ b/smart-crafting-addon/smart_crafting_RP/manifest.json @@ -0,0 +1,17 @@ +{ + "format_version": 2, + "header": { + "name": "Smart Crafting Table Resources", + "description": "Texture and lang for the silverlabs:smart_crafting_table block", + "uuid": "a4c2e1f8-3b7d-4f9e-a1c5-8d2e7b4f3a94", + "version": [1, 0, 0], + "min_engine_version": [1, 21, 0] + }, + "modules": [ + { + "type": "resources", + "uuid": "a4c2e1f8-3b7d-4f9e-a1c5-8d2e7b4f3a95", + "version": [1, 0, 0] + } + ] +} diff --git a/smart-crafting-addon/smart_crafting_RP/pack_icon.png b/smart-crafting-addon/smart_crafting_RP/pack_icon.png new file mode 100644 index 0000000..a507642 Binary files /dev/null and b/smart-crafting-addon/smart_crafting_RP/pack_icon.png differ diff --git a/smart-crafting-addon/smart_crafting_RP/texts/en_US.lang b/smart-crafting-addon/smart_crafting_RP/texts/en_US.lang new file mode 100644 index 0000000..f874d3a --- /dev/null +++ b/smart-crafting-addon/smart_crafting_RP/texts/en_US.lang @@ -0,0 +1 @@ +tile.silverlabs:smart_crafting_table.name=Smart Crafting Table diff --git a/smart-crafting-addon/smart_crafting_RP/textures/blocks/smart_crafting_table.png b/smart-crafting-addon/smart_crafting_RP/textures/blocks/smart_crafting_table.png new file mode 100644 index 0000000..0bb9084 Binary files /dev/null and b/smart-crafting-addon/smart_crafting_RP/textures/blocks/smart_crafting_table.png differ diff --git a/smart-crafting-addon/smart_crafting_RP/textures/terrain_texture.json b/smart-crafting-addon/smart_crafting_RP/textures/terrain_texture.json new file mode 100644 index 0000000..0e15db4 --- /dev/null +++ b/smart-crafting-addon/smart_crafting_RP/textures/terrain_texture.json @@ -0,0 +1,11 @@ +{ + "resource_pack_name": "smart_crafting_RP", + "texture_name": "atlas.terrain", + "padding": 8, + "num_mip_levels": 4, + "texture_data": { + "smart_crafting_table": { + "textures": "textures/blocks/smart_crafting_table" + } + } +}