Detonate rifle grenades on impact with original Half-Life fireball
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
"""Convert the original GoldSrc additive explosion into a portable PNG atlas.
|
||||
|
||||
Read-only on the Half-Life installation. No native SPR importer needed at runtime.
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import struct
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def extract(source: Path, destination: Path) -> dict:
|
||||
raw = source.read_bytes()
|
||||
ident, version, orientation, fmt, radius, width, height, count, beam, sync = (
|
||||
struct.unpack_from('<4siiifiiifi', raw)
|
||||
)
|
||||
assert (ident, version, fmt) == (b'IDSP', 2, 1), 'Expected GoldSrc additive SPR v2'
|
||||
palette_count = struct.unpack_from('<H', raw, 40)[0]
|
||||
assert 1 <= palette_count <= 256 and count == 15 and (width, height) == (64, 64)
|
||||
palette = raw[42:42 + palette_count * 3]
|
||||
offset = 42 + len(palette)
|
||||
columns, rows, gutter = 5, 3, 2
|
||||
tile_w, tile_h = width + 2 * gutter, height + 2 * gutter
|
||||
atlas = Image.new('RGB', (columns * tile_w, rows * tile_h))
|
||||
origins = []
|
||||
for index in range(count):
|
||||
kind, x, y, w, h = struct.unpack_from('<5i', raw, offset)
|
||||
assert kind == 0 and (w, h) == (width, height), 'Unsupported frame/group'
|
||||
offset += 20
|
||||
pixels = raw[offset:offset + w * h]
|
||||
assert len(pixels) == w * h and max(pixels) < palette_count
|
||||
frame = Image.frombytes('P', (w, h), pixels)
|
||||
frame.putpalette(palette)
|
||||
# Preserve RGB: black contributes zero under additive blending, NOT alphatest.
|
||||
atlas.paste(frame.convert('RGB'),
|
||||
((index % columns) * tile_w + gutter, (index // columns) * tile_h + gutter))
|
||||
origins.append([x, y])
|
||||
offset += w * h
|
||||
assert offset == len(raw), 'Trailing or truncated sprite data'
|
||||
destination.mkdir(parents=True, exist_ok=True)
|
||||
output = destination / 'zerogxplode_atlas.png'
|
||||
atlas.save(output, optimize=True)
|
||||
manifest = {
|
||||
'source': 'Half-Life/valve/sprites/zerogxplode.spr',
|
||||
'source_sha256': hashlib.sha256(raw).hexdigest(),
|
||||
'destination': str(output.relative_to(ROOT)),
|
||||
'destination_sha256': hashlib.sha256(output.read_bytes()).hexdigest(),
|
||||
'format': 'IDSP v2 / SPR_ADDITIVE', 'frame_size': [width, height],
|
||||
'frame_count': count, 'columns': columns, 'rows': rows, 'gutter': gutter,
|
||||
'origins': origins, 'fps': 15,
|
||||
'fps_source': 'https://github.com/ValveSoftware/halflife/blob/master/dlls/ggrenade.cpp',
|
||||
}
|
||||
(destination / 'source_manifest.json').write_text(json.dumps(manifest, indent=2) + '\n')
|
||||
return manifest
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--source', type=Path, default=Path.home() /
|
||||
'.steam/steam/steamapps/common/Half-Life/valve/sprites/zerogxplode.spr')
|
||||
args = parser.parse_args()
|
||||
print(json.dumps(extract(args.source, ROOT / 'assets/effects/explosion'), indent=2))
|
||||
Reference in New Issue
Block a user