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:
2026-09-03 19:05:51 +02:00
parent f520c66e85
commit dcbecd2843
55 changed files with 58741 additions and 566 deletions
+127
View File
@@ -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()