feat(smart-crafting): add smart crafting table using private chest inventory
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:
2026-04-21 11:16:19 +01:00
parent f7aa71e9eb
commit d6bafb9d16
17 changed files with 802 additions and 0 deletions

238
scripts/build-textures.py Normal file
View File

@@ -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()