feat(audio): implement interactive 3D jukebox (jackbox) and background music playback
This commit is contained in:
parent
5aec3ded9f
commit
82f3aa9437
22 changed files with 637 additions and 3 deletions
BIN
assets/animations/hall/jackbox-ui-v1.bin
Normal file
BIN
assets/animations/hall/jackbox-ui-v1.bin
Normal file
Binary file not shown.
BIN
assets/animations/hall/jackbox-v1.bin
Normal file
BIN
assets/animations/hall/jackbox-v1.bin
Normal file
Binary file not shown.
Binary file not shown.
BIN
assets/sprites/jackbox/Jackbox_UI.jpg
Normal file
BIN
assets/sprites/jackbox/Jackbox_UI.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 MiB |
BIN
assets/sprites/jackbox/Jackbox_front.jpg
Normal file
BIN
assets/sprites/jackbox/Jackbox_front.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 MiB |
BIN
assets/sprites/jackbox/Jackbox_side.jpg
Normal file
BIN
assets/sprites/jackbox/Jackbox_side.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 MiB |
BIN
bin/poker.exe
BIN
bin/poker.exe
Binary file not shown.
|
|
@ -8,7 +8,7 @@ namespace AssemblerCasinoLauncher
|
||||||
{
|
{
|
||||||
static class Program
|
static class Program
|
||||||
{
|
{
|
||||||
const string ExpectedPokerSha256 = "C3D857FDBF7597C976B16C5021D7F07951628E54F86FF2B7BB53F8A83FDE11CA";
|
const string ExpectedPokerSha256 = "B1D4D027ACE9BDF6B34C4621191E5DAB855D44E384F45F9B0D0E8EC528DF2B28";
|
||||||
|
|
||||||
[STAThread]
|
[STAThread]
|
||||||
static void Main()
|
static void Main()
|
||||||
|
|
|
||||||
Binary file not shown.
14
casino3d.inc
14
casino3d.inc
|
|
@ -172,7 +172,8 @@ include src_graphics\scratch_data.inc
|
||||||
include src_graphics\hell_tower_data.inc
|
include src_graphics\hell_tower_data.inc
|
||||||
include src_graphics\charms_data.inc
|
include src_graphics\charms_data.inc
|
||||||
include src_graphics\observer_data.inc
|
include src_graphics\observer_data.inc
|
||||||
include src_graphics\darts_data.inc
|
include src_graphics\darts_data.inc
|
||||||
|
include src_graphics\jackbox_data.inc
|
||||||
|
|
||||||
.data
|
.data
|
||||||
ALIGN 4
|
ALIGN 4
|
||||||
|
|
@ -5197,7 +5198,8 @@ include src_graphics\fortune_dialog.inc
|
||||||
include src_graphics\hell_tower.inc
|
include src_graphics\hell_tower.inc
|
||||||
include src_graphics\charms.inc
|
include src_graphics\charms.inc
|
||||||
include src_graphics\observer.inc
|
include src_graphics\observer.inc
|
||||||
include src_graphics\darts.inc
|
include src_graphics\darts.inc
|
||||||
|
include src_graphics\jackbox.inc
|
||||||
|
|
||||||
; --- Main Loop of the 3D Casino Engine ---
|
; --- Main Loop of the 3D Casino Engine ---
|
||||||
StartHellTower3D PROC uses ebx esi edi
|
StartHellTower3D PROC uses ebx esi edi
|
||||||
|
|
@ -5356,6 +5358,14 @@ check_walk_mode:
|
||||||
call RenderMinimap
|
call RenderMinimap
|
||||||
jmp present_frame
|
jmp present_frame
|
||||||
check_slots_mode:
|
check_slots_mode:
|
||||||
|
cmp engineMode,MODE_JACKBOX
|
||||||
|
jne check_jackbox_done
|
||||||
|
call HandleJackbox3D
|
||||||
|
cmp engineMode,MODE_WALK
|
||||||
|
je check_walk_mode
|
||||||
|
call RenderJackbox3D
|
||||||
|
jmp present_frame
|
||||||
|
check_jackbox_done:
|
||||||
cmp engineMode,MODE_DARTS
|
cmp engineMode,MODE_DARTS
|
||||||
jne check_darts_done
|
jne check_darts_done
|
||||||
call HandleDarts3D
|
call HandleDarts3D
|
||||||
|
|
|
||||||
BIN
dist/Assembler Casino.exe
vendored
BIN
dist/Assembler Casino.exe
vendored
Binary file not shown.
47
docs/JACKBOX.md
Normal file
47
docs/JACKBOX.md
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
# Музыкальный автомат (Jukebox / Jackbox) в казино
|
||||||
|
|
||||||
|
## Описание
|
||||||
|
В зале казино (зона бара и лаунжа, координаты `(6.0, 20.0)`) установлен интерактивный ретро-автомат для выбора и воспроизведения фоновой музыки.
|
||||||
|
|
||||||
|
## Возможности
|
||||||
|
1. **Фоновое воспроизведение (Windows MCI)**:
|
||||||
|
- Музыка воспроизводится асинхронно в фоне через нативный API `winmm.dll` (`mciSendStringA`).
|
||||||
|
- Музыка **продолжает играть** при выходе из меню автомата обратно в 3D-зал, во время ходьбы, игры за столами (покер, блэкджек, рулетка, дурак, слоты, дартс и др.).
|
||||||
|
- При завершении трека он автоматически зацикливается (`repeat`).
|
||||||
|
2. **Интерактивный экран (`MODE_JACKBOX = 28`)**:
|
||||||
|
- Отрисовывается на базе текстуры `assets/animations/hall/jackbox-ui-v1.bin` (640×480 BGRA).
|
||||||
|
- Отображает текущий статус («СЕЙЧАС ИГРАЕТ: <Трек>» или «МУЗЫКА ВЫКЛЮЧЕНА»).
|
||||||
|
- Список из 10 позиций с подсветкой текущего выбора `> ` и индикатором `[ИГРАЕТ]`.
|
||||||
|
3. **Управление**:
|
||||||
|
- `Стрелки Вверх / Вниз` или `W / S`: выбор трека в списке.
|
||||||
|
- `Enter` / `Пробел`: включить выбранный трек.
|
||||||
|
- `1`–`9`: мгновенный запуск соответствующего трека по номеру.
|
||||||
|
- `0`: выключить музыку (тишина).
|
||||||
|
- `S`: остановить воспроизведение.
|
||||||
|
- `R`: воспроизвести случайный трек.
|
||||||
|
- `Esc` / `E` / Клик по кнопке «ВЫХОД»: вернуться в 3D-зал.
|
||||||
|
- `Клик мыши` по строке трека: мгновенный выбор и запуск.
|
||||||
|
4. **Физическая 3D-модель в зале**:
|
||||||
|
- Габариты: ширина 0.7м, глубина 0.45м, высота 1.85м.
|
||||||
|
- Материал 161 (фасад `Jackbox_front.jpg`), материал 162 (боковины `Jackbox_side.jpg`), верхняя крышка (110).
|
||||||
|
- Коллизия: игрок не может пройти сквозь автомат (`JackboxCanWalk3D`).
|
||||||
|
|
||||||
|
## Плейлист
|
||||||
|
| № | Исполнитель и трек | Формат |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | Keygen Church — Tenebre Rosso Sangue (ULTRAKILL OST) | MP3 |
|
||||||
|
| 2 | Keygen Church — Hareklavit | MP3 |
|
||||||
|
| 3 | Keygen Church — Ladikerfos | MP3 |
|
||||||
|
| 4 | Keygen Church — Tilaskarot | MP3 |
|
||||||
|
| 5 | Mick Gordon — BFG Division (DOOM) | MP3 |
|
||||||
|
| 6 | Mick Gordon — Rip & Tear (DOOM) | MP3 |
|
||||||
|
| 7 | Mick Gordon — The Only Thing They Fear Is You (DOOM Eternal) | MP3 |
|
||||||
|
| 8 | Hakari Dance (Jackpot Win Theme) | WAV |
|
||||||
|
| 9 | Team Fortress 2 — Victory Anthem | WAV |
|
||||||
|
| 0 | *[ ВЫКЛЮЧИТЬ МУЗЫКУ / ТИШИНА ]* | — |
|
||||||
|
|
||||||
|
## Файлы реализации
|
||||||
|
- `src_graphics/jackbox_data.inc`: структуры, таблицы команд MCI, пути, CP1251 строки.
|
||||||
|
- `src_graphics/jackbox.inc`: загрузка ассетов, 3D-кабинет, коллизии, аудио-контроллер, ввод и рендеринг GUI.
|
||||||
|
- `tools/generators/pack_jackbox.py`: оффлайн-компилятор текстур из `assets/sprites/jackbox/` в бинарные контейнеры `ANM1`.
|
||||||
|
- `tools/tests/check_jackbox.py`: тестовый набор верификации ассетов, аудио-файлов и бинарника.
|
||||||
BIN
poker.exe
BIN
poker.exe
Binary file not shown.
BIN
poker.obj
BIN
poker.obj
Binary file not shown.
|
|
@ -44,5 +44,6 @@ close_legacy_sound:
|
||||||
cmp esi, legacySoundCount3D
|
cmp esi, legacySoundCount3D
|
||||||
jb close_legacy_sound
|
jb close_legacy_sound
|
||||||
call StopTowerMusic
|
call StopTowerMusic
|
||||||
|
call StopJukeboxTrack3D
|
||||||
ret
|
ret
|
||||||
CloseLegacySounds3D ENDP
|
CloseLegacySounds3D ENDP
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,13 @@ cityRayY3D dd SCREEN_W dup(?)
|
||||||
; Exterior-only materials do not consume or overwrite the hall's texture slots.
|
; Exterior-only materials do not consume or overwrite the hall's texture slots.
|
||||||
ResolveCitySurface3D PROC material:DWORD
|
ResolveCitySurface3D PROC material:DWORD
|
||||||
mov eax,material
|
mov eax,material
|
||||||
|
cmp eax,161
|
||||||
|
jb @F
|
||||||
|
cmp eax,162
|
||||||
|
ja @F
|
||||||
|
invoke ResolveJackboxSurface3D,material
|
||||||
|
ret
|
||||||
|
@@:
|
||||||
cmp eax,160
|
cmp eax,160
|
||||||
jne @F
|
jne @F
|
||||||
call ResolveDartsSurface3D
|
call ResolveDartsSurface3D
|
||||||
|
|
|
||||||
|
|
@ -782,6 +782,7 @@ CheckHallInteractions3D PROC
|
||||||
invoke HallTarget3D, playerX,23*FP_1+FP_HALF,INTERACT_BAR,OFFSET hallBarPrompt3D
|
invoke HallTarget3D, playerX,23*FP_1+FP_HALF,INTERACT_BAR,OFFSET hallBarPrompt3D
|
||||||
hall_check_other:
|
hall_check_other:
|
||||||
invoke HallTarget3D,FP_1*305/10,28*FP_1,INTERACT_DARTS,OFFSET dtPrompt
|
invoke HallTarget3D,FP_1*305/10,28*FP_1,INTERACT_DARTS,OFFSET dtPrompt
|
||||||
|
invoke HallTarget3D,JACKBOX_X,JACKBOX_Y,INTERACT_JACKBOX,OFFSET jackboxPrompt3D
|
||||||
invoke HallTarget3D,16*FP_1,FP_1*73/10,INTERACT_CABARET_SEAT,OFFSET cabaretSeat3D
|
invoke HallTarget3D,16*FP_1,FP_1*73/10,INTERACT_CABARET_SEAT,OFFSET cabaretSeat3D
|
||||||
invoke HallTarget3D,scrStation3D.posX,scrStation3D.posY,INTERACT_SCRATCH,OFFSET scratchHall3D
|
invoke HallTarget3D,scrStation3D.posX,scrStation3D.posY,INTERACT_SCRATCH,OFFSET scratchHall3D
|
||||||
invoke HallTarget3D,FP_1*285/10,FP_1*105/10,INTERACT_BASEMENT,OFFSET hallBunkerPrompt3D
|
invoke HallTarget3D,FP_1*285/10,FP_1*105/10,INTERACT_BASEMENT,OFFSET hallBunkerPrompt3D
|
||||||
|
|
@ -798,6 +799,12 @@ hall_check_bunker:
|
||||||
CheckHallInteractions3D ENDP
|
CheckHallInteractions3D ENDP
|
||||||
|
|
||||||
TriggerHallInteraction3D PROC
|
TriggerHallInteraction3D PROC
|
||||||
|
cmp interactTargetId,INTERACT_JACKBOX
|
||||||
|
jne @F
|
||||||
|
call BeginJackbox3D
|
||||||
|
mov eax,1
|
||||||
|
ret
|
||||||
|
@@:
|
||||||
cmp interactTargetId,INTERACT_DARTS
|
cmp interactTargetId,INTERACT_DARTS
|
||||||
jne hall_not_darts
|
jne hall_not_darts
|
||||||
call BeginDarts3D
|
call BeginDarts3D
|
||||||
|
|
@ -1119,6 +1126,9 @@ fortune_collision_stairs:
|
||||||
invoke TerminalCanWalk3D,px,py
|
invoke TerminalCanWalk3D,px,py
|
||||||
test eax,eax
|
test eax,eax
|
||||||
jz hall_collision_blocked
|
jz hall_collision_blocked
|
||||||
|
invoke JackboxCanWalk3D,px,py
|
||||||
|
test eax,eax
|
||||||
|
jz hall_collision_blocked
|
||||||
xor esi, esi
|
xor esi, esi
|
||||||
hall_collision_decor:
|
hall_collision_decor:
|
||||||
imul ebx, esi, SIZEOF CityProp3D
|
imul ebx, esi, SIZEOF CityProp3D
|
||||||
|
|
|
||||||
323
src_graphics/jackbox.inc
Normal file
323
src_graphics/jackbox.inc
Normal file
|
|
@ -0,0 +1,323 @@
|
||||||
|
; ==============================================================================
|
||||||
|
; JACKBOX / JUKEBOX IMPLEMENTATION - MASM32 / CP1251
|
||||||
|
; ==============================================================================
|
||||||
|
|
||||||
|
.code
|
||||||
|
|
||||||
|
LoadJackboxArt3D PROC
|
||||||
|
invoke LoadAnimPack3D, OFFSET jackboxWorldArt3D, OFFSET jackboxWorldPath3D
|
||||||
|
invoke ValidateCityPack3D, OFFSET jackboxWorldArt3D, 256, 512, 2
|
||||||
|
invoke LoadAnimPack3D, OFFSET jackboxScreenArt3D, OFFSET jackboxScreenPath3D
|
||||||
|
invoke ValidateCityPack3D, OFFSET jackboxScreenArt3D, 640, 480, 1
|
||||||
|
ret
|
||||||
|
LoadJackboxArt3D ENDP
|
||||||
|
|
||||||
|
FreeJackboxArt3D PROC
|
||||||
|
invoke FreeAnimPack3D, OFFSET jackboxWorldArt3D
|
||||||
|
invoke FreeAnimPack3D, OFFSET jackboxScreenArt3D
|
||||||
|
ret
|
||||||
|
FreeJackboxArt3D ENDP
|
||||||
|
|
||||||
|
ResolveJackboxSurface3D PROC material:DWORD
|
||||||
|
cmp jackboxWorldArt3D.pixels, 0
|
||||||
|
je jackbox_surface_missing
|
||||||
|
mov eax, material
|
||||||
|
cmp eax, MATERIAL_JACKBOX_FRONT
|
||||||
|
je @F
|
||||||
|
cmp eax, MATERIAL_JACKBOX_SIDE
|
||||||
|
jne jackbox_surface_missing
|
||||||
|
; Frame 1: side (offset 256*512*4 = 524288)
|
||||||
|
mov eax, jackboxWorldArt3D.pixels
|
||||||
|
add eax, 524288
|
||||||
|
mov ecx, 256
|
||||||
|
mov edx, 512
|
||||||
|
ret
|
||||||
|
@@:
|
||||||
|
; Frame 0: front
|
||||||
|
mov eax, jackboxWorldArt3D.pixels
|
||||||
|
mov ecx, 256
|
||||||
|
mov edx, 512
|
||||||
|
ret
|
||||||
|
jackbox_surface_missing:
|
||||||
|
xor eax, eax
|
||||||
|
ret
|
||||||
|
ResolveJackboxSurface3D ENDP
|
||||||
|
|
||||||
|
DrawJackboxCabinet3D PROC uses ebx esi edi wx:DWORD, wy:DWORD
|
||||||
|
local x0:DWORD, x1:DWORD, y0:DWORD, y1:DWORD
|
||||||
|
; Footprint: width 0.7m, depth 0.45m, height 1.85m
|
||||||
|
mov eax, wx
|
||||||
|
sub eax, FP_1*7/20
|
||||||
|
mov x0, eax
|
||||||
|
add eax, FP_1*7/10
|
||||||
|
mov x1, eax
|
||||||
|
mov eax, wy
|
||||||
|
sub eax, FP_1*9/40
|
||||||
|
mov y0, eax
|
||||||
|
add eax, FP_1*9/20
|
||||||
|
mov y1, eax
|
||||||
|
invoke DrawCityBoxSides3D, x0, x1, y0, y1, 0, FP_1*37/20, MATERIAL_JACKBOX_SIDE
|
||||||
|
invoke DrawCityHorizontal3D, x0, x1, y0, y1, FP_1*37/20, 110
|
||||||
|
mov eax, y1
|
||||||
|
add eax, FP_1/500
|
||||||
|
invoke DrawCityVertical3D, 1, eax, x0, x1, 0, FP_1*37/20, MATERIAL_JACKBOX_FRONT
|
||||||
|
ret
|
||||||
|
DrawJackboxCabinet3D ENDP
|
||||||
|
|
||||||
|
RenderJackboxCabinet3D PROC
|
||||||
|
invoke DrawJackboxCabinet3D, JACKBOX_X, JACKBOX_Y
|
||||||
|
ret
|
||||||
|
RenderJackboxCabinet3D ENDP
|
||||||
|
|
||||||
|
JackboxCanWalk3D PROC px:DWORD, py:DWORD
|
||||||
|
mov eax, py
|
||||||
|
sub eax, JACKBOX_Y
|
||||||
|
test eax, eax
|
||||||
|
jns @F
|
||||||
|
neg eax
|
||||||
|
@@:
|
||||||
|
cmp eax, FP_1*7/20
|
||||||
|
ja jackbox_walk_clear
|
||||||
|
mov eax, px
|
||||||
|
sub eax, JACKBOX_X
|
||||||
|
test eax, eax
|
||||||
|
jns @F
|
||||||
|
neg eax
|
||||||
|
@@:
|
||||||
|
cmp eax, FP_HALF
|
||||||
|
jbe jackbox_walk_blocked
|
||||||
|
jackbox_walk_clear:
|
||||||
|
mov eax, 1
|
||||||
|
ret
|
||||||
|
jackbox_walk_blocked:
|
||||||
|
xor eax, eax
|
||||||
|
ret
|
||||||
|
JackboxCanWalk3D ENDP
|
||||||
|
|
||||||
|
PlayJukeboxTrack3D PROC uses ebx trackIndex:DWORD
|
||||||
|
invoke mciSendStringA, OFFSET jboxStopCmd, 0, 0, 0
|
||||||
|
invoke mciSendStringA, OFFSET jboxCloseCmd, 0, 0, 0
|
||||||
|
mov jboxIsPlaying, 0
|
||||||
|
|
||||||
|
mov ebx, trackIndex
|
||||||
|
cmp ebx, 9
|
||||||
|
jae jbox_play_done
|
||||||
|
cmp ebx, JACKBOX_TRACK_COUNT
|
||||||
|
jae jbox_play_done
|
||||||
|
|
||||||
|
mov eax, jboxOpenCommands[ebx*4]
|
||||||
|
test eax, eax
|
||||||
|
jz jbox_play_done
|
||||||
|
invoke mciSendStringA, eax, 0, 0, 0
|
||||||
|
invoke mciSendStringA, OFFSET jboxPlayCmd, 0, 0, 0
|
||||||
|
mov jboxCurrentTrack, ebx
|
||||||
|
mov jboxIsPlaying, 1
|
||||||
|
jbox_play_done:
|
||||||
|
ret
|
||||||
|
PlayJukeboxTrack3D ENDP
|
||||||
|
|
||||||
|
StopJukeboxTrack3D PROC
|
||||||
|
invoke mciSendStringA, OFFSET jboxStopCmd, 0, 0, 0
|
||||||
|
invoke mciSendStringA, OFFSET jboxCloseCmd, 0, 0, 0
|
||||||
|
mov jboxIsPlaying, 0
|
||||||
|
mov jboxCurrentTrack, -1
|
||||||
|
ret
|
||||||
|
StopJukeboxTrack3D ENDP
|
||||||
|
|
||||||
|
BeginJackbox3D PROC
|
||||||
|
mov engineMode, MODE_JACKBOX
|
||||||
|
call ClearKeys3D
|
||||||
|
mov mouseClick3D, 0
|
||||||
|
ret
|
||||||
|
BeginJackbox3D ENDP
|
||||||
|
|
||||||
|
HandleJackbox3D PROC uses ebx esi
|
||||||
|
; 1. Mouse interactions
|
||||||
|
cmp mouseClick3D, 0
|
||||||
|
je jbox_keys
|
||||||
|
|
||||||
|
; Check Exit button in top right: x in [228..312], y in [8..26]
|
||||||
|
cmp mouseY3D, 8
|
||||||
|
jb @F
|
||||||
|
cmp mouseY3D, 26
|
||||||
|
ja @F
|
||||||
|
cmp mouseX3D, 228
|
||||||
|
jae jbox_exit
|
||||||
|
@@:
|
||||||
|
; Check track row click: y in [54..186]
|
||||||
|
cmp mouseY3D, 54
|
||||||
|
jb jbox_keys
|
||||||
|
cmp mouseY3D, 186
|
||||||
|
ja jbox_keys
|
||||||
|
cmp mouseX3D, 16
|
||||||
|
jb jbox_keys
|
||||||
|
cmp mouseX3D, 304
|
||||||
|
ja jbox_keys
|
||||||
|
mov eax, mouseY3D
|
||||||
|
sub eax, 54
|
||||||
|
xor edx, edx
|
||||||
|
mov ebx, 13
|
||||||
|
div ebx
|
||||||
|
cmp eax, JACKBOX_TRACK_COUNT
|
||||||
|
jae jbox_keys
|
||||||
|
mov jboxSelectedTrack, eax
|
||||||
|
invoke PlayJukeboxTrack3D, eax
|
||||||
|
jmp jbox_handled
|
||||||
|
|
||||||
|
jbox_keys:
|
||||||
|
; Exit keys: ESC (1Bh) or 'E' (45h)
|
||||||
|
invoke Pressed3D, 1Bh
|
||||||
|
test eax, eax
|
||||||
|
jnz jbox_exit
|
||||||
|
invoke Pressed3D, 45h
|
||||||
|
test eax, eax
|
||||||
|
jnz jbox_exit
|
||||||
|
|
||||||
|
; Navigation: UP (26h) / DOWN (28h)
|
||||||
|
invoke Pressed3D, 26h
|
||||||
|
test eax, eax
|
||||||
|
jz @F
|
||||||
|
dec jboxSelectedTrack
|
||||||
|
jns jbox_handled
|
||||||
|
mov jboxSelectedTrack, JACKBOX_TRACK_COUNT - 1
|
||||||
|
jmp jbox_handled
|
||||||
|
@@:
|
||||||
|
invoke Pressed3D, 28h
|
||||||
|
test eax, eax
|
||||||
|
jz @F
|
||||||
|
inc jboxSelectedTrack
|
||||||
|
cmp jboxSelectedTrack, JACKBOX_TRACK_COUNT
|
||||||
|
jb jbox_handled
|
||||||
|
mov jboxSelectedTrack, 0
|
||||||
|
jmp jbox_handled
|
||||||
|
@@:
|
||||||
|
; Play selection: ENTER (0Dh) or SPACE (20h)
|
||||||
|
invoke Pressed3D, 0Dh
|
||||||
|
test eax, eax
|
||||||
|
jnz jbox_play_selected
|
||||||
|
invoke Pressed3D, 20h
|
||||||
|
test eax, eax
|
||||||
|
jnz jbox_play_selected
|
||||||
|
|
||||||
|
; Stop key: 'S' (53h)
|
||||||
|
invoke Pressed3D, 53h
|
||||||
|
test eax, eax
|
||||||
|
jz @F
|
||||||
|
call StopJukeboxTrack3D
|
||||||
|
jmp jbox_handled
|
||||||
|
@@:
|
||||||
|
; Random key: 'R' (52h)
|
||||||
|
invoke Pressed3D, 52h
|
||||||
|
test eax, eax
|
||||||
|
jz @F
|
||||||
|
invoke RandomRange3D, 9
|
||||||
|
mov jboxSelectedTrack, eax
|
||||||
|
invoke PlayJukeboxTrack3D, eax
|
||||||
|
jmp jbox_handled
|
||||||
|
@@:
|
||||||
|
; Direct digit keys '1'..'9', '0'
|
||||||
|
mov ebx, 31h ; '1'
|
||||||
|
jbox_digit_loop:
|
||||||
|
invoke Pressed3D, ebx
|
||||||
|
test eax, eax
|
||||||
|
jz jbox_digit_next
|
||||||
|
mov eax, ebx
|
||||||
|
sub eax, 31h
|
||||||
|
mov jboxSelectedTrack, eax
|
||||||
|
invoke PlayJukeboxTrack3D, eax
|
||||||
|
jmp jbox_handled
|
||||||
|
jbox_digit_next:
|
||||||
|
inc ebx
|
||||||
|
cmp ebx, 39h ; '9'
|
||||||
|
jbe jbox_digit_loop
|
||||||
|
|
||||||
|
; '0' key for mute/stop
|
||||||
|
invoke Pressed3D, 30h
|
||||||
|
test eax, eax
|
||||||
|
jz jbox_handled
|
||||||
|
mov jboxSelectedTrack, 9
|
||||||
|
invoke PlayJukeboxTrack3D, 9
|
||||||
|
jmp jbox_handled
|
||||||
|
|
||||||
|
jbox_play_selected:
|
||||||
|
invoke PlayJukeboxTrack3D, jboxSelectedTrack
|
||||||
|
jmp jbox_handled
|
||||||
|
|
||||||
|
jbox_exit:
|
||||||
|
mov engineMode, MODE_WALK
|
||||||
|
call ClearKeys3D
|
||||||
|
|
||||||
|
jbox_handled:
|
||||||
|
mov mouseClick3D, 0
|
||||||
|
ret
|
||||||
|
HandleJackbox3D ENDP
|
||||||
|
|
||||||
|
RenderJackbox3D PROC uses ebx esi edi
|
||||||
|
local rowY:DWORD, color:DWORD
|
||||||
|
|
||||||
|
; 1. Draw base background
|
||||||
|
invoke DrawBox, 0, 0, 320, 240, 000B1815h
|
||||||
|
|
||||||
|
; 2. Overlay screen artwork if available
|
||||||
|
cmp jackboxScreenArt3D.pixels, 0
|
||||||
|
je @F
|
||||||
|
invoke DrawAnimSprite3D, OFFSET jackboxScreenArt3D, 0, 0, 0, 640, 480
|
||||||
|
@@:
|
||||||
|
; 3. Title & Exit Button
|
||||||
|
invoke DrawString8x8, 52, 12, OFFSET jboxTitle, 00E7C479h
|
||||||
|
invoke DrawBoxBorder, 228, 8, 84, 16, 001A2522h, 00C8AD75h, 1
|
||||||
|
invoke DrawString8x8, 234, 12, OFFSET jboxClose, 00F2E6C6h
|
||||||
|
|
||||||
|
; 4. Now Playing Status Banner
|
||||||
|
cmp jboxIsPlaying, 1
|
||||||
|
jne jbox_render_stopped
|
||||||
|
cmp jboxCurrentTrack, -1
|
||||||
|
je jbox_render_stopped
|
||||||
|
cmp jboxCurrentTrack, 9
|
||||||
|
jae jbox_render_stopped
|
||||||
|
|
||||||
|
invoke DrawString8x8, 20, 34, OFFSET jboxStatusPlaying, 008CD8AFh
|
||||||
|
mov eax, jboxCurrentTrack
|
||||||
|
mov edx, jboxTrackNames[eax*4]
|
||||||
|
invoke DrawString8x8, 140, 34, edx, 00F0E6C3h
|
||||||
|
jmp jbox_render_divider
|
||||||
|
|
||||||
|
jbox_render_stopped:
|
||||||
|
invoke DrawString8x8, 20, 34, OFFSET jboxStatusStopped, 0090B19Fh
|
||||||
|
|
||||||
|
jbox_render_divider:
|
||||||
|
invoke DrawBox, 20, 47, 280, 1, 00495B4Ah
|
||||||
|
|
||||||
|
; 5. Track Selection List (10 rows, step Y = 13)
|
||||||
|
mov rowY, 55
|
||||||
|
xor esi, esi
|
||||||
|
jbox_render_row:
|
||||||
|
mov color, 0090B19Fh ; Default muted
|
||||||
|
cmp esi, jboxSelectedTrack
|
||||||
|
jne @F
|
||||||
|
mov color, 00F2E6C6h ; Bright cream for selection
|
||||||
|
invoke DrawString8x8, 14, rowY, OFFSET jboxCursor, 00E7C479h
|
||||||
|
@@:
|
||||||
|
mov edx, jboxTrackNames[esi*4]
|
||||||
|
invoke DrawString8x8, 28, rowY, edx, color
|
||||||
|
|
||||||
|
; Mark playing track
|
||||||
|
cmp esi, jboxCurrentTrack
|
||||||
|
jne jbox_row_next
|
||||||
|
cmp jboxIsPlaying, 1
|
||||||
|
jne jbox_row_next
|
||||||
|
invoke DrawString8x8, 252, rowY, OFFSET jboxPlayingTag, 008CD8AFh
|
||||||
|
|
||||||
|
jbox_row_next:
|
||||||
|
add rowY, 13
|
||||||
|
inc esi
|
||||||
|
cmp esi, JACKBOX_TRACK_COUNT
|
||||||
|
jb jbox_render_row
|
||||||
|
|
||||||
|
; 6. Footer Controls Help
|
||||||
|
invoke DrawBox, 20, 192, 280, 1, 00314538h
|
||||||
|
invoke DrawString8x8, 20, 200, OFFSET jboxHelp1, 00C1A982h
|
||||||
|
invoke DrawString8x8, 20, 214, OFFSET jboxHelp2, 0090B19Fh
|
||||||
|
|
||||||
|
ret
|
||||||
|
RenderJackbox3D ENDP
|
||||||
78
src_graphics/jackbox_data.inc
Normal file
78
src_graphics/jackbox_data.inc
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
; ==============================================================================
|
||||||
|
; JACKBOX / JUKEBOX DATA - MASM32 / CP1251
|
||||||
|
; ==============================================================================
|
||||||
|
|
||||||
|
MODE_JACKBOX equ 28
|
||||||
|
INTERACT_JACKBOX equ 48
|
||||||
|
|
||||||
|
JACKBOX_X equ 6*FP_1
|
||||||
|
JACKBOX_Y equ 20*FP_1
|
||||||
|
JACKBOX_TRACK_COUNT equ 10
|
||||||
|
|
||||||
|
MATERIAL_JACKBOX_FRONT equ 161
|
||||||
|
MATERIAL_JACKBOX_SIDE equ 162
|
||||||
|
|
||||||
|
LoadJackboxArt3D PROTO
|
||||||
|
FreeJackboxArt3D PROTO
|
||||||
|
ResolveJackboxSurface3D PROTO :DWORD
|
||||||
|
RenderJackboxCabinet3D PROTO
|
||||||
|
JackboxCanWalk3D PROTO :DWORD,:DWORD
|
||||||
|
PlayJukeboxTrack3D PROTO :DWORD
|
||||||
|
StopJukeboxTrack3D PROTO
|
||||||
|
BeginJackbox3D PROTO
|
||||||
|
HandleJackbox3D PROTO
|
||||||
|
RenderJackbox3D PROTO
|
||||||
|
|
||||||
|
.data
|
||||||
|
jackboxWorldArt3D AnimPack3D <>
|
||||||
|
jackboxScreenArt3D AnimPack3D <>
|
||||||
|
jackboxWorldPath3D db "assets\animations\hall\jackbox-v1.bin",0
|
||||||
|
jackboxScreenPath3D db "assets\animations\hall\jackbox-ui-v1.bin",0
|
||||||
|
|
||||||
|
jackboxPrompt3D db "E: ÄÆÓÊÁÎÊÑ",0
|
||||||
|
|
||||||
|
jboxCurrentTrack dd -1
|
||||||
|
jboxSelectedTrack dd 0
|
||||||
|
jboxIsPlaying dd 0
|
||||||
|
|
||||||
|
jboxCloseCmd db "close jboxmusic",0
|
||||||
|
jboxStopCmd db "stop jboxmusic",0
|
||||||
|
jboxPlayCmd db "play jboxmusic repeat",0
|
||||||
|
|
||||||
|
jboxOpen0 db 'open "assets\music\tower\Keygen_Church_-_Tenebre_Rosso_Sangue_ULTRAKILL_Original_Game_Soundtrack_79610890.mp3" type mpegvideo alias jboxmusic',0
|
||||||
|
jboxOpen1 db 'open "assets\music\tower\KEYGEN_CHURCH_-_Hareklavit_75543927.mp3" type mpegvideo alias jboxmusic',0
|
||||||
|
jboxOpen2 db 'open "assets\music\tower\KEYGEN_CHURCH_-_Ladikerfos_75543991.mp3" type mpegvideo alias jboxmusic',0
|
||||||
|
jboxOpen3 db 'open "assets\music\tower\KEYGEN_CHURCH_-_Tilaskarot_75546826.mp3" type mpegvideo alias jboxmusic',0
|
||||||
|
jboxOpen4 db 'open "assets\music\tower\Mick_Gordon_-_BFG_Division_48126042.mp3" type mpegvideo alias jboxmusic',0
|
||||||
|
jboxOpen5 db 'open "assets\music\tower\Mick_Gordon_-_Rip_Tear_48126019.mp3" type mpegvideo alias jboxmusic',0
|
||||||
|
jboxOpen6 db 'open "assets\music\tower\Mick_Gordon_-_The_Only_Thing_They_Fear_Is_You_OST_DOOM_Eternal_72020713.mp3" type mpegvideo alias jboxmusic',0
|
||||||
|
jboxOpen7 db 'open "assets\audio\hakari_dance.wav" type waveaudio alias jboxmusic',0
|
||||||
|
jboxOpen8 db 'open "assets\audio\victory-tf2.wav" type waveaudio alias jboxmusic',0
|
||||||
|
|
||||||
|
jboxOpenCommands dd OFFSET jboxOpen0, OFFSET jboxOpen1, OFFSET jboxOpen2, OFFSET jboxOpen3
|
||||||
|
dd OFFSET jboxOpen4, OFFSET jboxOpen5, OFFSET jboxOpen6, OFFSET jboxOpen7
|
||||||
|
dd OFFSET jboxOpen8, 0
|
||||||
|
|
||||||
|
jboxName0 db "1. Keygen Church - Tenebre Rosso Sangue",0
|
||||||
|
jboxName1 db "2. Keygen Church - Hareklavit",0
|
||||||
|
jboxName2 db "3. Keygen Church - Ladikerfos",0
|
||||||
|
jboxName3 db "4. Keygen Church - Tilaskarot",0
|
||||||
|
jboxName4 db "5. Mick Gordon - BFG Division (DOOM)",0
|
||||||
|
jboxName5 db "6. Mick Gordon - Rip & Tear (DOOM)",0
|
||||||
|
jboxName6 db "7. Mick Gordon - The Only Thing They Fear",0
|
||||||
|
jboxName7 db "8. Hakari Dance (Jackpot Theme)",0
|
||||||
|
jboxName8 db "9. Team Fortress 2 - Victory Theme",0
|
||||||
|
jboxName9 db "0. [ ÂÛÊËÞ×ÈÒÜ ÌÓÇÛÊÓ / ÒÈØÈÍÀ ]",0
|
||||||
|
|
||||||
|
jboxTrackNames dd OFFSET jboxName0, OFFSET jboxName1, OFFSET jboxName2, OFFSET jboxName3
|
||||||
|
dd OFFSET jboxName4, OFFSET jboxName5, OFFSET jboxName6, OFFSET jboxName7
|
||||||
|
dd OFFSET jboxName8, OFFSET jboxName9
|
||||||
|
|
||||||
|
jboxTitle db "ÌÓÇÛÊÀËÜÍÛÉ ÀÂÒÎÌÀÒ",0
|
||||||
|
jboxClose db "ÂÛÕÎÄ [ESC]",0
|
||||||
|
jboxStatusPlaying db "ÑÅÉ×ÀÑ ÈÃÐÀÅÒ:",0
|
||||||
|
jboxStatusStopped db "ÌÓÇÛÊÀ ÂÛÊËÞ×ÅÍÀ",0
|
||||||
|
jboxCursor db "> ",0
|
||||||
|
jboxPlayingTag db " [ÈÃÐÀÅÒ]",0
|
||||||
|
jboxHelp1 db "ÑÒÐÅËÊÈ / 0-9: Âûáîð ENTER: Âêëþ÷èòü",0
|
||||||
|
jboxHelp2 db "S: Ñòîï R: Ñëó÷àéíûé ESC:  çàë",0
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
LoadWorldDressing3D PROC uses ebx esi
|
LoadWorldDressing3D PROC uses ebx esi
|
||||||
call LoadTerminalArt3D
|
call LoadTerminalArt3D
|
||||||
|
call LoadJackboxArt3D
|
||||||
invoke LoadAnimPack3D,OFFSET worldDressingArt3D,OFFSET worldDressingPath3D
|
invoke LoadAnimPack3D,OFFSET worldDressingArt3D,OFFSET worldDressingPath3D
|
||||||
invoke ValidateCityPack3D,OFFSET worldDressingArt3D,128,128,3
|
invoke ValidateCityPack3D,OFFSET worldDressingArt3D,128,128,3
|
||||||
cmp worldStreetSized3D,0
|
cmp worldStreetSized3D,0
|
||||||
|
|
@ -89,6 +90,7 @@ UpdateStreetGround3D ENDP
|
||||||
|
|
||||||
FreeWorldDressing3D PROC
|
FreeWorldDressing3D PROC
|
||||||
call FreeTerminalArt3D
|
call FreeTerminalArt3D
|
||||||
|
call FreeJackboxArt3D
|
||||||
invoke FreeAnimPack3D,OFFSET worldDressingArt3D
|
invoke FreeAnimPack3D,OFFSET worldDressingArt3D
|
||||||
mov hallLightReady3D,0
|
mov hallLightReady3D,0
|
||||||
ret
|
ret
|
||||||
|
|
@ -115,6 +117,7 @@ RenderHallDressing3D PROC uses ebx esi edi
|
||||||
cmp hallLevel3D,0
|
cmp hallLevel3D,0
|
||||||
jne hall_dressing_done
|
jne hall_dressing_done
|
||||||
call RenderTerminalCabinets3D
|
call RenderTerminalCabinets3D
|
||||||
|
call RenderJackboxCabinet3D
|
||||||
call DrawHallDarts3D
|
call DrawHallDarts3D
|
||||||
lea esi,hallWallArt3D
|
lea esi,hallWallArt3D
|
||||||
mov edi,WORLD_WALL_ART_COUNT
|
mov edi,WORLD_WALL_ART_COUNT
|
||||||
|
|
|
||||||
41
tools/generators/pack_jackbox.py
Normal file
41
tools/generators/pack_jackbox.py
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
"""Offline packaging of Jackbox / Jukebox art for the MASM32 assembly renderer."""
|
||||||
|
from pathlib import Path
|
||||||
|
import struct
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
SOURCE = ROOT / "assets/sprites/jackbox"
|
||||||
|
OUT = ROOT / "assets/animations/hall"
|
||||||
|
|
||||||
|
|
||||||
|
def pack(path: Path, frames: list):
|
||||||
|
w, h = frames[0].size
|
||||||
|
raw = b"".join(image.tobytes("raw", "BGRA") for image in frames)
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
path.write_bytes(struct.pack("<4s4I", b"ANM1", w, h, len(frames), len(raw)) + raw)
|
||||||
|
print(f"Packed {len(frames)} frame(s) ({w}x{h}) into {path.name}: {len(raw)} bytes")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
front_path = SOURCE / "Jackbox_front.jpg"
|
||||||
|
side_path = SOURCE / "Jackbox_side.jpg"
|
||||||
|
ui_path = SOURCE / "Jackbox_UI.jpg"
|
||||||
|
|
||||||
|
assert front_path.exists(), f"Missing {front_path}"
|
||||||
|
assert side_path.exists(), f"Missing {side_path}"
|
||||||
|
assert ui_path.exists(), f"Missing {ui_path}"
|
||||||
|
|
||||||
|
# 1. World Cabinet Pack: 2 frames (256x512 BGRA)
|
||||||
|
front_img = Image.open(front_path).convert("RGBA").resize((256, 512), Image.Resampling.LANCZOS)
|
||||||
|
side_img = Image.open(side_path).convert("RGBA").resize((256, 512), Image.Resampling.LANCZOS)
|
||||||
|
pack(OUT / "jackbox-v1.bin", [front_img, side_img])
|
||||||
|
|
||||||
|
# 2. Screen UI Pack: 1 frame (640x480 BGRA)
|
||||||
|
ui_img = Image.open(ui_path).convert("RGBA").resize((640, 480), Image.Resampling.LANCZOS)
|
||||||
|
pack(OUT / "jackbox-ui-v1.bin", [ui_img])
|
||||||
|
|
||||||
|
print("Jackbox asset packaging complete.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
114
tools/tests/check_jackbox.py
Normal file
114
tools/tests/check_jackbox.py
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
"""Verification test suite for the Jukebox / Jackbox music system."""
|
||||||
|
import ctypes
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
import struct
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
|
||||||
|
|
||||||
|
def test_packed_assets():
|
||||||
|
world_bin = ROOT / "assets/animations/hall/jackbox-v1.bin"
|
||||||
|
ui_bin = ROOT / "assets/animations/hall/jackbox-ui-v1.bin"
|
||||||
|
|
||||||
|
assert world_bin.is_file(), f"Missing {world_bin}"
|
||||||
|
assert ui_bin.is_file(), f"Missing {ui_bin}"
|
||||||
|
|
||||||
|
# Check world bin header: ANM1, 256, 512, 2 frames
|
||||||
|
wdata = world_bin.read_bytes()
|
||||||
|
magic, w, h, frames, raw_len = struct.unpack("<4s4I", wdata[:20])
|
||||||
|
assert magic == b"ANM1", f"Bad magic: {magic}"
|
||||||
|
assert (w, h, frames) == (256, 512, 2), f"Bad dimensions: {w}x{h}, {frames} frames"
|
||||||
|
assert len(wdata) == 20 + raw_len == 20 + 256 * 512 * 4 * 2
|
||||||
|
print(f"[OK] jackbox-v1.bin verified: {frames} frames ({w}x{h}), {raw_len} bytes")
|
||||||
|
|
||||||
|
# Check UI bin header: ANM1, 640, 480, 1 frame
|
||||||
|
uidata = ui_bin.read_bytes()
|
||||||
|
magic, w, h, frames, raw_len = struct.unpack("<4s4I", uidata[:20])
|
||||||
|
assert magic == b"ANM1", f"Bad magic: {magic}"
|
||||||
|
assert (w, h, frames) == (640, 480, 1), f"Bad dimensions: {w}x{h}, {frames} frames"
|
||||||
|
assert len(uidata) == 20 + raw_len == 20 + 640 * 480 * 4
|
||||||
|
print(f"[OK] jackbox-ui-v1.bin verified: {frames} frame ({w}x{h}), {raw_len} bytes")
|
||||||
|
|
||||||
|
|
||||||
|
def test_audio_tracks():
|
||||||
|
data_inc = (ROOT / "src_graphics/jackbox_data.inc").read_bytes().decode("cp1251")
|
||||||
|
open_cmds = re.findall(r'open\s+"([^"]+)"\s+type\s+(\w+)\s+alias\s+jboxmusic', data_inc)
|
||||||
|
assert len(open_cmds) == 9, f"Expected 9 audio tracks, got {len(open_cmds)}"
|
||||||
|
|
||||||
|
for rel_path, media_type in open_cmds:
|
||||||
|
full_path = ROOT / rel_path
|
||||||
|
assert full_path.is_file(), f"Audio file missing: {rel_path}"
|
||||||
|
# Test MCI open & close
|
||||||
|
cmd_open = f'open "{full_path}" type {media_type} alias testjbox'
|
||||||
|
err = ctypes.windll.winmm.mciSendStringW(cmd_open, None, 0, None)
|
||||||
|
assert err == 0, f"MCI failed to open {rel_path}: error {err}"
|
||||||
|
try:
|
||||||
|
length_buf = ctypes.create_unicode_buffer(64)
|
||||||
|
err = ctypes.windll.winmm.mciSendStringW("status testjbox length", length_buf, 64, None)
|
||||||
|
assert err == 0, f"MCI failed to query length of {rel_path}"
|
||||||
|
finally:
|
||||||
|
ctypes.windll.winmm.mciSendStringW("close testjbox", None, 0, None)
|
||||||
|
print(f"[OK] MCI verified track: {rel_path} ({media_type})")
|
||||||
|
|
||||||
|
|
||||||
|
def test_code_structure():
|
||||||
|
data_inc = (ROOT / "src_graphics/jackbox_data.inc").read_bytes().decode("cp1251")
|
||||||
|
code_inc = (ROOT / "src_graphics/jackbox.inc").read_bytes().decode("cp1251")
|
||||||
|
casino_inc = (ROOT / "casino3d.inc").read_bytes().decode("cp1251")
|
||||||
|
hall_inc = (ROOT / "src_graphics/hall_scene.inc").read_bytes().decode("cp1251")
|
||||||
|
world_inc = (ROOT / "src_graphics/world_dressing.inc").read_bytes().decode("cp1251")
|
||||||
|
city_inc = (ROOT / "src_graphics/city_bins.inc").read_bytes().decode("cp1251")
|
||||||
|
|
||||||
|
# Check constants
|
||||||
|
assert "MODE_JACKBOX equ 28" in data_inc
|
||||||
|
assert "INTERACT_JACKBOX equ 48" in data_inc
|
||||||
|
assert "JACKBOX_X equ 6*FP_1" in data_inc
|
||||||
|
assert "JACKBOX_Y equ 20*FP_1" in data_inc
|
||||||
|
|
||||||
|
# Check procs
|
||||||
|
procs = [
|
||||||
|
"LoadJackboxArt3D", "FreeJackboxArt3D", "ResolveJackboxSurface3D",
|
||||||
|
"DrawJackboxCabinet3D", "RenderJackboxCabinet3D", "JackboxCanWalk3D",
|
||||||
|
"PlayJukeboxTrack3D", "StopJukeboxTrack3D", "BeginJackbox3D",
|
||||||
|
"HandleJackbox3D", "RenderJackbox3D"
|
||||||
|
]
|
||||||
|
for p in procs:
|
||||||
|
assert f"{p} PROC" in code_inc, f"Missing proc: {p}"
|
||||||
|
|
||||||
|
# Check integrations
|
||||||
|
assert "include src_graphics\\jackbox_data.inc" in casino_inc
|
||||||
|
assert "include src_graphics\\jackbox.inc" in casino_inc
|
||||||
|
assert "MODE_JACKBOX" in casino_inc
|
||||||
|
assert "HandleJackbox3D" in casino_inc
|
||||||
|
assert "RenderJackbox3D" in casino_inc
|
||||||
|
|
||||||
|
assert "call LoadJackboxArt3D" in world_inc
|
||||||
|
assert "call FreeJackboxArt3D" in world_inc
|
||||||
|
assert "call RenderJackboxCabinet3D" in world_inc
|
||||||
|
|
||||||
|
assert "INTERACT_JACKBOX" in hall_inc
|
||||||
|
assert "BeginJackbox3D" in hall_inc
|
||||||
|
assert "JackboxCanWalk3D" in hall_inc
|
||||||
|
|
||||||
|
assert "ResolveJackboxSurface3D" in city_inc
|
||||||
|
print("[OK] Code structure and all integrations verified.")
|
||||||
|
|
||||||
|
|
||||||
|
def test_binary_executable():
|
||||||
|
exe_path = ROOT / "poker.exe"
|
||||||
|
assert exe_path.is_file()
|
||||||
|
exe_bytes = exe_path.read_bytes()
|
||||||
|
|
||||||
|
assert b"jackbox-v1.bin" in exe_bytes
|
||||||
|
assert b"jackbox-ui-v1.bin" in exe_bytes
|
||||||
|
assert b"jboxmusic" in exe_bytes
|
||||||
|
print(f"[OK] poker.exe verified with embedded Jackbox strings ({len(exe_bytes)} bytes).")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_packed_assets()
|
||||||
|
test_audio_tracks()
|
||||||
|
test_code_structure()
|
||||||
|
test_binary_executable()
|
||||||
|
print("\nALL JACKBOX / JUKEBOX VERIFICATION CHECKS PASSED!")
|
||||||
Loading…
Add table
Reference in a new issue