SPP bot world as of 2026-08-26: merged state of all Cowork sessions (brain, combat data, chat seams 2.0, idiolect, heard/context, headless players, name generator, crests, seeds)
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user