#!/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) # Tent canvas — weathered green army-tent fabric CANVAS_LIGHT = (118, 138, 88) CANVAS_MID = (90, 110, 68) CANVAS_DARK = (66, 84, 50) CANVAS_SHADOW = (44, 58, 36) CANVAS_STITCH = (190, 175, 130) 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 # ── Tent Canvas ──────────────────────────────────────────── # Weathered army-canvas fabric: subtle horizontal weave + occasional darker # mottling + a vertical seam stitch line. Fully opaque so it doesn't ghost # against the alpha_test renderer. def tent_canvas() -> Image.Image: img = Image.new("RGBA", (16, 16), CANVAS_MID) # Horizontal weave bands (every 2 rows alternating tone) for y in range(16): row = CANVAS_MID if (y // 2) % 2 == 0 else CANVAS_DARK rect(img, 0, y, 15, y, row) # Diagonal weave specks for fabric texture for y in range(16): for x in range(16): if (x + y) % 5 == 0: px(img, x, y, CANVAS_LIGHT) elif (x - y) % 7 == 0: px(img, x, y, CANVAS_SHADOW) # Wear / sun-bleached patches (clusters) for (x, y) in [(2, 3), (3, 3), (10, 6), (11, 6), (5, 11), (6, 11), (13, 12)]: px(img, x, y, CANVAS_LIGHT) # Vertical seam stitch down the center for y in range(0, 16, 2): px(img, 7, y, CANVAS_STITCH) px(img, 8, y, CANVAS_SHADOW) # Edge darken (gives the panel some depth at block boundaries) rect(img, 0, 0, 15, 0, CANVAS_SHADOW) rect(img, 0, 15, 15, 15, CANVAS_SHADOW) rect(img, 0, 0, 0, 15, CANVAS_SHADOW) rect(img, 15, 0, 15, 15, CANVAS_SHADOW) return img # ── Mailbox v2 — block-style (full 16x16 face, no transparency) ─ # The previous sprite was an item-sized icon centered on transparency, which # reads as a placeholder when applied to all 6 faces of a cube. This version # fills the face: red body panel, vertical seam, slot, latch, riveted corners. def mailbox_block() -> Image.Image: img = Image.new("RGBA", (16, 16), MB_RED) # Vertical highlight strip on the left rect(img, 0, 0, 1, 15, MB_RED_HL) # Vertical shadow strip on the right rect(img, 14, 0, 15, 15, MB_RED_DARK) # Top dome highlight (1px band) rect(img, 2, 0, 13, 0, MB_RED_HL) # Bottom shadow band rect(img, 2, 15, 13, 15, MB_RED_DARK) # Horizontal mail slot (centered) rect(img, 4, 6, 11, 8, MB_BLACK) rect(img, 4, 6, 11, 6, (50, 50, 50)) # slot lip highlight rect(img, 4, 8, 11, 8, (5, 5, 5)) # slot lip shadow # Round latch below the slot rect(img, 7, 11, 8, 12, MB_BLACK) px(img, 7, 11, (90, 90, 90)) # Yellow flag emblem in the top-right corner rect(img, 12, 2, 13, 4, MB_FLAG) px(img, 12, 2, (180, 140, 40)) # Flag pole px(img, 13, 5, (60, 60, 60)) # Rivets in the corners for (x, y) in [(2, 2), (13, 2), (2, 13), (13, 13)]: px(img, x, y, (40, 15, 15)) return img # ── Post Office v2 — block-style facade ───────────────────── # A small post-office building face: brick wall, "POST" plaque, awning hint. def post_office_block() -> Image.Image: img = Image.new("RGBA", (16, 16), BRICK_RED) # Brick courses with mortar lines (offset every other row) for y in range(16): # mortar line every 4 rows if y % 4 == 0: rect(img, 0, y, 15, y, MORTAR) else: # vertical mortar joints, offset by course offset = 0 if (y // 4) % 2 == 0 else 4 for x in range(16): if (x + offset) % 8 == 0: px(img, x, y, MORTAR) else: # subtle brick tone variation if (x + y) % 3 == 0: px(img, x, y, BRICK_DARK) # Cream-colored "POST" plaque (centered) rect(img, 2, 5, 13, 10, CREAM) rect(img, 2, 5, 13, 5, ENVELOPE_LINE) # top border rect(img, 2, 10, 13, 10, ENVELOPE_LINE) # bottom border rect(img, 2, 5, 2, 10, ENVELOPE_LINE) # left border rect(img, 13, 5, 13, 10, ENVELOPE_LINE) # right border # "POST" lettering — 4 chunky chars on the plaque # P rect(img, 3, 7, 4, 8, ENVELOPE_LINE); px(img, 3, 6, ENVELOPE_LINE) # O rect(img, 6, 6, 7, 9, ENVELOPE_LINE); px(img, 6, 7, CREAM); px(img, 7, 7, CREAM); px(img, 6, 8, CREAM); px(img, 7, 8, CREAM) # S (simplified stub) rect(img, 9, 6, 10, 6, ENVELOPE_LINE); px(img, 9, 7, ENVELOPE_LINE); rect(img, 9, 8, 10, 8, ENVELOPE_LINE); px(img, 10, 9, ENVELOPE_LINE); rect(img, 9, 9, 10, 9, ENVELOPE_LINE) # T rect(img, 11, 6, 12, 6, ENVELOPE_LINE); rect(img, 11, 7, 11, 9, ENVELOPE_LINE) # Red stamp accent in the top-right rect(img, 12, 1, 14, 3, STAMP_RED) px(img, 13, 2, (255, 220, 220)) # Awning suggestion (alternating red/cream stripes at the top) for x in range(16): if (x // 2) % 2 == 0: px(img, x, 1, (210, 70, 70)) else: px(img, x, 1, CREAM) rect(img, 0, 0, 15, 0, ENVELOPE_LINE) return img # ── Portal Field ─────────────────────────────────────────── # Translucent swirling-energy texture for the walk-through portal column. # Renders with alpha blending — the dark areas read as voids, the bright # center streaks read as concentrated portal energy. def portal_field() -> Image.Image: import math img = Image.new("RGBA", (16, 16), (0, 0, 0, 0)) cx, cy = 7.5, 7.5 for y in range(16): for x in range(16): dx = x - cx dy = y - cy dist = math.sqrt(dx * dx + dy * dy) angle = math.atan2(dy, dx) # swirl: angle modulated by distance swirl = math.sin(angle * 3 + dist * 1.5) * 0.5 + 0.5 # purple/violet base r = int(80 + swirl * 100) g = int(20 + swirl * 30) b = int(140 + swirl * 90) # alpha falls off near edges, bright in middle bands edge = max(0, 1 - dist / 8.5) alpha = int(160 * edge + swirl * 60) alpha = max(0, min(220, alpha)) img.putpixel((x, y), (r, g, b, alpha)) # Bright vertical core streaks for y in range(16): for x in (7, 8): r, g, b, _ = img.getpixel((x, y)) img.putpixel((x, y), (min(255, r + 60), min(255, g + 30), min(255, b + 60), 220)) return img def main() -> None: save(smart_crafting_table(), "smart-crafting-addon/smart_crafting_RP/textures/blocks/smart_crafting_table.png") save(post_office_block(), "postal-service-addon/postal_service_RP/textures/blocks/post_office.png") save(mailbox_block(), "postal-service-addon/postal_service_RP/textures/blocks/mailbox.png") save(tent_canvas(), "camping-supplies-addon/camping_supplies_RP/textures/blocks/tent_canvas.png") save(portal_field(), "lobby-addon/lobby_transfer_RP/textures/blocks/portal_field.png") if __name__ == "__main__": main()