Add scaling Alien Grunt boss waves and native simulation; publish Web release
This commit is contained in:
@@ -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()
|
||||
Reference in New Issue
Block a user