Add scaling Alien Grunt boss waves and native simulation; publish Web release

This commit is contained in:
enne2
2026-10-15 15:08:52 +02:00
parent 5761e160c1
commit 2606fc5cc2
44 changed files with 1464 additions and 9 deletions
+88
View File
@@ -0,0 +1,88 @@
"""Prepare original Alien Grunt / hornet assets, without changing Steam files.
Embed the companion texture MDL; keep only self-contained sequence-group-zero
animations. External group 1 contains cinematic sequences, not the combat set.
"""
import hashlib
import json
import shutil
import struct
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SOURCE = Path.home() / '.steam/steam/steamapps/common/Half-Life/valve'
DEST = ROOT / 'assets/boss'
def digest(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
def prepare_model(name, companion=None):
src = SOURCE / 'models' / (name + '.mdl')
body = bytearray(src.read_bytes())
assert body[:4] == b'IDST' and struct.unpack_from('<i', body, 4)[0] == 10
sources = [{'path': 'models/' + src.name, 'sha256': digest(src)}]
if companion:
texpath = SOURCE / 'models' / companion
tex = bytearray(texpath.read_bytes())
base = len(body)
count, records, data, refs, families, skins = struct.unpack_from('<6i', tex, 180)
assert count > 0
for i in range(count):
pos = records + i * 80
pixel = struct.unpack_from('<i', tex, pos + 76)[0]
struct.pack_into('<i', tex, pos + 76, pixel + base)
struct.pack_into('<6i', body, 180, count, records + base, data + base,
refs, families, skins + base)
body.extend(tex)
sources.append({'path': 'models/' + companion, 'sha256': digest(texpath)})
count, offset = struct.unpack_from('<2i', body, 164)
sequences, omitted, used = [], [], set()
for i in range(count):
record = bytearray(body[offset + 176 * i:offset + 176 * (i + 1)])
label = bytes(record[:32]).split(b'\0')[0].decode('ascii')
group = struct.unpack_from('<i', record, 156)[0]
if group:
omitted.append({'index': i, 'label': label, 'external_group': group})
continue
unique = label
suffix = 2
while unique in used:
unique = label + '_' + str(suffix)
suffix += 1
used.add(unique)
record[:32] = unique.encode().ljust(32, b'\0')
sequences.append(record)
# All sequence offsets still address the original, unchanged body data.
struct.pack_into('<2i', body, 164, len(sequences), len(body))
body.extend(b''.join(sequences))
struct.pack_into('<i', body, 172, 1)
struct.pack_into('<i', body, 72, len(body))
out = DEST / (name + '_textured.mdl')
out.write_bytes(body)
return {'sources': sources, 'destination': str(out.relative_to(ROOT)),
'sha256': digest(out), 'animations': sorted(used), 'omitted_sequences': omitted}
def main():
DEST.mkdir(parents=True, exist_ok=True)
records = [prepare_model('agrunt', 'agruntt.mdl'), prepare_model('hornet')]
sounds = ['agrunt/ag_alert1.wav', 'agrunt/ag_attack1.wav', 'agrunt/ag_fire1.wav',
'agrunt/ag_idle1.wav', 'agrunt/ag_pain1.wav', 'agrunt/ag_die1.wav',
'ambience/port_suckin1.wav', 'ambience/port_suckout1.wav']
for relative in sounds:
src = SOURCE / 'sound' / relative
out = ROOT / 'assets/audio' / relative
out.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(src, out)
assert digest(src) == digest(out)
records.append({'source': 'sound/' + relative,
'destination': str(out.relative_to(ROOT)), 'sha256': digest(src)})
(DEST / 'source_manifest.json').write_text(json.dumps(records, indent=2) + '\n')
for r in records:
print(r.get('destination'), r['sha256'])
if __name__ == '__main__':
main()