Files
minecraft-aiworld/scripts/build-textures.py
T
sysadminandClaude Opus 5 50af20981f
Deploy Addons / deploy (push) Failing after 0s
fix(lobby): ship real portal textures; they pointed at vanilla paths
The four portal blocks referenced vanilla texture paths from their own pack
(emerald_block, amethyst_block, prismarine_bricks, end_portal_frame_top)
without shipping those files. While the blocks rendered through the legacy
blocks.json path this was invisible; once minecraft:geometry made
material_instances take effect the lookup failed outright and they rendered
as missing-texture.

Ships real 16x16 textures generated through scripts/build-textures.py: a dark
stone frame around a glowing field, one hue per destination -- jamie green,
lyla purple, mya teal, plus a gold-ringed frame. terrain_texture.json now
points at these instead of vanilla paths, so every key in the pack resolves
to a file the pack actually contains.

lobby_transfer_RP bumped to 1.0.1 so clients re-download rather than serving
cached art; the lobby world pin was updated to match.

Known issue, not fixed here: lobby_transfer_RP and hub_return_transfer_BP
share pack UUID b2c3d4e5-1111-2222-3333-fedcba654321. They never load on the
same server today, so the collision is latent, but it makes version bumps on
either pack risky and should be resolved with a fresh UUID plus a pin update.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Gf1kZypRDfPL1YiWUS1LC
2026-08-28 12:49:46 +01:00

553 lines
20 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)
# 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
# ── Redstone Link transmitter / receiver ───────────────────
# Machine faces, not icons. Following the mailbox lesson: the motif has to
# reach the edges or a cube of it reads as a placeholder. Both share a gunmetal
# plate with corner rivets and a beveled frame; the transmitter carries redstone
# traces radiating OUT to all four edges, the receiver carries traces running IN
# to a collector dish. Same silhouette, mirrored behaviour, different accent.
LINK_PLATE = (62, 66, 74)
LINK_PLATE_HL = (92, 97, 107)
LINK_PLATE_SH = (38, 41, 47)
LINK_RIVET = (140, 146, 158)
TX_TRACE = (168, 30, 28)
TX_TRACE_HOT = REDSTONE_BRIGHT
TX_CORE = (255, 140, 110)
RX_TRACE = (34, 96, 150)
RX_TRACE_HOT = (92, 178, 240)
RX_CORE = (200, 240, 255)
def _link_plate() -> Image.Image:
"""Shared gunmetal base: beveled frame + corner rivets, fully opaque."""
img = Image.new("RGBA", (16, 16), LINK_PLATE)
# Bevel: lit top/left, shadowed bottom/right
rect(img, 0, 0, 15, 0, LINK_PLATE_HL)
rect(img, 0, 0, 0, 15, LINK_PLATE_HL)
rect(img, 0, 15, 15, 15, LINK_PLATE_SH)
rect(img, 15, 0, 15, 15, LINK_PLATE_SH)
# Corner rivets
for cx, cy in ((2, 2), (13, 2), (2, 13), (13, 13)):
px(img, cx, cy, LINK_RIVET)
px(img, cx, cy + 1, LINK_PLATE_SH)
return img
def redstone_link_tx() -> Image.Image:
img = _link_plate()
# Traces running from the core out to all four edges: this is what makes a
# cube of it read as wiring rather than a centred sprite.
rect(img, 7, 0, 8, 15, TX_TRACE)
rect(img, 0, 7, 15, 8, TX_TRACE)
# Diagonal spurs toward the corners
for i in range(3, 7):
px(img, i, i, TX_TRACE)
px(img, 15 - i, i, TX_TRACE)
px(img, i, 15 - i, TX_TRACE)
px(img, 15 - i, 15 - i, TX_TRACE)
# Emitter core, hot centre
rect(img, 6, 6, 9, 9, TX_TRACE_HOT)
rect(img, 7, 7, 8, 8, TX_CORE)
px(img, 7, 7, (255, 235, 220))
# Trace highlight so the cross reads as raised
rect(img, 7, 1, 7, 5, TX_TRACE_HOT)
rect(img, 1, 7, 5, 7, TX_TRACE_HOT)
return img
def redstone_link_rx() -> Image.Image:
img = _link_plate()
# Collector rings, broken at the axes so the in-running traces read clearly
for x in range(3, 13):
px(img, x, 3, RX_TRACE)
px(img, x, 12, RX_TRACE)
for y in range(3, 13):
px(img, 3, y, RX_TRACE)
px(img, 12, y, RX_TRACE)
for x in range(5, 11):
px(img, x, 5, RX_TRACE_HOT)
px(img, x, 10, RX_TRACE_HOT)
for y in range(5, 11):
px(img, 5, y, RX_TRACE_HOT)
px(img, 10, y, RX_TRACE_HOT)
# Feed lines from the edges into the dish
rect(img, 7, 0, 8, 2, RX_TRACE)
rect(img, 7, 13, 8, 15, RX_TRACE)
rect(img, 0, 7, 2, 8, RX_TRACE)
rect(img, 13, 7, 15, 8, RX_TRACE)
# Collector core
rect(img, 7, 7, 8, 8, RX_TRACE_HOT)
px(img, 7, 7, RX_CORE)
px(img, 8, 8, RX_CORE)
return img
# ── Lobby portal blocks ────────────────────────────────────
# These previously pointed terrain_texture.json at vanilla paths
# (emerald_block, amethyst_block, prismarine_bricks, end_portal_frame_top)
# which a custom pack cannot resolve, so they rendered as missing-texture
# once minecraft:geometry made material_instances take effect. Ship real
# textures instead: a dark stone frame around a glowing field, one hue per
# child, so the three destinations are distinguishable at a glance.
PORTAL_STONE = (58, 60, 68)
PORTAL_STONE_HL = (86, 89, 99)
PORTAL_STONE_SH = (34, 35, 41)
def _portal_face(core, bright, glow):
img = Image.new("RGBA", (16, 16), PORTAL_STONE)
# chiselled frame
rect(img, 0, 0, 15, 0, PORTAL_STONE_HL)
rect(img, 0, 0, 0, 15, PORTAL_STONE_HL)
rect(img, 0, 15, 15, 15, PORTAL_STONE_SH)
rect(img, 15, 0, 15, 15, PORTAL_STONE_SH)
rect(img, 2, 2, 13, 13, PORTAL_STONE_SH)
# glowing field
rect(img, 3, 3, 12, 12, core)
rect(img, 4, 4, 11, 11, bright)
rect(img, 6, 6, 9, 9, glow)
# corner sparks so the field reads as energy, not flat paint
for cx, cy in ((4, 4), (11, 4), (4, 11), (11, 11)):
px(img, cx, cy, glow)
# dark centre seam
rect(img, 7, 7, 8, 8, core)
return img
def portal_jamie(): return _portal_face((22, 92, 40), (46, 156, 66), (140, 240, 150))
def portal_lyla(): return _portal_face((78, 40, 130), (126, 74, 190), (214, 168, 255))
def portal_mya(): return _portal_face((22, 96, 104), (46, 152, 160), (150, 238, 244))
def portal_frame():
img = Image.new("RGBA", (16, 16), PORTAL_STONE)
rect(img, 0, 0, 15, 0, PORTAL_STONE_HL)
rect(img, 0, 0, 0, 15, PORTAL_STONE_HL)
rect(img, 0, 15, 15, 15, PORTAL_STONE_SH)
rect(img, 15, 0, 15, 15, PORTAL_STONE_SH)
# inset panel with a dull gold ring, echoing an end portal frame
rect(img, 3, 3, 12, 12, (48, 50, 57))
for x in range(4, 12):
px(img, x, 4, GOLD); px(img, x, 11, GOLD)
for y in range(4, 12):
px(img, 4, y, GOLD); px(img, 11, y, GOLD)
rect(img, 6, 6, 9, 9, (70, 66, 40))
px(img, 7, 7, GOLD_BRIGHT); px(img, 8, 8, GOLD_BRIGHT)
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")
save(portal_frame(), "lobby-addon/lobby_transfer_RP/textures/blocks/portal_frame.png")
save(portal_jamie(), "lobby-addon/lobby_transfer_RP/textures/blocks/portal_jamie.png")
save(portal_lyla(), "lobby-addon/lobby_transfer_RP/textures/blocks/portal_lyla.png")
save(portal_mya(), "lobby-addon/lobby_transfer_RP/textures/blocks/portal_mya.png")
save(redstone_link_tx(), "redstone-link-addon/redstone_link_RP/textures/blocks/redstone_link_tx.png")
save(redstone_link_rx(), "redstone-link-addon/redstone_link_RP/textures/blocks/redstone_link_rx.png")
if __name__ == "__main__":
main()