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>
239 lines
7.5 KiB
Python
239 lines
7.5 KiB
Python
#!/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()
|