#!/usr/bin/env python3 """make_texturepack.py - Build assets/textures/texturepack.bin for EBLAN ENGINE. Pack layout (little-endian): dd magic 0x58544245 ('EBTX') dw version (1) dw slotCount (16) then slotCount * 65536 bytes: 128x128 BGRA32 pixels per slot, slot 0 first. DIB memory order note: a DWORD 00RRGGBBh stores bytes B,G,R,0 little-endian. Sources: - Slot 1..48 taken from a KAZIK-format pack (48 slots, 128x128 BGRA) when --kazik path is given (default: ..\\..\\KAZIK\\assets\\textures\\casino3d-hd.bin). - Missing slots are filled with procedural fallback patterns that mirror the engine's GenerateDefaultTextures (with correct colors). """ import argparse import os import struct import sys TEX_W = TEX_H = 128 TEX_BYTES = TEX_W * TEX_H * 4 MAX_TEXTURES = 16 # 00RRGGBB helper ----------------------------------------------------------- def px(r, g, b): return bytes((b & 0xFF, g & 0xFF, r & 0xFF, 0)) # --------------------------------------------------------------------------- # Procedural fallbacks (mirror include/engine_textures.inc) # --------------------------------------------------------------------------- def gen_brick(): out = bytearray() for y in range(TEX_H): for x in range(TEX_W): r, g, b = 0x85, 0x22, 0x15 mortar = (y & 15) in (0, 1) vx = x + (16 if (y & 16) else 0) if (vx & 31) in (0, 1): mortar = True if mortar: r = g = b = 0x55 else: n = (((x ^ y) & 7) << 2) r = min(255, r + n); g = min(255, g + n); b = min(255, b + n) out += px(r, g, b) return bytes(out) def gen_wood(): out = bytearray() for y in range(TEX_H): for x in range(TEX_W): r, g, b = 0x68, 0x3A, 0x12 if (x & 31) == 0: r, g, b = 0x20, 0x10, 0x05 else: n = ((y & 3) << 3) r = min(255, r + n); g = min(255, g + n); b = min(255, b + n) out += px(r, g, b) return bytes(out) def gen_stone(): out = bytearray() for y in range(TEX_H): for x in range(TEX_W): r = g = b = 0x55 if (x & 31) == 0 or (y & 31) == 0: r = g = b = 0x22 else: n = ((((x * 3) + y) & 15) << 2) r = min(255, r + n); g = min(255, g + n); b = min(255, b + n) out += px(r, g, b) return bytes(out) def gen_metal(): out = bytearray() for y in range(TEX_H): for x in range(TEX_W): r, g, b = 0x60, 0x65, 0x70 if (x - 10) ** 2 + (y - 10) ** 2 <= 16 or \ (x - 118) ** 2 + (y - 118) ** 2 <= 16: r, g, b = 0xA0, 0xB0, 0xC0 out += px(r, g, b) return bytes(out) def gen_door(): out = bytearray() for y in range(TEX_H): for x in range(TEX_W): if x < 8 or x >= 120 or y < 8: r = g = b = 0x33 else: r, g, b = 0x50, 0x2D, 0x15 if (x - 100) ** 2 + (y - 64) ** 2 <= 36: r, g, b = 0xFF, 0xD0, 0x00 out += px(r, g, b) return bytes(out) def gen_pillar(): out = bytearray() for y in range(TEX_H): for x in range(TEX_W): if (x & 15) < 8: r, g, b = 0x70, 0xB0, 0xC0 else: r, g, b = 0x40, 0x80, 0x90 out += px(r, g, b) return bytes(out) def gen_glass(): out = bytearray() for y in range(TEX_H): for x in range(TEX_W): r, g, b = 0x40, 0x30, 0x20 if x < 6 or x >= 122 or y < 6 or y >= 122: r = g = b = 0x60 else: d = (x + y) & 63 if d < 4 or 24 <= d < 28: r, g, b = 0x90, 0x70, 0x50 out += px(r, g, b) return bytes(out) def gen_marble(): out = bytearray() for y in range(TEX_H): for x in range(TEX_W): r, g, b = 0xD8, 0xD0, 0xC0 v = (x * 7 + y * 13) v = (v + (v >> 3)) & 63 if v <= 2 or v == 40: r = g = b = 0x55 else: n = (x ^ y) & 3 r = min(255, r + n); g = min(255, g + n); b = min(255, b + n) out += px(r, g, b) return bytes(out) def gen_lamp(): """Sprite texture slot 10: street lamp / torch with magenta chroma key.""" out = bytearray() CK = (0xFF, 0x00, 0xFF) for y in range(TEX_H): for x in range(TEX_W): r, g, b = CK # pole if 60 <= x < 68 and 40 <= y < 120: r = g = b = 0x40 # lamp head if 48 <= x < 80 and 16 <= y < 44: r, g, b = 0xFF, 0xC0, 0x30 # base if 52 <= x < 76 and 120 <= y < 126: r = g = b = 0x30 out += px(r, g, b) return bytes(out) def gen_barrel(): """Sprite texture slot 11: wooden barrel with chroma key.""" out = bytearray() CK = (0xFF, 0x00, 0xFF) cx = 64 for y in range(TEX_H): for x in range(TEX_W): r, g, b = CK # barrel silhouette: ellipse-ish dx = (x - cx) / 34.0 dy = (y - 70) / 52.0 if dx * dx + dy * dy <= 1.0: r, g, b = 0x6A, 0x40, 0x1E # metal bands if abs(dy) > 0.55 and abs(dy) < 0.68: r = g = b = 0x55 # highlight if dx < -0.35 and abs(dy) < 0.5: r, g, b = 0x8A, 0x5A, 0x2E out += px(r, g, b) return bytes(out) def gen_statue(): """Sprite texture slot 12: stone statue / column prop with chroma key.""" out = bytearray() CK = (0xFF, 0x00, 0xFF) for y in range(TEX_H): for x in range(TEX_W): r, g, b = CK # pedestal if 44 <= x < 84 and 112 <= y < 126: r = g = b = 0x60 # body if 54 <= x < 74 and 40 <= y < 112: r = g = b = 0x8A + ((x ^ y) & 7) # head dx, dy = x - 64, y - 28 if dx * dx + dy * dy <= 100: r = g = b = 0x9A out += px(r, g, b) return bytes(out) GENERATORS = { 0: lambda: bytes(TEX_BYTES), # slot 0 = empty / solid marker 1: gen_brick, 2: gen_wood, 3: gen_stone, 4: gen_metal, 5: gen_door, 6: lambda: bytes(TEX_BYTES), # open door: never rendered 7: gen_pillar, 8: gen_glass, 9: gen_marble, 10: gen_lamp, 11: gen_barrel, 12: gen_statue, 13: lambda: bytes(TEX_BYTES), # reserved 14: lambda: bytes(TEX_BYTES), # reserved 15: lambda: bytes(TEX_BYTES), # reserved } def main(): ap = argparse.ArgumentParser() here = os.path.dirname(os.path.abspath(__file__)) ap.add_argument("--kazik", default=os.path.normpath(os.path.join( here, "..", "..", "KAZIK", "assets", "textures", "casino3d-hd.bin")), help="Path to KAZIK 48-slot texture pack (optional override)") ap.add_argument("--out", default=os.path.normpath(os.path.join( here, "..", "assets", "textures", "texturepack.bin")), help="Output pack path") args = ap.parse_args() slots = [GENERATORS[i]() for i in range(MAX_TEXTURES)] if os.path.isfile(args.kazik): data = open(args.kazik, "rb").read() avail = len(data) // TEX_BYTES print(f"[pack] KAZIK pack found: {args.kazik} ({avail} slots)") # KAZIK walls start at slot 1; map 1..min(9, avail) onto our 1..9 for slot in range(1, min(10, avail)): src = data[slot * TEX_BYTES:(slot + 1) * TEX_BYTES] if len(src) == TEX_BYTES: slots[slot] = src else: print(f"[pack] KAZIK pack not found at {args.kazik} - full procedural pack") os.makedirs(os.path.dirname(args.out), exist_ok=True) with open(args.out, "wb") as f: f.write(struct.pack("