Add the REM interface generators (tools) and ignore dumps, logs and server files

make_icon_3db, make_image_3db and make_hud_txm with their helpers, standalone
(the name CRC table is in flcrc.py). The .gitignore keeps crash dumps, logs,
traces, debug symbols and everything of the server out of the mod.
This commit is contained in:
2026-10-20 14:01:12 +02:00
parent 574536f419
commit 6eb785d57c
7 changed files with 598 additions and 1 deletions
+94
View File
@@ -0,0 +1,94 @@
"""A coloured Freelancer interface image (.3db) from a PNG -- for pictures in the REM windows
(statistics medals, banners, portraits), shown with a StaticControl like the inventory's item
icons. Unlike make_icon_3db.py the colours stay and the quad keeps the picture's aspect.
python tools/make_image_3db.py <in.png> <out.3db> <name> [--size 256] [--nocrop] [--crop x0,y0,x1,y1]
The transparent border is cut off (unless --nocrop), the picture is stretched into a square
texture of --size pixels (three mips) and drawn on a quad of the picture's aspect, the longer
side as long as the retail icon quad. --crop cuts that pixel rectangle out first (a banner
strip from a square picture). <name> gives the material "UI_REMIMG_<name>", the texture
"REMIMG_<name>.tga" and the mesh library "rem_img_<name>.lod0.vms".
"""
import os
import struct
import sys
sys.path.insert(0, os.path.dirname(__file__))
import flcrc # noqa: E402
import make_icon_3db as icon # noqa: E402
import utf # noqa: E402
HALF = icon.QUAD_HALF
Z = icon.QUAD_Z
INDICES = [0, 1, 2, 1, 3, 2]
def crop(w, h, rows):
xs = [x for line in rows for x, p in enumerate(line) if p[3] > 8]
ys = [y for y, line in enumerate(rows) if any(p[3] > 8 for p in line)]
if not xs:
return w, h, rows
x0, x1, y0, y1 = min(xs), max(xs) + 1, min(ys), max(ys) + 1
return x1 - x0, y1 - y0, [line[x0:x1] for line in rows[y0:y1]]
def make(png, out, name, size=256, do_crop=True, rect=None):
w, h, rows = icon.read_png(png)
if rect is not None:
x0, y0, x1, y1 = rect
w, h, rows = x1 - x0, y1 - y0, [line[x0:x1] for line in rows[y0:y1]]
if do_crop:
w, h, rows = crop(w, h, rows)
hx, hy = (HALF, HALF * h / w) if w >= h else (HALF * w / h, HALF)
quad = [(-hx, hy, Z, 0.0, 1.0), (-hx, -hy, Z, 0.0, 0.0), (hx, hy, Z, 1.0, 1.0), (hx, -hy, Z, 1.0, 0.0)]
mips = []
s = size
for _ in range(3):
mips.append(icon.tga(icon.resample(w, h, rows, s)))
s = max(4, s // 2)
material = 'UI_REMIMG_' + name
texture = 'REMIMG_' + name + '.tga'
library = 'rem_img_' + name.lower() + '.lod0.vms'
data = struct.pack('<IIHHHH', 1, 4, 1, len(INDICES), 0x102, len(quad))
data += struct.pack('<IHHHH', flcrc.crc(material), 0, len(quad) - 1, len(INDICES), 204)
data += struct.pack('<%dH' % len(INDICES), *INDICES)
for v in quad:
data += struct.pack('<5f', *v)
ref = struct.pack('<IIHHHHHH10f', 60, flcrc.crc(library), 0, len(quad), 0, len(INDICES), 0, 1,
hx, -hx, hy, -hy, Z, Z, 0.0, 0.0, Z, max(hx, hy) * 1.5094)
N = utf.Node
root = N('\\', children=[
N('Exporter Version', data=b'REM make_image_3db\0'),
N('VMeshLibrary', children=[N(library, children=[N('VMeshData', data=data)])]),
N('VMeshPart', children=[N('VMeshRef', data=ref)]),
N('Material library', children=[
N('Material count', data=struct.pack('<I', 1)),
N(material, children=[N('Type', data=b'DcDtOcOt\0'), N('Dt_name', data=texture.encode() + b'\0'),
N('Dt_flags', data=struct.pack('<I', 64))])]),
N('Texture library', children=[
N(texture, children=[N('MIP%d' % i, data=m) for i, m in enumerate(mips)])]),
])
os.makedirs(os.path.dirname(os.path.abspath(out)), exist_ok=True)
utf.write(out, root)
return w, h
if __name__ == '__main__':
argv = sys.argv[1:]
size = 256
if '--size' in argv:
i = argv.index('--size')
size = int(argv[i + 1])
del argv[i:i + 2]
rect = None
if '--crop' in argv:
i = argv.index('--crop')
rect = tuple(int(v) for v in argv[i + 1].split(','))
del argv[i:i + 2]
args = [a for a in argv if not a.startswith('--')]
if len(args) != 3:
print(__doc__)
sys.exit(1)
w, h = make(args[0], args[1], args[2], size, '--nocrop' not in argv, rect)
print('wrote', args[1], '(%dx%d)' % (w, h))