128 lines
5.6 KiB
Python
128 lines
5.6 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Импорт своего пака гербов в бот-кланы.
|
||
|
||
Использование:
|
||
python import-crests.py C:\\путь\\к\\папке_с_гербами
|
||
python import-crests.py C:\\путь --ally (только альянсовые, 8x16)
|
||
|
||
Принимает PNG / BMP / DDS. Всё, что не 16x16 (клан) или 8x16 (альянс),
|
||
масштабируется автоматически. Результат: server/db_installer/sql/spp_crest_seed.sql
|
||
Дальше: docker compose ... up -d --build и reset-bots.ps1
|
||
|
||
Требует: pip install pillow
|
||
"""
|
||
import sys, os, glob, struct, re, hashlib, random
|
||
|
||
try:
|
||
from PIL import Image
|
||
except ImportError:
|
||
print("Нужен Pillow: pip install pillow"); sys.exit(1)
|
||
|
||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||
SEED_CLAN = os.path.join(HERE, "server", "db_installer", "sql", "spp_clan_seed.sql")
|
||
SEED_OUT = os.path.join(HERE, "server", "db_installer", "sql", "spp_crest_seed.sql")
|
||
|
||
def dds_header(w, h, linear):
|
||
hdr = bytearray(128)
|
||
hdr[0:4] = b'DDS '
|
||
struct.pack_into('<I', hdr, 4, 124)
|
||
struct.pack_into('<I', hdr, 8, 0x0081007)
|
||
struct.pack_into('<I', hdr, 12, h)
|
||
struct.pack_into('<I', hdr, 16, w)
|
||
struct.pack_into('<I', hdr, 20, linear)
|
||
struct.pack_into('<I', hdr, 76, 32)
|
||
struct.pack_into('<I', hdr, 80, 0x4)
|
||
hdr[84:88] = b'DXT1'
|
||
struct.pack_into('<I', hdr, 108, 0x1000)
|
||
return bytes(hdr)
|
||
|
||
def to565(c):
|
||
return ((c[0] >> 3) << 11) | ((c[1] >> 2) << 5) | (c[2] >> 3)
|
||
|
||
def encode_dxt1(img):
|
||
"""Простое DXT1-сжатие: в каждом блоке 4x4 берём две крайние по яркости краски."""
|
||
w, h = img.size
|
||
px = img.convert("RGB").load()
|
||
body = b''
|
||
for by in range(h // 4):
|
||
for bx in range(w // 4):
|
||
cells = [px[bx * 4 + c, by * 4 + r] for r in range(4) for c in range(4)]
|
||
lum = [0.299 * c[0] + 0.587 * c[1] + 0.114 * c[2] for c in cells]
|
||
cmax = cells[lum.index(max(lum))]
|
||
cmin = cells[lum.index(min(lum))]
|
||
c0, c1 = to565(cmax), to565(cmin)
|
||
if c0 <= c1:
|
||
c0 = min(0xFFFF, c1 + 1)
|
||
idx = 0
|
||
for i, cell in enumerate(cells):
|
||
l = 0.299 * cell[0] + 0.587 * cell[1] + 0.114 * cell[2]
|
||
lo, hi = min(lum), max(lum)
|
||
t = 0 if hi == lo else (l - lo) / (hi - lo)
|
||
sel = 0 if t > 0.83 else (2 if t > 0.5 else (3 if t > 0.17 else 1))
|
||
idx |= (sel & 3) << (i * 2)
|
||
body += struct.pack('<HH', c0, c1) + struct.pack('<I', idx)
|
||
return dds_header(w, h, len(body)) + body
|
||
|
||
def load_crest(path, w, h):
|
||
if path.lower().endswith(".dds"):
|
||
data = open(path, "rb").read()
|
||
dw = int.from_bytes(data[16:20], 'little'); dh = int.from_bytes(data[12:16], 'little')
|
||
if (dw, dh) == (w, h) and len(data) <= (256 if w == 16 else 192):
|
||
return data # уже готовый файл нужного размера
|
||
print(f" ! {os.path.basename(path)}: {dw}x{dh} -> пересобираю")
|
||
return None
|
||
img = Image.open(path)
|
||
if img.size != (w, h):
|
||
img = img.resize((w, h), Image.LANCZOS)
|
||
return encode_dxt1(img)
|
||
|
||
def main():
|
||
if len(sys.argv) < 2:
|
||
print(__doc__); sys.exit(1)
|
||
folder = sys.argv[1]
|
||
files = []
|
||
for ext in ("png", "bmp", "dds", "jpg", "gif"):
|
||
files += sorted(glob.glob(os.path.join(folder, "**", "*." + ext), recursive=True))
|
||
if not files:
|
||
print("В папке нет картинок"); sys.exit(1)
|
||
print(f"Найдено файлов: {len(files)}")
|
||
|
||
clans = re.findall(r"INSERT IGNORE INTO clan_data \(clan_id, clan_name.*?VALUES \((\d+), '([^']+)'",
|
||
open(SEED_CLAN, encoding="utf-8").read())
|
||
print(f"Кланов в сиде: {len(clans)}")
|
||
|
||
out = ["-- SPP crests: imported from " + folder.replace("\\", "/"),
|
||
"DELETE FROM crests WHERE crest_id BETWEEN 9000 AND 9999;"]
|
||
crest_id = 9000
|
||
rnd = random.Random(1)
|
||
ally_size = 8
|
||
allies = [clans[i:i + ally_size] for i in range(0, len(clans), ally_size)]
|
||
A = ["Free","Iron","Silver","Northern","Old","Wild","Royal","United","Dark","Golden","Storm","Silent"]
|
||
B = ["Union","League","Pact","Alliance","Circle","Banner","Order","Coalition"]
|
||
ok = 0
|
||
for ai, group in enumerate(allies):
|
||
if len(group) < 3:
|
||
continue
|
||
name = A[ai % len(A)] + B[(ai // len(A)) % len(B)]
|
||
ally_src = files[ai % len(files)]
|
||
ally_data = load_crest(ally_src, 8, 16) or encode_dxt1(Image.open(ally_src).resize((8, 16)))
|
||
ally_id = crest_id; crest_id += 1
|
||
out.append(f"INSERT INTO crests (crest_id, data, type) VALUES ({ally_id}, 0x{ally_data.hex()}, 3);")
|
||
leader = int(group[0][0])
|
||
for cid, cname in group:
|
||
src = files[hash(cname) % len(files)]
|
||
data = load_crest(src, 16, 16)
|
||
if data is None:
|
||
data = encode_dxt1(Image.open(src).convert("RGB").resize((16, 16), Image.LANCZOS))
|
||
cc = crest_id; crest_id += 1
|
||
out.append(f"INSERT INTO crests (crest_id, data, type) VALUES ({cc}, 0x{data.hex()}, 1);")
|
||
out.append(f"UPDATE clan_data SET crest_id={cc}, ally_id={leader}, ally_name='{name}', ally_crest_id={ally_id} WHERE clan_id={cid};")
|
||
ok += 1
|
||
open(SEED_OUT, "w", encoding="utf-8").write("\n".join(out) + "\n")
|
||
print(f"Готово: {ok} клановых гербов -> {SEED_OUT}")
|
||
print("Дальше: docker compose -f server\\docker\\docker-compose.yml up --build -d и reset-bots.ps1")
|
||
|
||
if __name__ == "__main__":
|
||
main()
|