- Stage 1: Eliminate fish-eye distortion with precomputed tangential ray offset table and fractional DDA depth - Stage 2: Full asset management in EblanStudio (stb_image/stb_image_write, 128x128 BGRA texture packaging, WAV sound manager) - Stage 3: Multi-level room streaming, LevelTransition/CELL_PORTAL trigger system, 16-frame screen fade to black - Stage 4: 60Hz cutscene engine in pure x86 ASM (v4 binary track, waypoint camera motion, shortest-arc pan, CP1251 Russian subtitles, letterbox overlay, audio) - Stage 5: EblanStudio cutscene timeline (F6) and windowless Live ASM Viewport over shared-memory IPC directly rendered into DX11 - Tooling & Tests: comprehensive regression test suite (94 editor + 107 engine tests), IPC tests, and cutscene demo
46 lines
2.4 KiB
Python
46 lines
2.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Build the separate cutscene example; never modifies the user's assets/ maps."""
|
|
from pathlib import Path
|
|
import math
|
|
import struct
|
|
import wave
|
|
|
|
ROOT = Path(__file__).resolve().parents[1] / "examples/cutscene_demo"
|
|
|
|
|
|
def write_map(name, *, spawn, angle, events=(), strings=(), transitions=(), autoplay=False):
|
|
grid = bytearray(1024)
|
|
for x in range(32): grid[x] = grid[31*32+x] = 1
|
|
for y in range(32): grid[y*32] = grid[y*32+31] = 1
|
|
for y in range(1, 18): grid[y*32+16] = 3
|
|
for x, y in ((6,6),(10,5),(12,12),(4,10)): grid[y*32+x] = 7
|
|
for t in transitions: grid[t[1]*32+t[0]] = 10
|
|
sprites = [(7*64+32,5*64+32,11,65536,0,0),(11*64+32,7*64+32,10,65536,0,0)]
|
|
header = struct.pack("<I7HII4H4s",0x4E4C4245,4,32,32,*spawn,angle,len(sprites),0x303848,0x282018,
|
|
len(transitions),len(events),len(strings),int(autoplay),bytes(4))
|
|
payload = header+grid+b"".join(struct.pack("<6i",*s) for s in sprites)
|
|
payload += b"".join(struct.pack("<BB32sHHHBB",*t) for t in transitions)
|
|
payload += b"".join(struct.pack("<HBBiii",*e) for e in events)
|
|
payload += b"".join(s.encode("cp1251").ljust(96,b"\0") for s in strings)
|
|
(ROOT / "assets" / name).write_bytes(payload)
|
|
|
|
|
|
def main():
|
|
(ROOT / "assets/sounds").mkdir(parents=True,exist_ok=True)
|
|
# END, WAIT, MOVE, PAN, TEXT, HIDE, SOUND, FADE, LEVEL
|
|
events = [(0,7,0,16,0,0),(0,7,0,0,60,0),(0,6,0,1,0,0),(0,2,0,544,224,180),
|
|
(20,4,0,0,140,0),(180,3,0,128,90,0),(180,2,0,544,544,180),
|
|
(240,4,0,2,170,0),(360,3,0,0,90,0),(360,2,0,800,544,180),
|
|
(450,5,0,0,0,0),(540,7,0,16,60,0),(600,8,0,0,0,0),(601,0,0,0,0,0)]
|
|
write_map("map.dat",spawn=(3,3),angle=0,events=events,
|
|
strings=("Кат-сцена: камера, текст и звук", "chime.wav", "Tab — пропустить. После пролёта — другая комната."),
|
|
transitions=((15,8,b"second_room.dat",3,3,0,1,0),),autoplay=True)
|
|
write_map("second_room.dat",spawn=(3,3),angle=0,
|
|
transitions=((2,3,b"map.dat",3,3,0,1,0),))
|
|
with wave.open(str(ROOT / "assets/sounds/chime.wav"),"wb") as sound:
|
|
sound.setnchannels(1);sound.setsampwidth(2);sound.setframerate(16000)
|
|
sound.writeframes(b"".join(struct.pack("<h",int(4000*math.sin(2*math.pi*660*i/16000)*(1-i/8000))) for i in range(8000)))
|
|
print(ROOT)
|
|
|
|
|
|
if __name__ == "__main__": main()
|