Compare commits
11 commits
23dc40ee5c
...
eef2039c7e
| Author | SHA1 | Date | |
|---|---|---|---|
| eef2039c7e | |||
| 75d594ec79 | |||
| d4436674b4 | |||
| 4c2428c367 | |||
| 7c262a15ad | |||
| 6bf2a2885c | |||
| 0a85e271e9 | |||
| 82f3aa9437 | |||
| 5aec3ded9f | |||
| 2ab7c9ed40 | |||
| 4ca7912562 |
2
.gitignore
vendored
|
|
@ -48,3 +48,5 @@ __pycache__/
|
||||||
/debug_render.png
|
/debug_render.png
|
||||||
/render_view.png
|
/render_view.png
|
||||||
/docs/*.zip
|
/docs/*.zip
|
||||||
|
|
||||||
|
/scratch/
|
||||||
|
|
|
||||||
BIN
assets/animations/buckshot/dealer-actions-v3-hell.bin
Normal file
BIN
assets/animations/buckshot/dealer-v2-hell.bin
Normal file
BIN
assets/animations/buckshot/death-v3-hell.bin
Normal file
BIN
assets/animations/buckshot/room-v2-hell.bin
Normal file
BIN
assets/animations/coin/table-v1-hell.bin
Normal file
BIN
assets/animations/hall/helltower-menu.bin
Normal file
BIN
assets/animations/hall/jackbox-ui-v1.bin
Normal file
BIN
assets/animations/hall/jackbox-v1.bin
Normal file
BIN
assets/animations/wheel/closeup-v1-hell.bin
Normal file
BIN
assets/animations/wheel/table-v1-hell.bin
Normal file
BIN
assets/hell_tower/01_coin/faces-v1.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
assets/hell_tower/01_coin/hands-v1.png
Normal file
|
After Width: | Height: | Size: 847 KiB |
BIN
assets/hell_tower/01_coin/hands-v2.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
assets/hell_tower/01_coin/table-v1-hell.jpg
Normal file
|
After Width: | Height: | Size: 2.6 MiB |
BIN
assets/hell_tower/02_dice/hands-v1.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
assets/hell_tower/02_dice/hands-v2.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 2.8 MiB |
BIN
assets/hell_tower/06_russian_roulette/dealer-source-v2-hell.png
Normal file
|
After Width: | Height: | Size: 4.6 MiB |
BIN
assets/hell_tower/06_russian_roulette/gman-talk-v1.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
assets/hell_tower/06_russian_roulette/hands-v5.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
assets/hell_tower/06_russian_roulette/revolver-actions-v1.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
assets/hell_tower/06_russian_roulette/room-v2-hell.jpg
Normal file
|
After Width: | Height: | Size: 2.6 MiB |
BIN
assets/hell_tower/07_casino_roulette/closeup-v1-hell.jpg
Normal file
|
After Width: | Height: | Size: 3 MiB |
BIN
assets/hell_tower/07_casino_roulette/table-v1-hell.jpg
Normal file
|
After Width: | Height: | Size: 2.9 MiB |
BIN
assets/hell_tower/10_buckshot/blood-v3.png
Normal file
|
After Width: | Height: | Size: 576 KiB |
BIN
assets/hell_tower/10_buckshot/dealer-actions-v3-hell.png
Normal file
|
After Width: | Height: | Size: 5.1 MiB |
BIN
assets/hell_tower/10_buckshot/dealer-source-v2-hell.png
Normal file
|
After Width: | Height: | Size: 4.6 MiB |
BIN
assets/hell_tower/10_buckshot/death-v3-hell.png
Normal file
|
After Width: | Height: | Size: 3.7 MiB |
BIN
assets/hell_tower/10_buckshot/items-v2.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
assets/hell_tower/10_buckshot/loading-v2.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
assets/hell_tower/10_buckshot/medical-dealer-v5.png
Normal file
|
After Width: | Height: | Size: 3.1 MiB |
BIN
assets/hell_tower/10_buckshot/medical-player-v5.png
Normal file
|
After Width: | Height: | Size: 3.3 MiB |
BIN
assets/hell_tower/10_buckshot/medical-v3.png
Normal file
|
After Width: | Height: | Size: 3.2 MiB |
BIN
assets/hell_tower/10_buckshot/props-v2.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
assets/hell_tower/10_buckshot/room-v2-hell.jpg
Normal file
|
After Width: | Height: | Size: 2.6 MiB |
BIN
assets/hell_tower/10_buckshot/self-flash-v4.png
Normal file
|
After Width: | Height: | Size: 1,005 KiB |
BIN
assets/hell_tower/10_buckshot/self-v3.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
assets/hell_tower/10_buckshot/shots-v2.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
68
assets/hell_tower/README.md
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
# Спрайты для адской версии игр Башни (Hell Tower)
|
||||||
|
|
||||||
|
В этой папке собраны **только визуальные спрайты (PNG)**, которые непосредственно используются в играх Адской Башни. Вы можете перерисовать их в демоническом / адском стиле (кровь, руны, пламя, черепа, черные металлы).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Этаж 1: Орел и решка (`01_coin`)
|
||||||
|
*Босс: БЕС АЗАРТА*
|
||||||
|
- `faces-v1.png` — Лицевая и обратная сторона монеты (анимация вращения / стороны).
|
||||||
|
- `hands-v1.png` / `hands-v2.png` — Руки игрока / демона, подбрасывающие монету.
|
||||||
|
- `table-v1.png` — Стол / сукно, на которое падает монета.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Этаж 2: Кости (`02_dice`)
|
||||||
|
*Босс: ДЕМОН КОСТЕЙ*
|
||||||
|
- `hands-v1.png` / `hands-v2.png` — Руки, бросающие игральные кости (кубики).
|
||||||
|
*(Стол используется общий с `01_coin/table-v1.png`)*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Этажи 4, 5, 8: Карточные игры 21, Покер, Дурак (`04_05_08_card_tables`)
|
||||||
|
*Боссы: КРУПЬЕ ПРЕИСПОДНЕЙ, ДЕМОН БЛЕФА, КАРТОЧНЫЙ ЧЕРТ*
|
||||||
|
- `blackjack-guests-v1.png` — Силуэты гостей / грешников за карточным столом.
|
||||||
|
- `blackjack-room-v1.png` — Фон комнаты карточных дуэлей.
|
||||||
|
- `card-table-v1.png`, `card-table-active-v2.png`, `card-table-pixel-v3.png` — Варианты карточного стола и сукна.
|
||||||
|
- `sprite_blackjack_table.png` — 3D-биллборд стола блэкджека в холле/башне.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Этаж 6: Русская рулетка (`06_russian_roulette`)
|
||||||
|
*Босс: ОБРЕЧЕННЫЙ ГРЕШНИК*
|
||||||
|
- `revolver-actions-v1.png` — Револьвер (барабан, курок, взвод, наведение).
|
||||||
|
- `hands-v5.png` — Руки от первого лица с револьвером у виска / стола.
|
||||||
|
- `participants-v1.png` — Сидящие напротив оппоненты русской рулетки.
|
||||||
|
- `gman-talk-v1.png` — Ведущий / распорядитель игры.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Этаж 7: Казино-рулетка (`07_casino_roulette`)
|
||||||
|
*Босс: КОЛЕСО СУДЬБЫ*
|
||||||
|
- `closeup-v1.png` — Крупный план вращающегося колеса рулетки.
|
||||||
|
- `table-v1.png` — Стол ставок и разметки чисел рулетки.
|
||||||
|
- `sprite_roulette_table.png` — 3D-биллборд стола рулетки.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Этаж 10: Buckshot Roulette (`10_buckshot`)
|
||||||
|
*Босс: ВЛАДЫКА БАШНИ*
|
||||||
|
- `dealer-actions-v3.png` — Спрайты дилера (прицеливание, передача дробовика, действия).
|
||||||
|
- `dealer-source-v2.png` — Базовый портрет дилера напротив игрока.
|
||||||
|
- `death-v3.png` — Спрайты смерти / поражения.
|
||||||
|
- `items-v2.png` — Предметы на столе (ножовка, сигареты, пиво, лупа, наручники).
|
||||||
|
- `loading-v2.png` — Заряжание боевых и холостых патронов в патронник.
|
||||||
|
- `medical-dealer-v5.png` / `medical-player-v5.png` / `medical-v3.png` — Дефибрилляторы и реанимация.
|
||||||
|
- `props-v2.png` — Реквизит стола: патроны, коробка, дробовик.
|
||||||
|
- `room-v2.png` — Фон комнаты смертельного поединка.
|
||||||
|
- `self-flash-v4.png` — Вспышка и выстрел в себя.
|
||||||
|
- `self-v3.png` — Наведение дробовика на себя.
|
||||||
|
- `shots-v2.png` — Выстрелы в дилера.
|
||||||
|
- `blood-v3.png` — Брызги крови, повреждения.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Текстуры окружения (`environment`)
|
||||||
|
- `tex_wall_hell.png` — Текстура адских стен Башни.
|
||||||
|
|
||||||
|
*(Примечание: На этажах 3 (Черепа) и 9 (Крюк: Skill Check) используются процедурные векторные элементы 3D-движка (`DrawGothicCup`, разметка шкалы) либо консольная ANSI-анимация `dbd_hook.ans`).*
|
||||||
BIN
assets/hell_tower/environment/HellTower_ menu.jpg
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
assets/hell_tower/environment/tex_wall_hell.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
assets/music/jackbox/BYBOLDEN. - Art Deco.mp3
Normal file
BIN
assets/music/jackbox/BYBOLDEN. - Art Deco.opus
Normal file
BIN
assets/music/jackbox/BYBOLDEN. - Soft Hours.mp3
Normal file
BIN
assets/music/jackbox/BYBOLDEN. - Soft Hours.opus
Normal file
BIN
assets/sprites/jackbox/Jackbox_UI.jpg
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
assets/sprites/jackbox/Jackbox_front.jpg
Normal file
|
After Width: | Height: | Size: 3.1 MiB |
BIN
assets/sprites/jackbox/Jackbox_side.jpg
Normal file
|
After Width: | Height: | Size: 2.5 MiB |
BIN
assets/textures/card-table-hell.bin
Normal file
BIN
bin/poker.exe
12
build.bat
|
|
@ -15,6 +15,18 @@ if exist "%VS_COMM_BIN%\ml.exe" (
|
||||||
goto do_build
|
goto do_build
|
||||||
)
|
)
|
||||||
|
|
||||||
|
rem Check Visual Studio BuildTools
|
||||||
|
set "MSVC_BIN=C:\Program Files (x86)\Microsoft Visual Studio\18\BuildTools\VC\Tools\MSVC\14.50.35717\bin\Hostx64\x86"
|
||||||
|
set "VC_LIB=C:\Program Files (x86)\Microsoft Visual Studio\18\BuildTools\VC\Tools\MSVC\14.50.35717\lib\x86"
|
||||||
|
|
||||||
|
if exist "%MSVC_BIN%\ml.exe" (
|
||||||
|
set "ML_EXE=%MSVC_BIN%\ml.exe"
|
||||||
|
set "LINK_EXE=%MSVC_BIN%\link.exe"
|
||||||
|
set LIB_PATHS=/LIBPATH:"%SDK_LIB%" /LIBPATH:"%UCRT_LIB%" /LIBPATH:"%VC_LIB%"
|
||||||
|
set "EXTRA_FLAGS=/NOLOGO /SUBSYSTEM:WINDOWS"
|
||||||
|
goto do_build
|
||||||
|
)
|
||||||
|
|
||||||
rem Check standard MASM32 paths
|
rem Check standard MASM32 paths
|
||||||
if exist "F:\MASM32\masm32\bin\ml.exe" (
|
if exist "F:\MASM32\masm32\bin\ml.exe" (
|
||||||
set "ML_EXE=F:\MASM32\masm32\bin\ml.exe"
|
set "ML_EXE=F:\MASM32\masm32\bin\ml.exe"
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ namespace AssemblerCasinoLauncher
|
||||||
{
|
{
|
||||||
static class Program
|
static class Program
|
||||||
{
|
{
|
||||||
const string ExpectedPokerSha256 = "C3D857FDBF7597C976B16C5021D7F07951628E54F86FF2B7BB53F8A83FDE11CA";
|
const string ExpectedPokerSha256 = "07D474A0223390E7AC4F4F779818D77CC2DAE5123F4BDB0F72DD975473267310";
|
||||||
|
|
||||||
[STAThread]
|
[STAThread]
|
||||||
static void Main()
|
static void Main()
|
||||||
|
|
|
||||||
14
casino3d.inc
|
|
@ -173,6 +173,7 @@ 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
|
||||||
|
|
@ -5198,6 +5199,7 @@ 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
|
||||||
|
|
@ -5344,6 +5346,7 @@ check_walk_mode:
|
||||||
call UpdateCamera3D
|
call UpdateCamera3D
|
||||||
call CheckInteractions
|
call CheckInteractions
|
||||||
call HandlePlayerInput
|
call HandlePlayerInput
|
||||||
|
call HandleJukeboxKeys3D
|
||||||
call UpdateInteriorTravel3D
|
call UpdateInteriorTravel3D
|
||||||
call UpdateInteriorEye3D
|
call UpdateInteriorEye3D
|
||||||
call UpdateCamera3D
|
call UpdateCamera3D
|
||||||
|
|
@ -5356,6 +5359,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
|
||||||
|
|
@ -5423,6 +5434,7 @@ check_darts_done:
|
||||||
cmp engineMode, MODE_STAIRS
|
cmp engineMode, MODE_STAIRS
|
||||||
jne @F
|
jne @F
|
||||||
call HandleStairs3D
|
call HandleStairs3D
|
||||||
|
call HandleJukeboxKeys3D
|
||||||
call RenderStairs3D
|
call RenderStairs3D
|
||||||
jmp present_frame
|
jmp present_frame
|
||||||
@@:
|
@@:
|
||||||
|
|
@ -5584,7 +5596,9 @@ pause_low_spec_saved:
|
||||||
call SaveProgress3D
|
call SaveProgress3D
|
||||||
jmp engine_shutdown
|
jmp engine_shutdown
|
||||||
present_frame:
|
present_frame:
|
||||||
|
call UpdateJukeboxAudio3D
|
||||||
call RenderTowerInGameBadge
|
call RenderTowerInGameBadge
|
||||||
|
call RenderJukeboxVolumeHUD
|
||||||
mov eax, engineMode
|
mov eax, engineMode
|
||||||
cmp eax, lastPersistMode3D
|
cmp eax, lastPersistMode3D
|
||||||
je @F
|
je @F
|
||||||
|
|
|
||||||
BIN
dist/Assembler Casino.exe
vendored
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
docs/previews_minigames/jukebox_3d_angle.png
Normal file
|
After Width: | Height: | Size: 246 KiB |
BIN
docs/previews_minigames/jukebox_3d_close.png
Normal file
|
After Width: | Height: | Size: 223 KiB |
BIN
docs/previews_minigames/jukebox_3d_front.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
docs/previews_minigames/jukebox_3d_side.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
120
poker.asm
|
|
@ -514,28 +514,9 @@ StopTowerMusic PROC
|
||||||
ret
|
ret
|
||||||
StopTowerMusic ENDP
|
StopTowerMusic ENDP
|
||||||
|
|
||||||
PlayTowerRandomMusic PROC uses eax
|
PlayTowerRandomMusic PROC
|
||||||
|
; Tower music disabled for now per user request
|
||||||
call StopTowerMusic
|
call StopTowerMusic
|
||||||
invoke RandomRange, 4
|
|
||||||
cmp eax, 0
|
|
||||||
je tower_music_1
|
|
||||||
cmp eax, 1
|
|
||||||
je tower_music_2
|
|
||||||
cmp eax, 2
|
|
||||||
je tower_music_3
|
|
||||||
tower_music_4:
|
|
||||||
invoke mciSendStringA, OFFSET towerMusicOpen4, 0, 0, 0
|
|
||||||
jmp tower_music_play
|
|
||||||
tower_music_1:
|
|
||||||
invoke mciSendStringA, OFFSET towerMusicOpen1, 0, 0, 0
|
|
||||||
jmp tower_music_play
|
|
||||||
tower_music_2:
|
|
||||||
invoke mciSendStringA, OFFSET towerMusicOpen2, 0, 0, 0
|
|
||||||
jmp tower_music_play
|
|
||||||
tower_music_3:
|
|
||||||
invoke mciSendStringA, OFFSET towerMusicOpen3, 0, 0, 0
|
|
||||||
tower_music_play:
|
|
||||||
invoke mciSendStringA, OFFSET towerMusicPlay, 0, 0, 0
|
|
||||||
ret
|
ret
|
||||||
PlayTowerRandomMusic ENDP
|
PlayTowerRandomMusic ENDP
|
||||||
|
|
||||||
|
|
@ -1923,6 +1904,103 @@ CheckDurakEnd ENDP
|
||||||
|
|
||||||
include casino3d.inc
|
include casino3d.inc
|
||||||
|
|
||||||
|
|
||||||
|
IFDEF CASINO_ACTION_TEST
|
||||||
|
RunCasinoActionsTest3D PROC uses ebx esi edi
|
||||||
|
local stageLoop:DWORD
|
||||||
|
|
||||||
|
call InitPaths3D
|
||||||
|
call Init3DEngine
|
||||||
|
test eax, eax
|
||||||
|
jz test_error_init
|
||||||
|
|
||||||
|
; Enter Hell Tower
|
||||||
|
call InitHellTower3D
|
||||||
|
cmp tournamentMode, 1
|
||||||
|
jne test_error_tower_mode
|
||||||
|
|
||||||
|
mov stageLoop, 1
|
||||||
|
stage_test_loop:
|
||||||
|
; 1. Briefing state frame
|
||||||
|
call HandleHellTowerInput
|
||||||
|
call RenderHellTower3D
|
||||||
|
call Present3D
|
||||||
|
|
||||||
|
; 2. Launch current floor minigame
|
||||||
|
call TowerLaunchCurrentFloor
|
||||||
|
cmp towerFloorState, TOWER_STATE_IN_GAME
|
||||||
|
jne test_error_launch
|
||||||
|
|
||||||
|
; 3. Win the round on current floor
|
||||||
|
invoke TowerReportRoundOutcome, 1
|
||||||
|
|
||||||
|
; 4. Check outcome state
|
||||||
|
cmp stageLoop, 10
|
||||||
|
jae stage_check_champ
|
||||||
|
|
||||||
|
cmp towerFloorState, TOWER_STATE_CLEAR
|
||||||
|
jne test_error_clear_state
|
||||||
|
|
||||||
|
; Frame in clear state
|
||||||
|
call HandleHellTowerInput
|
||||||
|
call RenderHellTower3D
|
||||||
|
call Present3D
|
||||||
|
|
||||||
|
; Advance to next stage
|
||||||
|
inc towerStage
|
||||||
|
mov towerFloorState, TOWER_STATE_BRIEFING
|
||||||
|
mov towerCalloutAnim, 0
|
||||||
|
|
||||||
|
inc stageLoop
|
||||||
|
jmp stage_test_loop
|
||||||
|
|
||||||
|
stage_check_champ:
|
||||||
|
cmp towerFloorState, TOWER_STATE_CHAMPION
|
||||||
|
jne test_error_champ_state
|
||||||
|
|
||||||
|
; Frame in champion state
|
||||||
|
call HandleHellTowerInput
|
||||||
|
call RenderHellTower3D
|
||||||
|
call Present3D
|
||||||
|
|
||||||
|
; Exit tower back to hall
|
||||||
|
call TowerExitToHall
|
||||||
|
cmp tournamentMode, 0
|
||||||
|
jne test_error_exit_mode
|
||||||
|
cmp engineMode, MODE_WALK
|
||||||
|
jne test_error_exit_engine
|
||||||
|
|
||||||
|
; Hall walk frame
|
||||||
|
call Present3D
|
||||||
|
|
||||||
|
; SUCCESS!
|
||||||
|
mov eax, 0
|
||||||
|
ret
|
||||||
|
|
||||||
|
test_error_init:
|
||||||
|
mov eax, 101
|
||||||
|
ret
|
||||||
|
test_error_tower_mode:
|
||||||
|
mov eax, 102
|
||||||
|
ret
|
||||||
|
test_error_launch:
|
||||||
|
mov eax, 103
|
||||||
|
ret
|
||||||
|
test_error_clear_state:
|
||||||
|
mov eax, 104
|
||||||
|
ret
|
||||||
|
test_error_champ_state:
|
||||||
|
mov eax, 105
|
||||||
|
ret
|
||||||
|
test_error_exit_mode:
|
||||||
|
mov eax, 106
|
||||||
|
ret
|
||||||
|
test_error_exit_engine:
|
||||||
|
mov eax, 107
|
||||||
|
ret
|
||||||
|
RunCasinoActionsTest3D ENDP
|
||||||
|
ENDIF
|
||||||
|
|
||||||
main PROC
|
main PROC
|
||||||
IFDEF CASINO_ACTION_TEST
|
IFDEF CASINO_ACTION_TEST
|
||||||
call RunCasinoActionsTest3D
|
call RunCasinoActionsTest3D
|
||||||
|
|
|
||||||
BIN
poker.exe
BIN
poker.obj
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,11 @@ FreeCardTableAssets3D ENDP
|
||||||
|
|
||||||
LoadCardTableAssets3D PROC uses ebx
|
LoadCardTableAssets3D PROC uses ebx
|
||||||
local count:DWORD
|
local count:DWORD
|
||||||
|
local resolvedPath[260]:BYTE
|
||||||
cmp cardTablePixels3D, 0
|
cmp cardTablePixels3D, 0
|
||||||
jne card_load_ranks
|
jne card_load_ranks
|
||||||
invoke CreateFileA, OFFSET cardTablePath3D, 80000000h, 1, 0, 3, 80h, 0
|
invoke ResolveHellAssetPath, OFFSET cardTablePath3D, ADDR resolvedPath
|
||||||
|
invoke CreateFileA, eax, 80000000h, 1, 0, 3, 80h, 0
|
||||||
cmp eax, -1
|
cmp eax, -1
|
||||||
je card_load_ranks
|
je card_load_ranks
|
||||||
mov ebx, eax
|
mov ebx, eax
|
||||||
|
|
|
||||||
|
|
@ -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,163
|
||||||
|
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
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,30 @@
|
||||||
; --- hell_tower.inc: Implementation of Hell Tower (10-floor Boss Rush) ---
|
; --- hell_tower.inc: Implementation of Hell Tower (10-floor Boss Rush) ---
|
||||||
.code
|
.code
|
||||||
|
|
||||||
|
FreeTowerMinigameAssets PROC
|
||||||
|
call FreeBuckshotAssets3D
|
||||||
|
call FreeRussianAssets3D
|
||||||
|
call FreeCardTableAssets3D
|
||||||
|
call FreeNativeCoinArt3D
|
||||||
|
call FreeNativeWheelArt3D
|
||||||
|
ret
|
||||||
|
FreeTowerMinigameAssets ENDP
|
||||||
|
|
||||||
InitHellTower3D PROC
|
InitHellTower3D PROC
|
||||||
|
call FreeTowerMinigameAssets
|
||||||
mov tournamentMode, 1
|
mov tournamentMode, 1
|
||||||
mov towerStage, 1
|
mov towerStage, 1
|
||||||
mov towerFloorState, TOWER_STATE_BRIEFING
|
mov towerFloorState, TOWER_STATE_BRIEFING
|
||||||
mov towerAnimClock, 0
|
mov towerAnimClock, 0
|
||||||
call PlayTowerRandomMusic
|
mov towerCalloutAnim, 0
|
||||||
|
|
||||||
|
mov eax, towerFloorY[0]
|
||||||
|
mov towerCursorY, eax
|
||||||
|
mov towerCursorTargetY, eax
|
||||||
|
|
||||||
|
invoke LoadAnimPack3D, OFFSET hellTowerMenuArt3D, OFFSET hellTowerMenuPath3D
|
||||||
|
|
||||||
|
call StopTowerMusic
|
||||||
mov engineMode, MODE_HELL_TOWER
|
mov engineMode, MODE_HELL_TOWER
|
||||||
call ClearKeys3D
|
call ClearKeys3D
|
||||||
ret
|
ret
|
||||||
|
|
@ -14,6 +32,7 @@ InitHellTower3D ENDP
|
||||||
|
|
||||||
TowerExitToHall PROC
|
TowerExitToHall PROC
|
||||||
call StopTowerMusic
|
call StopTowerMusic
|
||||||
|
call FreeTowerMinigameAssets
|
||||||
mov tournamentMode, 0
|
mov tournamentMode, 0
|
||||||
mov engineMode, MODE_WALK
|
mov engineMode, MODE_WALK
|
||||||
call ClearKeys3D
|
call ClearKeys3D
|
||||||
|
|
@ -110,6 +129,14 @@ TowerReportRoundOutcome PROC uses eax outcome:DWORD
|
||||||
jae report_champion
|
jae report_champion
|
||||||
|
|
||||||
mov towerFloorState, TOWER_STATE_CLEAR
|
mov towerFloorState, TOWER_STATE_CLEAR
|
||||||
|
mov towerCalloutAnim, 0
|
||||||
|
mov eax, towerStage
|
||||||
|
dec eax
|
||||||
|
cmp eax, 10
|
||||||
|
jae @F
|
||||||
|
mov edx, towerFloorY[eax*4]
|
||||||
|
mov towerCursorTargetY, edx
|
||||||
|
@@:
|
||||||
invoke PlayCasinoSound3D, OFFSET soundRoundWin, 0, 1
|
invoke PlayCasinoSound3D, OFFSET soundRoundWin, 0, 1
|
||||||
mov engineMode, MODE_HELL_TOWER
|
mov engineMode, MODE_HELL_TOWER
|
||||||
call ClearKeys3D
|
call ClearKeys3D
|
||||||
|
|
@ -194,6 +221,14 @@ input_clear:
|
||||||
advance_stage:
|
advance_stage:
|
||||||
inc towerStage
|
inc towerStage
|
||||||
mov towerFloorState, TOWER_STATE_BRIEFING
|
mov towerFloorState, TOWER_STATE_BRIEFING
|
||||||
|
mov towerCalloutAnim, 0
|
||||||
|
mov eax, towerStage
|
||||||
|
dec eax
|
||||||
|
cmp eax, 10
|
||||||
|
jae @F
|
||||||
|
mov edx, towerFloorY[eax*4]
|
||||||
|
mov towerCursorTargetY, edx
|
||||||
|
@@:
|
||||||
call ClearKeys3D
|
call ClearKeys3D
|
||||||
ret
|
ret
|
||||||
|
|
||||||
|
|
@ -227,132 +262,321 @@ exit_to_hall:
|
||||||
HandleHellTowerInput ENDP
|
HandleHellTowerInput ENDP
|
||||||
|
|
||||||
RenderHellTower3D PROC uses ebx esi edi
|
RenderHellTower3D PROC uses ebx esi edi
|
||||||
; 1. Clear background with deep infernal maroon
|
local cardX:DWORD, cardY:DWORD, cardW:DWORD, cardH:DWORD
|
||||||
|
local currY:DWORD, tgtY:DWORD
|
||||||
|
|
||||||
|
; 1. Background: draw HellTower menu art if loaded, else fallback to maroon
|
||||||
|
cmp hellTowerMenuArt3D.pixels, 0
|
||||||
|
je fallback_clear
|
||||||
|
|
||||||
|
cmp hellTowerMenuArt3D.imageWidth, SCREEN_W
|
||||||
|
jne fallback_sprite
|
||||||
|
cmp hellTowerMenuArt3D.height, SCREEN_H
|
||||||
|
jne fallback_sprite
|
||||||
|
|
||||||
|
mov esi, hellTowerMenuArt3D.pixels
|
||||||
|
lea edi, screenBuffer
|
||||||
|
mov ecx, SCREEN_SIZE
|
||||||
|
rep movsd
|
||||||
|
jmp bg_done
|
||||||
|
|
||||||
|
fallback_sprite:
|
||||||
|
invoke DrawAnimSprite3D, OFFSET hellTowerMenuArt3D, 0, 0, 0, SCREEN_W, SCREEN_H
|
||||||
|
jmp bg_done
|
||||||
|
|
||||||
|
fallback_clear:
|
||||||
lea edi, screenBuffer
|
lea edi, screenBuffer
|
||||||
mov ecx, SCREEN_SIZE
|
mov ecx, SCREEN_SIZE
|
||||||
mov eax, 00100202h
|
mov eax, 00100202h
|
||||||
rep stosd
|
rep stosd
|
||||||
|
|
||||||
; 2. Outer and inner border
|
bg_done:
|
||||||
invoke DrawBoxBorder, 8, 8, 304, 224, 00120202h, 00CC2222h, 2
|
; 2. Update towerAnimClock
|
||||||
invoke DrawBoxBorder, 12, 12, 296, 216, 000C0202h, 00881111h, 1
|
inc towerAnimClock
|
||||||
|
|
||||||
; 3. Header Banner (Centered: 192px wide, (320 - 192)/2 = 64)
|
; 3. Smooth cursor Y movement towards towerCursorTargetY (exponential damping)
|
||||||
invoke DrawBoxBorder, 18, 16, 284, 20, 00220404h, 00FF4444h, 1
|
mov eax, towerCursorTargetY
|
||||||
invoke DrawString8x8, 64, 22, OFFSET towerHeader, 00FFDD66h
|
cmp eax, towerCursorY
|
||||||
|
je cursor_update_done
|
||||||
|
jg cursor_move_down
|
||||||
|
|
||||||
; 4. Floor Header & Segment Progress Bar
|
; Moving UP towards tower peak (target < current)
|
||||||
invoke DrawString8x8, 26, 42, OFFSET towerFloorPrefix, 00FF6666h
|
mov ecx, towerCursorY
|
||||||
|
sub ecx, eax
|
||||||
|
shr ecx, 2
|
||||||
|
inc ecx
|
||||||
|
sub towerCursorY, ecx
|
||||||
|
mov eax, towerCursorTargetY
|
||||||
|
cmp towerCursorY, eax
|
||||||
|
jge cursor_update_done
|
||||||
|
mov towerCursorY, eax
|
||||||
|
jmp cursor_update_done
|
||||||
|
|
||||||
|
cursor_move_down:
|
||||||
|
; Moving DOWN towards tower base (target > current)
|
||||||
|
mov ecx, eax
|
||||||
|
sub ecx, towerCursorY
|
||||||
|
shr ecx, 2
|
||||||
|
inc ecx
|
||||||
|
add towerCursorY, ecx
|
||||||
|
mov eax, towerCursorTargetY
|
||||||
|
cmp towerCursorY, eax
|
||||||
|
jle cursor_update_done
|
||||||
|
mov towerCursorY, eax
|
||||||
|
|
||||||
|
cursor_update_done:
|
||||||
|
; 4. Update sliding callout animation (0 to 100%)
|
||||||
|
cmp towerCalloutAnim, 100
|
||||||
|
jge callout_step_done
|
||||||
|
add towerCalloutAnim, 10
|
||||||
|
cmp towerCalloutAnim, 100
|
||||||
|
jle callout_step_done
|
||||||
|
mov towerCalloutAnim, 100
|
||||||
|
callout_step_done:
|
||||||
|
|
||||||
|
; 5. Calculate sliding callout card dimensions occupying free left space:
|
||||||
|
; Max width = 206, fixed right bound = 214, top = 20, height = 208
|
||||||
|
mov cardH, 208
|
||||||
|
mov cardY, 20
|
||||||
|
|
||||||
|
; cardW = 206 * towerCalloutAnim / 100
|
||||||
|
mov eax, 206
|
||||||
|
imul eax, towerCalloutAnim
|
||||||
|
xor edx, edx
|
||||||
|
mov ecx, 100
|
||||||
|
div ecx
|
||||||
|
cmp eax, 10
|
||||||
|
jae @F
|
||||||
|
mov eax, 10
|
||||||
|
@@:
|
||||||
|
mov cardW, eax
|
||||||
|
|
||||||
|
; cardX = 214 - cardW
|
||||||
|
mov edx, 214
|
||||||
|
sub edx, cardW
|
||||||
|
mov cardX, edx
|
||||||
|
|
||||||
|
; 6. Draw glowing cursor & indicator on the tower (Right side)
|
||||||
|
; Indicator line from card right edge (214) to cursor on tower (X=224)
|
||||||
|
mov eax, towerCursorY
|
||||||
|
invoke DrawBox, 214, eax, 10, 2, 00FF4444h
|
||||||
|
|
||||||
|
; Cursor chevron/diamond at X=224
|
||||||
|
mov eax, towerCursorY
|
||||||
|
sub eax, 3
|
||||||
|
invoke DrawBoxBorder, 224, eax, 7, 7, 00FF3300h, 00FFDD44h, 1
|
||||||
|
|
||||||
|
; Pulsing flame pip on the tower floor at X=234
|
||||||
|
mov eax, towerAnimClock
|
||||||
|
and eax, 8
|
||||||
|
jz pip_bright
|
||||||
|
mov edx, 00EE3300h
|
||||||
|
jmp pip_draw
|
||||||
|
pip_bright:
|
||||||
|
mov edx, 00FFAA22h
|
||||||
|
pip_draw:
|
||||||
|
mov eax, towerCursorY
|
||||||
|
sub eax, 2
|
||||||
|
invoke DrawBox, 234, eax, 5, 5, edx
|
||||||
|
|
||||||
|
; 7. Draw Callout Box (outer and inner border with deep infernal tint)
|
||||||
|
invoke DrawBoxBorder, cardX, cardY, cardW, cardH, 00120202h, 00CC2222h, 2
|
||||||
|
mov eax, cardX
|
||||||
|
add eax, 2
|
||||||
|
mov edx, cardY
|
||||||
|
add edx, 2
|
||||||
|
mov ecx, cardW
|
||||||
|
sub ecx, 4
|
||||||
|
mov ebx, cardH
|
||||||
|
sub ebx, 4
|
||||||
|
invoke DrawBoxBorder, eax, edx, ecx, ebx, 000C0202h, 00551111h, 1
|
||||||
|
|
||||||
|
; Only render full content once card has slid out sufficiently (cardW >= 120)
|
||||||
|
cmp cardW, 120
|
||||||
|
jb render_tower_done
|
||||||
|
|
||||||
|
; Header Banner: "ÊÀÇÈÍÎ 3D - ÀÄÑÊÀß ÁÀØÍß"
|
||||||
|
mov eax, cardX
|
||||||
|
add eax, 6
|
||||||
|
mov ecx, cardW
|
||||||
|
sub ecx, 12
|
||||||
|
invoke DrawBoxBorder, eax, 26, ecx, 18, 00220404h, 00FF4444h, 1
|
||||||
|
mov eax, cardX
|
||||||
|
add eax, 12
|
||||||
|
invoke DrawString8x8, eax, 31, OFFSET towerHeader, 00FFDD66h
|
||||||
|
|
||||||
|
; Floor indicator: "ÝÒÀÆ N / 10"
|
||||||
|
mov eax, cardX
|
||||||
|
add eax, 10
|
||||||
|
invoke DrawString8x8, eax, 50, OFFSET towerFloorPrefix, 00FF6666h
|
||||||
invoke FormatNumber, towerStage, OFFSET hudNumBuffer
|
invoke FormatNumber, towerStage, OFFSET hudNumBuffer
|
||||||
invoke DrawString8x8, 66, 42, OFFSET hudNumBuffer, 00FFFFFFh
|
mov eax, cardX
|
||||||
invoke DrawString8x8, 74, 42, OFFSET towerOfTen, 00AAAAAAh
|
add eax, 50
|
||||||
invoke DrawCharmsHUD, 130, 42
|
invoke DrawString8x8, eax, 50, OFFSET hudNumBuffer, 00FFFFFFh
|
||||||
|
mov eax, cardX
|
||||||
|
add eax, 58
|
||||||
|
invoke DrawString8x8, eax, 50, OFFSET towerOfTen, 00AAAAAAh
|
||||||
|
|
||||||
; Draw 10 stage pips across x = 26 .. 293
|
; Specific state content inside the card
|
||||||
xor esi, esi
|
|
||||||
draw_pips_loop:
|
|
||||||
mov eax, esi
|
|
||||||
imul eax, 27
|
|
||||||
add eax, 26 ; x position
|
|
||||||
mov ebx, eax
|
|
||||||
|
|
||||||
; Color based on state
|
|
||||||
mov ecx, towerStage
|
|
||||||
dec ecx
|
|
||||||
cmp esi, ecx
|
|
||||||
jae pip_future
|
|
||||||
; Past stage (Cleared): Bright Gold
|
|
||||||
invoke DrawBoxBorder, ebx, 54, 24, 6, 00DDBB22h, 00FFEE44h, 1
|
|
||||||
jmp pip_next
|
|
||||||
pip_future:
|
|
||||||
je pip_current
|
|
||||||
; Future stage: Dark
|
|
||||||
invoke DrawBoxBorder, ebx, 54, 24, 6, 002A0808h, 00441111h, 1
|
|
||||||
jmp pip_next
|
|
||||||
pip_current:
|
|
||||||
; Current stage: Pulsing Fire Red/Orange
|
|
||||||
invoke DrawBoxBorder, ebx, 54, 24, 6, 00EE3311h, 00FFAA33h, 1
|
|
||||||
pip_next:
|
|
||||||
inc esi
|
|
||||||
cmp esi, 10
|
|
||||||
jb draw_pips_loop
|
|
||||||
|
|
||||||
; 5. Specific State Panels
|
|
||||||
mov eax, towerFloorState
|
mov eax, towerFloorState
|
||||||
cmp eax, TOWER_STATE_BRIEFING
|
cmp eax, TOWER_STATE_BRIEFING
|
||||||
je render_briefing
|
je render_card_briefing
|
||||||
cmp eax, TOWER_STATE_CLEAR
|
cmp eax, TOWER_STATE_CLEAR
|
||||||
je render_clear
|
je render_card_clear
|
||||||
cmp eax, TOWER_STATE_DEFEAT
|
cmp eax, TOWER_STATE_DEFEAT
|
||||||
je render_defeat
|
je render_card_defeat
|
||||||
cmp eax, TOWER_STATE_CHAMPION
|
cmp eax, TOWER_STATE_CHAMPION
|
||||||
je render_champion
|
je render_card_champion
|
||||||
ret
|
jmp render_tower_done
|
||||||
|
|
||||||
render_briefing:
|
render_card_briefing:
|
||||||
; Boss & Game Card
|
; Boss & Game sub-card
|
||||||
invoke DrawBoxBorder, 20, 66, 280, 58, 001E0404h, 00AA2222h, 1
|
mov eax, cardX
|
||||||
invoke DrawString8x8, 28, 72, OFFSET towerEnemyLabel, 00FFAA55h
|
add eax, 6
|
||||||
|
mov ecx, cardW
|
||||||
|
sub ecx, 12
|
||||||
|
invoke DrawBoxBorder, eax, 64, ecx, 62, 001E0404h, 00881818h, 1
|
||||||
|
|
||||||
|
mov eax, cardX
|
||||||
|
add eax, 10
|
||||||
|
invoke DrawString8x8, eax, 70, OFFSET towerEnemyLabel, 00FFAA55h
|
||||||
mov eax, towerStage
|
mov eax, towerStage
|
||||||
dec eax
|
dec eax
|
||||||
mov edx, towerBossNames[eax*4]
|
mov edx, towerBossNames[eax*4]
|
||||||
invoke DrawString8x8, 76, 72, edx, 00FFFFFFh
|
mov eax, cardX
|
||||||
|
add eax, 56
|
||||||
|
invoke DrawString8x8, eax, 70, edx, 00FFFFFFh
|
||||||
|
|
||||||
invoke DrawString8x8, 28, 86, OFFSET towerGameLabel, 00FFAA55h
|
mov eax, cardX
|
||||||
|
add eax, 10
|
||||||
|
invoke DrawString8x8, eax, 84, OFFSET towerGameLabel, 00FFAA55h
|
||||||
mov eax, towerStage
|
mov eax, towerStage
|
||||||
dec eax
|
dec eax
|
||||||
mov edx, towerGameNames[eax*4]
|
mov edx, towerGameNames[eax*4]
|
||||||
invoke DrawString8x8, 76, 86, edx, 00FFDD66h
|
mov eax, cardX
|
||||||
|
add eax, 56
|
||||||
|
invoke DrawString8x8, eax, 84, edx, 00FFDD66h
|
||||||
|
|
||||||
mov eax, towerStage
|
mov eax, towerStage
|
||||||
dec eax
|
dec eax
|
||||||
mov edx, towerDescriptions[eax*4]
|
mov edx, towerDescriptions[eax*4]
|
||||||
invoke DrawString8x8, 28, 104, edx, 00CCCCCCh
|
mov eax, cardX
|
||||||
|
add eax, 10
|
||||||
|
invoke DrawString8x8, eax, 100, edx, 00CCCCCCh
|
||||||
|
|
||||||
; Rules & Warning (fits cleanly inside 300px boundary)
|
; Rules & warning
|
||||||
invoke DrawString8x8, 24, 132, OFFSET towerRuleText, 00FFAA33h
|
mov eax, cardX
|
||||||
invoke DrawString8x8, 24, 146, OFFSET towerLossWarning, 00EE4444h
|
add eax, 10
|
||||||
|
invoke DrawString8x8, eax, 134, OFFSET towerRuleText, 00FFAA33h
|
||||||
|
mov eax, cardX
|
||||||
|
add eax, 10
|
||||||
|
invoke DrawString8x8, eax, 148, OFFSET towerLossWarning, 00EE4444h
|
||||||
|
|
||||||
; Bankroll Display
|
; Bankroll & Prize
|
||||||
invoke DrawString8x8, 24, 164, OFFSET textBank3D, 00AAAAAAh
|
mov eax, cardX
|
||||||
|
add eax, 10
|
||||||
|
invoke DrawString8x8, eax, 164, OFFSET textBank3D, 00AAAAAAh
|
||||||
invoke FormatNumber, bankroll, OFFSET hudNumBuffer
|
invoke FormatNumber, bankroll, OFFSET hudNumBuffer
|
||||||
invoke DrawString8x8, 76, 164, OFFSET hudNumBuffer, 0066FF66h
|
mov eax, cardX
|
||||||
|
add eax, 56
|
||||||
|
invoke DrawString8x8, eax, 164, OFFSET hudNumBuffer, 0066FF66h
|
||||||
|
|
||||||
; Action Buttons (Centered text inside buttons)
|
mov eax, cardX
|
||||||
invoke DrawBoxBorder, 22, 184, 132, 24, 003D0C0Ch, 00FF4444h, 1
|
add eax, 110
|
||||||
invoke DrawString8x8, 32, 192, OFFSET towerStartBtn, 00FFFFFFh
|
invoke DrawString8x8, eax, 164, OFFSET towerPrizePrompt, 00FFAA55h
|
||||||
|
invoke FormatNumber, towerPrizeAmount, OFFSET hudNumBuffer
|
||||||
|
mov eax, cardX
|
||||||
|
add eax, 154
|
||||||
|
invoke DrawString8x8, eax, 164, OFFSET hudNumBuffer, 00FFDD44h
|
||||||
|
|
||||||
invoke DrawBoxBorder, 164, 184, 132, 24, 00220606h, 00882222h, 1
|
; Action Buttons (Start & Escape)
|
||||||
invoke DrawString8x8, 178, 192, OFFSET towerEscBtn, 00AAAAAAh
|
mov eax, cardX
|
||||||
ret
|
add eax, 8
|
||||||
|
invoke DrawBoxBorder, eax, 186, 92, 22, 003D0C0Ch, 00FF4444h, 1
|
||||||
|
mov eax, cardX
|
||||||
|
add eax, 14
|
||||||
|
invoke DrawString8x8, eax, 193, OFFSET towerStartBtn, 00FFFFFFh
|
||||||
|
|
||||||
render_clear:
|
mov eax, cardX
|
||||||
invoke DrawBoxBorder, 20, 76, 280, 74, 00082208h, 0033CC33h, 2
|
add eax, 106
|
||||||
invoke DrawString8x8, 48, 90, OFFSET towerClearTitle, 0066FF66h
|
invoke DrawBoxBorder, eax, 186, 92, 22, 00220606h, 00882222h, 1
|
||||||
invoke DrawString8x8, 24, 112, OFFSET towerClearDesc, 00FFFFFFh
|
mov eax, cardX
|
||||||
|
add eax, 114
|
||||||
|
invoke DrawString8x8, eax, 193, OFFSET towerEscBtn, 00AAAAAAh
|
||||||
|
jmp render_tower_done
|
||||||
|
|
||||||
invoke DrawBoxBorder, 50, 174, 220, 26, 00114411h, 0044FF44h, 1
|
render_card_clear:
|
||||||
invoke DrawString8x8, 100, 183, OFFSET towerNextBtn, 00FFFFFFh
|
mov eax, cardX
|
||||||
ret
|
add eax, 8
|
||||||
|
mov ecx, cardW
|
||||||
|
sub ecx, 16
|
||||||
|
invoke DrawBoxBorder, eax, 74, ecx, 60, 00082208h, 0033CC33h, 2
|
||||||
|
mov eax, cardX
|
||||||
|
add eax, 14
|
||||||
|
invoke DrawString8x8, eax, 86, OFFSET towerClearTitle, 0066FF66h
|
||||||
|
mov eax, cardX
|
||||||
|
add eax, 14
|
||||||
|
invoke DrawString8x8, eax, 108, OFFSET towerClearDesc, 00FFFFFFh
|
||||||
|
|
||||||
render_defeat:
|
mov eax, cardX
|
||||||
invoke DrawBoxBorder, 20, 76, 280, 74, 002A0404h, 00FF2222h, 2
|
add eax, 18
|
||||||
invoke DrawString8x8, 60, 90, OFFSET towerDefeatTitle, 00FF4444h
|
mov ecx, cardW
|
||||||
invoke DrawString8x8, 36, 112, OFFSET towerDefeatDesc, 00FFFFFFh
|
sub ecx, 36
|
||||||
|
invoke DrawBoxBorder, eax, 186, ecx, 22, 00114411h, 0044FF44h, 1
|
||||||
|
mov eax, cardX
|
||||||
|
add eax, 42
|
||||||
|
invoke DrawString8x8, eax, 193, OFFSET towerNextBtn, 00FFFFFFh
|
||||||
|
jmp render_tower_done
|
||||||
|
|
||||||
invoke DrawBoxBorder, 32, 174, 256, 26, 00441010h, 00FF4444h, 1
|
render_card_defeat:
|
||||||
invoke DrawString8x8, 48, 183, OFFSET towerReturnBtn, 00FFFFFFh
|
mov eax, cardX
|
||||||
ret
|
add eax, 8
|
||||||
|
mov ecx, cardW
|
||||||
|
sub ecx, 16
|
||||||
|
invoke DrawBoxBorder, eax, 74, ecx, 60, 002A0404h, 00FF2222h, 2
|
||||||
|
mov eax, cardX
|
||||||
|
add eax, 14
|
||||||
|
invoke DrawString8x8, eax, 86, OFFSET towerDefeatTitle, 00FF4444h
|
||||||
|
mov eax, cardX
|
||||||
|
add eax, 14
|
||||||
|
invoke DrawString8x8, eax, 108, OFFSET towerDefeatDesc, 00FFFFFFh
|
||||||
|
|
||||||
render_champion:
|
mov eax, cardX
|
||||||
invoke DrawBoxBorder, 18, 70, 284, 86, 00332200h, 00FFD700h, 2
|
add eax, 16
|
||||||
invoke DrawString8x8, 40, 82, OFFSET towerChampTitle, 00FFE066h
|
mov ecx, cardW
|
||||||
invoke DrawString8x8, 56, 100, OFFSET towerChampSubtitle, 00FFFFFFh
|
sub ecx, 32
|
||||||
invoke DrawString8x8, 36, 122, OFFSET towerPrizeText, 0066FF66h
|
invoke DrawBoxBorder, eax, 186, ecx, 22, 00441010h, 00FF4444h, 1
|
||||||
|
mov eax, cardX
|
||||||
|
add eax, 22
|
||||||
|
invoke DrawString8x8, eax, 193, OFFSET towerReturnBtn, 00FFFFFFh
|
||||||
|
jmp render_tower_done
|
||||||
|
|
||||||
invoke DrawBoxBorder, 32, 174, 256, 26, 00443300h, 00FFCC00h, 1
|
render_card_champion:
|
||||||
invoke DrawString8x8, 48, 183, OFFSET towerReturnBtn, 00FFFFFFh
|
mov eax, cardX
|
||||||
|
add eax, 8
|
||||||
|
mov ecx, cardW
|
||||||
|
sub ecx, 16
|
||||||
|
invoke DrawBoxBorder, eax, 68, ecx, 72, 00332200h, 00FFD700h, 2
|
||||||
|
mov eax, cardX
|
||||||
|
add eax, 12
|
||||||
|
invoke DrawString8x8, eax, 78, OFFSET towerChampTitle, 00FFE066h
|
||||||
|
mov eax, cardX
|
||||||
|
add eax, 16
|
||||||
|
invoke DrawString8x8, eax, 96, OFFSET towerChampSubtitle, 00FFFFFFh
|
||||||
|
mov eax, cardX
|
||||||
|
add eax, 14
|
||||||
|
invoke DrawString8x8, eax, 116, OFFSET towerPrizeText, 0066FF66h
|
||||||
|
|
||||||
|
mov eax, cardX
|
||||||
|
add eax, 16
|
||||||
|
mov ecx, cardW
|
||||||
|
sub ecx, 32
|
||||||
|
invoke DrawBoxBorder, eax, 186, ecx, 22, 00443300h, 00FFCC00h, 1
|
||||||
|
mov eax, cardX
|
||||||
|
add eax, 22
|
||||||
|
invoke DrawString8x8, eax, 193, OFFSET towerReturnBtn, 00FFFFFFh
|
||||||
|
|
||||||
|
render_tower_done:
|
||||||
ret
|
ret
|
||||||
RenderHellTower3D ENDP
|
RenderHellTower3D ENDP
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ InitHellTower3D PROTO
|
||||||
TowerExitToHall PROTO
|
TowerExitToHall PROTO
|
||||||
TowerReportRoundOutcome PROTO :DWORD
|
TowerReportRoundOutcome PROTO :DWORD
|
||||||
RenderTowerInGameBadge PROTO
|
RenderTowerInGameBadge PROTO
|
||||||
|
ResolveHellAssetPath PROTO :DWORD, :DWORD
|
||||||
|
FreeTowerMinigameAssets PROTO
|
||||||
|
|
||||||
.data
|
.data
|
||||||
towerStage dd 1
|
towerStage dd 1
|
||||||
|
|
@ -19,6 +21,16 @@ towerCurrentGame dd 0
|
||||||
towerPrizeAmount dd 66600
|
towerPrizeAmount dd 66600
|
||||||
towerHudText db 91,32,0
|
towerHudText db 91,32,0
|
||||||
|
|
||||||
|
hellTowerMenuArt3D AnimPack3D <0>
|
||||||
|
hellTowerMenuPath3D db "assets\animations\hall\helltower-menu.bin",0
|
||||||
|
|
||||||
|
towerFloorY dd 205, 186, 167, 148, 129, 110, 91, 72, 53, 34
|
||||||
|
towerCursorY dd 205
|
||||||
|
towerCursorTargetY dd 205
|
||||||
|
towerCalloutAnim dd 0
|
||||||
|
|
||||||
|
towerPrizePrompt db 202,211,216,58,32,36,0 ; "КУШ: $"
|
||||||
|
|
||||||
towerFloorModes dd MODE_COIN, MODE_DICE, MODE_SKULLS, MODE_BLACKJACK, MODE_POKER, MODE_RUSSIAN, MODE_ROULETTE, MODE_DURAK, MODE_SKILL, MODE_BUCKSHOT
|
towerFloorModes dd MODE_COIN, MODE_DICE, MODE_SKULLS, MODE_BLACKJACK, MODE_POKER, MODE_RUSSIAN, MODE_ROULETTE, MODE_DURAK, MODE_SKILL, MODE_BUCKSHOT
|
||||||
|
|
||||||
towerBoss1 db 193,197,209,32,192,199,192,208,210,192,0
|
towerBoss1 db 193,197,209,32,192,199,192,208,210,192,0
|
||||||
|
|
|
||||||
885
src_graphics/jackbox.inc
Normal file
|
|
@ -0,0 +1,885 @@
|
||||||
|
; ==============================================================================
|
||||||
|
; JACKBOX / JUKEBOX IMPLEMENTATION - MASM32 / CP1251
|
||||||
|
; ==============================================================================
|
||||||
|
|
||||||
|
.code
|
||||||
|
|
||||||
|
LoadJackboxArt3D PROC
|
||||||
|
invoke LoadAnimPack3D, OFFSET jackboxWorldArt3D, OFFSET jackboxWorldPath3D
|
||||||
|
invoke ValidateCityPack3D, OFFSET jackboxWorldArt3D, 256, 512, 3
|
||||||
|
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 jackbox_surface_front
|
||||||
|
cmp eax, MATERIAL_JACKBOX_SIDE
|
||||||
|
je jackbox_surface_side
|
||||||
|
cmp eax, MATERIAL_JACKBOX_BACK
|
||||||
|
jne jackbox_surface_missing
|
||||||
|
; Frame 2: rear panel (offset 256*512*4*2 = 1048576)
|
||||||
|
mov eax, jackboxWorldArt3D.pixels
|
||||||
|
add eax, 1048576
|
||||||
|
mov ecx, 256
|
||||||
|
mov edx, 512
|
||||||
|
ret
|
||||||
|
jackbox_surface_side:
|
||||||
|
; Frame 1: side profile (offset 256*512*4 = 524288)
|
||||||
|
mov eax, jackboxWorldArt3D.pixels
|
||||||
|
add eax, 524288
|
||||||
|
mov ecx, 256
|
||||||
|
mov edx, 512
|
||||||
|
ret
|
||||||
|
jackbox_surface_front:
|
||||||
|
; Frame 0: front face
|
||||||
|
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
|
||||||
|
; Solid rectangular cabinet against East wall beside billiards:
|
||||||
|
; Depth along X: 0.55m (x0 = 30.40m, x1 = 30.95m against wall at 31.0m)
|
||||||
|
; Width along Y: 0.85m (y0 = 23.075m, y1 = 23.925m, centered at Y = 23.5m)
|
||||||
|
; Leaves painting 116 at Y = [25.0 .. 26.5] completely visible and unblocked!
|
||||||
|
; Height Z: 1.85m
|
||||||
|
mov x0, FP_1*304/10
|
||||||
|
mov x1, FP_1*3095/100
|
||||||
|
mov y0, FP_1*235/10 - FP_1*425/1000
|
||||||
|
mov y1, FP_1*235/10 + FP_1*425/1000
|
||||||
|
|
||||||
|
; 1. Back Face (against East wall: X = x1)
|
||||||
|
invoke DrawCityVertical3D, 0, x1, y0, y1, 0, FP_1*37/20, MATERIAL_JACKBOX_BACK
|
||||||
|
; 2. North Side (Y = y0) - Authentic wooden terminal cabinet panel with brass trim
|
||||||
|
invoke DrawCityVertical3D, 1, y0, x0, x1, 0, FP_1*37/20, MATERIAL_JACKBOX_SIDE
|
||||||
|
; 3. South Side (Y = y1) - Authentic wooden terminal cabinet panel with brass trim
|
||||||
|
invoke DrawCityVertical3D, 1, y1, x0, x1, 0, FP_1*37/20, MATERIAL_JACKBOX_SIDE
|
||||||
|
; 4. Front Face (facing West towards billiards: X = x0)
|
||||||
|
invoke DrawCityVertical3D, 0, x0, y0, y1, 0, FP_1*37/20, MATERIAL_JACKBOX_FRONT
|
||||||
|
; 5. Top Lid (horizontal cap at z = 1.85m)
|
||||||
|
invoke DrawCityHorizontal3D, x0, x1, y0, y1, FP_1*37/20, 110
|
||||||
|
ret
|
||||||
|
DrawJackboxCabinet3D ENDP
|
||||||
|
|
||||||
|
RenderJackboxCabinet3D PROC
|
||||||
|
invoke DrawJackboxCabinet3D, JACKBOX_X, JACKBOX_Y
|
||||||
|
ret
|
||||||
|
RenderJackboxCabinet3D ENDP
|
||||||
|
|
||||||
|
JackboxCanWalk3D PROC px:DWORD, py:DWORD
|
||||||
|
mov eax, px
|
||||||
|
cmp eax, FP_1*303/10
|
||||||
|
jb jackbox_walk_clear
|
||||||
|
cmp eax, 31*FP_1+9830
|
||||||
|
ja jackbox_walk_clear
|
||||||
|
mov eax, py
|
||||||
|
sub eax, FP_1*235/10
|
||||||
|
test eax, eax
|
||||||
|
jns @F
|
||||||
|
neg eax
|
||||||
|
@@:
|
||||||
|
cmp eax, FP_1*55/100
|
||||||
|
ja jackbox_walk_clear
|
||||||
|
xor eax, eax
|
||||||
|
ret
|
||||||
|
jackbox_walk_clear:
|
||||||
|
mov eax, 1
|
||||||
|
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, 2
|
||||||
|
jae jbox_play_stop_selected
|
||||||
|
cmp ebx, JACKBOX_TRACK_COUNT
|
||||||
|
jae jbox_play_stop_selected
|
||||||
|
|
||||||
|
mov eax, jboxOpenCommands[ebx*4]
|
||||||
|
test eax, eax
|
||||||
|
jz jbox_play_stop_selected
|
||||||
|
invoke mciSendStringA, eax, 0, 0, 0
|
||||||
|
invoke mciSendStringA, OFFSET jboxPlayCmd, 0, 0, 0
|
||||||
|
mov jboxCurrentTrack, ebx
|
||||||
|
mov jboxIsPlaying, 1
|
||||||
|
mov jboxEffectiveVolume, -1
|
||||||
|
call UpdateJukeboxAudio3D
|
||||||
|
ret
|
||||||
|
jbox_play_stop_selected:
|
||||||
|
mov jboxCurrentTrack, -1
|
||||||
|
call UpdateJukeboxAudio3D
|
||||||
|
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
|
||||||
|
call UpdateJukeboxAudio3D
|
||||||
|
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
|
||||||
|
|
||||||
|
; Exit via Top-Right LCD slot: x in [232..284], y in [12..26]
|
||||||
|
cmp mouseY3D, 12
|
||||||
|
jb @F
|
||||||
|
cmp mouseY3D, 26
|
||||||
|
ja @F
|
||||||
|
cmp mouseX3D, 232
|
||||||
|
jb @F
|
||||||
|
cmp mouseX3D, 284
|
||||||
|
jbe jbox_exit
|
||||||
|
@@:
|
||||||
|
; Exit via Bottom Red CANCEL button: x in [224..256], y in [195..225]
|
||||||
|
cmp mouseY3D, 195
|
||||||
|
jb @F
|
||||||
|
cmp mouseY3D, 225
|
||||||
|
ja @F
|
||||||
|
cmp mouseX3D, 224
|
||||||
|
jb @F
|
||||||
|
cmp mouseX3D, 256
|
||||||
|
jbe jbox_exit
|
||||||
|
@@:
|
||||||
|
; Bottom Green START button: x in [154..188], y in [195..225] -> Play selected
|
||||||
|
cmp mouseY3D, 195
|
||||||
|
jb @F
|
||||||
|
cmp mouseY3D, 225
|
||||||
|
ja @F
|
||||||
|
cmp mouseX3D, 154
|
||||||
|
jb @F
|
||||||
|
cmp mouseX3D, 188
|
||||||
|
jbe jbox_play_selected
|
||||||
|
@@:
|
||||||
|
; Bottom Orange SELECT button: x in [190..222], y in [195..225] -> Next track & play
|
||||||
|
cmp mouseY3D, 195
|
||||||
|
jb @F
|
||||||
|
cmp mouseY3D, 225
|
||||||
|
ja @F
|
||||||
|
cmp mouseX3D, 190
|
||||||
|
jb @F
|
||||||
|
cmp mouseX3D, 222
|
||||||
|
ja @F
|
||||||
|
inc jboxSelectedTrack
|
||||||
|
cmp jboxSelectedTrack, JACKBOX_TRACK_COUNT
|
||||||
|
jb jbox_play_selected
|
||||||
|
mov jboxSelectedTrack, 0
|
||||||
|
jmp jbox_play_selected
|
||||||
|
@@:
|
||||||
|
; Bottom Left Arrow: x in [256..274], y in [204..224] -> Prev track
|
||||||
|
cmp mouseY3D, 204
|
||||||
|
jb @F
|
||||||
|
cmp mouseY3D, 224
|
||||||
|
ja @F
|
||||||
|
cmp mouseX3D, 256
|
||||||
|
jb @F
|
||||||
|
cmp mouseX3D, 274
|
||||||
|
ja @F
|
||||||
|
dec jboxSelectedTrack
|
||||||
|
jns jbox_play_selected
|
||||||
|
mov jboxSelectedTrack, JACKBOX_TRACK_COUNT - 1
|
||||||
|
jmp jbox_play_selected
|
||||||
|
@@:
|
||||||
|
; Bottom Right Arrow: x in [276..294], y in [204..224] -> Next track
|
||||||
|
cmp mouseY3D, 204
|
||||||
|
jb @F
|
||||||
|
cmp mouseY3D, 224
|
||||||
|
ja @F
|
||||||
|
cmp mouseX3D, 276
|
||||||
|
jb @F
|
||||||
|
cmp mouseX3D, 294
|
||||||
|
ja @F
|
||||||
|
inc jboxSelectedTrack
|
||||||
|
cmp jboxSelectedTrack, JACKBOX_TRACK_COUNT
|
||||||
|
jb jbox_play_selected
|
||||||
|
mov jboxSelectedTrack, 0
|
||||||
|
jmp jbox_play_selected
|
||||||
|
@@:
|
||||||
|
; Track list click: x in [46..274], y in [66..108], step = 14
|
||||||
|
cmp mouseY3D, 66
|
||||||
|
jb jbox_keys
|
||||||
|
cmp mouseY3D, 108
|
||||||
|
ja jbox_keys
|
||||||
|
cmp mouseX3D, 46
|
||||||
|
jb jbox_keys
|
||||||
|
cmp mouseX3D, 274
|
||||||
|
ja jbox_keys
|
||||||
|
mov eax, mouseY3D
|
||||||
|
sub eax, 66
|
||||||
|
xor edx, edx
|
||||||
|
mov ebx, 14
|
||||||
|
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, 2
|
||||||
|
mov jboxSelectedTrack, eax
|
||||||
|
invoke PlayJukeboxTrack3D, eax
|
||||||
|
jmp jbox_handled
|
||||||
|
@@:
|
||||||
|
; Direct digit keys '1'..'2'
|
||||||
|
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, 32h ; '2'
|
||||||
|
jbe jbox_digit_loop
|
||||||
|
|
||||||
|
; '0' key for mute/stop
|
||||||
|
invoke Pressed3D, 30h
|
||||||
|
test eax, eax
|
||||||
|
jz jbox_handled
|
||||||
|
mov jboxSelectedTrack, 2
|
||||||
|
invoke PlayJukeboxTrack3D, 2
|
||||||
|
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 (640x480 physical buffer)
|
||||||
|
cmp jackboxScreenArt3D.pixels, 0
|
||||||
|
je @F
|
||||||
|
invoke DrawAnimSprite3D, OFFSET jackboxScreenArt3D, 0, 0, 0, 640, 480
|
||||||
|
@@:
|
||||||
|
; 3. Top Digital LCD Slots
|
||||||
|
; Left slot: status text
|
||||||
|
cmp jboxIsPlaying, 1
|
||||||
|
jne jbox_top_lcd_stopped
|
||||||
|
cmp jboxCurrentTrack, -1
|
||||||
|
je jbox_top_lcd_stopped
|
||||||
|
cmp jboxCurrentTrack, 2
|
||||||
|
jae jbox_top_lcd_stopped
|
||||||
|
invoke DrawString8x8, 42, 17, OFFSET jboxLcdPlay, 0050FF90h
|
||||||
|
jmp jbox_top_lcd_exit
|
||||||
|
jbox_top_lcd_stopped:
|
||||||
|
invoke DrawString8x8, 45, 17, OFFSET jboxLcdStop, 008CB8A5h
|
||||||
|
jbox_top_lcd_exit:
|
||||||
|
; Right slot: exit button text
|
||||||
|
invoke DrawString8x8, 241, 17, OFFSET jboxLcdExit, 00F2A648h
|
||||||
|
|
||||||
|
; 4. CRT Monitor Header: Now Playing Status
|
||||||
|
cmp jboxIsPlaying, 1
|
||||||
|
jne jbox_render_stopped
|
||||||
|
cmp jboxCurrentTrack, -1
|
||||||
|
je jbox_render_stopped
|
||||||
|
cmp jboxCurrentTrack, 2
|
||||||
|
jae jbox_render_stopped
|
||||||
|
|
||||||
|
invoke DrawString8x8, 48, 52, OFFSET jboxHdrNow, 0050FF90h
|
||||||
|
mov eax, jboxCurrentTrack
|
||||||
|
mov edx, jboxTrackNames[eax*4]
|
||||||
|
invoke DrawString8x8, 108, 52, edx, 00F0E6C3h
|
||||||
|
jmp jbox_render_divider
|
||||||
|
|
||||||
|
jbox_render_stopped:
|
||||||
|
invoke DrawString8x8, 48, 52, OFFSET jboxHdrStop, 008CB8A5h
|
||||||
|
|
||||||
|
jbox_render_divider:
|
||||||
|
invoke DrawBox, 46, 62, 228, 1, 001C3828h
|
||||||
|
|
||||||
|
; 5. Track Selection List (3 rows, step Y = 14, from Y = 70)
|
||||||
|
mov rowY, 70
|
||||||
|
xor esi, esi
|
||||||
|
jbox_render_row:
|
||||||
|
mov color, 008CB8A5h ; Default CRT phosphor pale cyan
|
||||||
|
|
||||||
|
cmp esi, jboxSelectedTrack
|
||||||
|
jne @F
|
||||||
|
; Selected row: highlight box, golden cursor, bright white text
|
||||||
|
invoke DrawBox, 46, rowY, 228, 10, 00143526h
|
||||||
|
invoke DrawString8x8, 48, rowY, OFFSET jboxCursor, 00FFD700h
|
||||||
|
mov color, 00FFFFFFh
|
||||||
|
@@:
|
||||||
|
mov edx, jboxTrackNames[esi*4]
|
||||||
|
invoke DrawString8x8, 58, rowY, edx, color
|
||||||
|
|
||||||
|
; Mark playing track with [>] tag
|
||||||
|
cmp esi, jboxCurrentTrack
|
||||||
|
jne jbox_row_next
|
||||||
|
cmp jboxIsPlaying, 1
|
||||||
|
jne jbox_row_next
|
||||||
|
invoke DrawString8x8, 238, rowY, OFFSET jboxTagPlay, 0050FF90h
|
||||||
|
|
||||||
|
jbox_row_next:
|
||||||
|
add rowY, 14
|
||||||
|
inc esi
|
||||||
|
cmp esi, JACKBOX_TRACK_COUNT
|
||||||
|
jb jbox_render_row
|
||||||
|
|
||||||
|
; 6. Footer Controls Help (strictly inside CRT)
|
||||||
|
invoke DrawBox, 46, 166, 228, 1, 001C3828h
|
||||||
|
invoke DrawString8x8, 56, 170, OFFSET jboxFooterHelp, 0070AA90h
|
||||||
|
|
||||||
|
ret
|
||||||
|
RenderJackbox3D ENDP
|
||||||
|
|
||||||
|
; ==============================================================================
|
||||||
|
; 3D SOUND PROPAGATION & MULTI-FLOOR ATTENUATION
|
||||||
|
; ==============================================================================
|
||||||
|
|
||||||
|
CalculateJukeboxFalloff PROC uses ebx esi edi
|
||||||
|
local dxDist:DWORD, dyDist:DWORD, distSq:DWORD, distVal:DWORD
|
||||||
|
local factor:DWORD
|
||||||
|
|
||||||
|
; If music is not playing, factor = 0
|
||||||
|
cmp jboxIsPlaying, 1
|
||||||
|
jne falloff_silent
|
||||||
|
cmp jboxCurrentTrack, -1
|
||||||
|
je falloff_silent
|
||||||
|
cmp jboxCurrentTrack, 2
|
||||||
|
jae falloff_silent
|
||||||
|
|
||||||
|
; Check if on stairs (MODE_STAIRS)
|
||||||
|
cmp engineMode, MODE_STAIRS
|
||||||
|
je falloff_stairs
|
||||||
|
|
||||||
|
; Check floor level
|
||||||
|
mov eax, hallLevel3D
|
||||||
|
cmp eax, 0
|
||||||
|
je falloff_ground
|
||||||
|
cmp eax, 1
|
||||||
|
je falloff_bunker
|
||||||
|
cmp eax, 2
|
||||||
|
je falloff_upper
|
||||||
|
cmp eax, 3
|
||||||
|
je falloff_fortune
|
||||||
|
jmp falloff_silent
|
||||||
|
|
||||||
|
falloff_ground:
|
||||||
|
; Level 0 (Ground Hall) - standard quadratic 2D distance falloff from Jackbox
|
||||||
|
; Jackbox: JACKBOX_X = 30.6m, JACKBOX_Y = 23.5m
|
||||||
|
mov eax, playerX
|
||||||
|
sub eax, JACKBOX_X
|
||||||
|
test eax, eax
|
||||||
|
jns @F
|
||||||
|
neg eax
|
||||||
|
@@:
|
||||||
|
shr eax, 16 ; integer meters
|
||||||
|
mov dxDist, eax
|
||||||
|
|
||||||
|
mov eax, playerY
|
||||||
|
sub eax, JACKBOX_Y
|
||||||
|
test eax, eax
|
||||||
|
jns @F
|
||||||
|
neg eax
|
||||||
|
@@:
|
||||||
|
shr eax, 16 ; integer meters
|
||||||
|
mov dyDist, eax
|
||||||
|
|
||||||
|
; distSq = dx*dx + dy*dy
|
||||||
|
mov eax, dxDist
|
||||||
|
imul eax, eax
|
||||||
|
mov ebx, dyDist
|
||||||
|
imul ebx, ebx
|
||||||
|
add eax, ebx
|
||||||
|
mov distSq, eax
|
||||||
|
|
||||||
|
; distVal = sqrt(distSq)
|
||||||
|
fild distSq
|
||||||
|
fsqrt
|
||||||
|
fistp distVal
|
||||||
|
|
||||||
|
; Acoustic curve:
|
||||||
|
; dist <= 2m -> factor = 1000
|
||||||
|
; dist >= 35m -> factor = 0
|
||||||
|
; in between: rem = 35 - dist; factor = 1000 * rem * rem / 1089
|
||||||
|
mov eax, distVal
|
||||||
|
cmp eax, 2
|
||||||
|
jbe falloff_full
|
||||||
|
cmp eax, 35
|
||||||
|
jae falloff_silent
|
||||||
|
|
||||||
|
mov ecx, 35
|
||||||
|
sub ecx, eax ; ecx = 35 - distVal (in 1..32)
|
||||||
|
imul eax, ecx, 1000
|
||||||
|
imul eax, ecx ; eax = 1000 * rem * rem
|
||||||
|
xor edx, edx
|
||||||
|
mov ebx, 1089 ; 33 * 33
|
||||||
|
div ebx
|
||||||
|
mov factor, eax
|
||||||
|
jmp falloff_done
|
||||||
|
|
||||||
|
falloff_full:
|
||||||
|
mov factor, 1000
|
||||||
|
jmp falloff_done
|
||||||
|
|
||||||
|
falloff_upper:
|
||||||
|
; Level 2 (Upper Lounge) - sound travels up the open stairs (stairwell at X=16, Y=21 on upper map)
|
||||||
|
; Base attenuation through floor + distance from stairwell opening
|
||||||
|
mov eax, playerX
|
||||||
|
sub eax, 16*FP_1
|
||||||
|
test eax, eax
|
||||||
|
jns @F
|
||||||
|
neg eax
|
||||||
|
@@:
|
||||||
|
shr eax, 16
|
||||||
|
mov dxDist, eax
|
||||||
|
|
||||||
|
mov eax, playerY
|
||||||
|
sub eax, 21*FP_1
|
||||||
|
test eax, eax
|
||||||
|
jns @F
|
||||||
|
neg eax
|
||||||
|
@@:
|
||||||
|
shr eax, 16
|
||||||
|
mov dyDist, eax
|
||||||
|
|
||||||
|
mov eax, dxDist
|
||||||
|
imul eax, eax
|
||||||
|
mov ebx, dyDist
|
||||||
|
imul ebx, ebx
|
||||||
|
add eax, ebx
|
||||||
|
mov distSq, eax
|
||||||
|
|
||||||
|
fild distSq
|
||||||
|
fsqrt
|
||||||
|
fistp distVal
|
||||||
|
|
||||||
|
; At stair opening: factor = 420 (muffled from below)
|
||||||
|
; Drops by 18 per meter, min 90
|
||||||
|
mov eax, distVal
|
||||||
|
imul eax, 18
|
||||||
|
mov ecx, 420
|
||||||
|
sub ecx, eax
|
||||||
|
cmp ecx, 90
|
||||||
|
jge @F
|
||||||
|
mov ecx, 90
|
||||||
|
@@:
|
||||||
|
mov factor, ecx
|
||||||
|
jmp falloff_done
|
||||||
|
|
||||||
|
falloff_bunker:
|
||||||
|
; Level 1 (Bunker) - underground, heavily muffled concrete absorption (~160)
|
||||||
|
mov factor, 160
|
||||||
|
jmp falloff_done
|
||||||
|
|
||||||
|
falloff_fortune:
|
||||||
|
; Level 3 (Fortune Room) - secret room on opposite side of casino (~90)
|
||||||
|
mov factor, 90
|
||||||
|
jmp falloff_done
|
||||||
|
|
||||||
|
falloff_stairs:
|
||||||
|
; On stairs between floors
|
||||||
|
mov eax, stairRoute3D
|
||||||
|
cmp eax, 2
|
||||||
|
je falloff_stairs_upper
|
||||||
|
cmp eax, 1
|
||||||
|
je falloff_stairs_bunker
|
||||||
|
mov factor, 150
|
||||||
|
jmp falloff_done
|
||||||
|
|
||||||
|
falloff_stairs_upper:
|
||||||
|
; Route 2 (Upper stairs)
|
||||||
|
; playerX goes from ~3 (bottom, beside jukebox) to ~16 (top, upper floor)
|
||||||
|
; At bottom: 850. At top: 420.
|
||||||
|
mov eax, playerX
|
||||||
|
shr eax, 16 ; integer X (3..16)
|
||||||
|
cmp eax, 3
|
||||||
|
jge @F
|
||||||
|
mov eax, 3
|
||||||
|
@@:
|
||||||
|
cmp eax, 16
|
||||||
|
jle @F
|
||||||
|
mov eax, 16
|
||||||
|
@@:
|
||||||
|
; Interpolate: factor = 850 - (eax - 3) * (850 - 420) / 13
|
||||||
|
sub eax, 3
|
||||||
|
imul eax, 430
|
||||||
|
xor edx, edx
|
||||||
|
mov ebx, 13
|
||||||
|
div ebx
|
||||||
|
mov ecx, 850
|
||||||
|
sub ecx, eax
|
||||||
|
mov factor, ecx
|
||||||
|
jmp falloff_done
|
||||||
|
|
||||||
|
falloff_stairs_bunker:
|
||||||
|
; Route 1 (Basement stairs)
|
||||||
|
; Smooth blend between 350 (top) and 160 (bottom)
|
||||||
|
mov factor, 250
|
||||||
|
jmp falloff_done
|
||||||
|
|
||||||
|
falloff_silent:
|
||||||
|
mov factor, 0
|
||||||
|
|
||||||
|
falloff_done:
|
||||||
|
mov eax, factor
|
||||||
|
ret
|
||||||
|
CalculateJukeboxFalloff ENDP
|
||||||
|
|
||||||
|
UpdateJukeboxAudio3D PROC uses ebx esi edi
|
||||||
|
local falloff:DWORD, targetVol:DWORD
|
||||||
|
|
||||||
|
; Check if music is playing
|
||||||
|
cmp jboxIsPlaying, 1
|
||||||
|
jne jbox_audio_mute
|
||||||
|
|
||||||
|
; If user volume is 0 -> mute
|
||||||
|
mov eax, jboxUserVolume
|
||||||
|
test eax, eax
|
||||||
|
jz jbox_audio_mute
|
||||||
|
|
||||||
|
; Calculate 3D attenuation factor (0..1000)
|
||||||
|
call CalculateJukeboxFalloff
|
||||||
|
mov falloff, eax
|
||||||
|
test eax, eax
|
||||||
|
jz jbox_audio_mute
|
||||||
|
|
||||||
|
; targetVol = (jboxUserVolume * 100) * falloff / 1000
|
||||||
|
; jboxUserVolume is 0..10 (0..1000 base)
|
||||||
|
mov eax, jboxUserVolume
|
||||||
|
imul eax, 100
|
||||||
|
imul eax, falloff
|
||||||
|
xor edx, edx
|
||||||
|
mov ebx, 1000
|
||||||
|
div ebx
|
||||||
|
mov targetVol, eax
|
||||||
|
jmp jbox_apply_vol
|
||||||
|
|
||||||
|
jbox_audio_mute:
|
||||||
|
mov targetVol, 0
|
||||||
|
|
||||||
|
jbox_apply_vol:
|
||||||
|
; If targetVol matches last applied volume, skip redundant MCI call
|
||||||
|
mov eax, targetVol
|
||||||
|
cmp eax, jboxEffectiveVolume
|
||||||
|
je jbox_audio_done
|
||||||
|
|
||||||
|
mov jboxEffectiveVolume, eax
|
||||||
|
|
||||||
|
; Format: "setaudio jboxmusic volume to <targetVol>"
|
||||||
|
; Copy prefix to jboxVolCmdBuf
|
||||||
|
lea esi, jboxVolCmdPrefix
|
||||||
|
lea edi, jboxVolCmdBuf
|
||||||
|
jbox_vol_copy:
|
||||||
|
lodsb
|
||||||
|
stosb
|
||||||
|
test al, al
|
||||||
|
jnz jbox_vol_copy
|
||||||
|
dec edi ; point at null terminator
|
||||||
|
|
||||||
|
; Append decimal number targetVol
|
||||||
|
invoke FormatNumber, targetVol, edi
|
||||||
|
|
||||||
|
; Send to MCI
|
||||||
|
invoke mciSendStringA, OFFSET jboxVolCmdBuf, 0, 0, 0
|
||||||
|
|
||||||
|
jbox_audio_done:
|
||||||
|
ret
|
||||||
|
UpdateJukeboxAudio3D ENDP
|
||||||
|
|
||||||
|
; ==============================================================================
|
||||||
|
; VOLUME CONTROL KEYS & POP-UP SLIDING HUD BAR
|
||||||
|
; ==============================================================================
|
||||||
|
|
||||||
|
HandleJukeboxKeys3D PROC
|
||||||
|
; '+' key: VK_OEM_PLUS (0BBh) or VK_ADD (6Bh - numpad +)
|
||||||
|
invoke Pressed3D, 0BBh
|
||||||
|
test eax, eax
|
||||||
|
jnz jbox_do_plus
|
||||||
|
invoke Pressed3D, 6Bh
|
||||||
|
test eax, eax
|
||||||
|
jz jbox_check_minus
|
||||||
|
jbox_do_plus:
|
||||||
|
cmp jboxUserVolume, 10
|
||||||
|
jae @F
|
||||||
|
inc jboxUserVolume
|
||||||
|
@@:
|
||||||
|
mov jboxHudTimer, 90 ; Show HUD for ~1.5s
|
||||||
|
call UpdateJukeboxAudio3D
|
||||||
|
ret
|
||||||
|
|
||||||
|
jbox_check_minus:
|
||||||
|
; '-' key: VK_OEM_MINUS (0BDh) or VK_SUBTRACT (6Dh - numpad -)
|
||||||
|
invoke Pressed3D, 0BDh
|
||||||
|
test eax, eax
|
||||||
|
jnz jbox_do_minus
|
||||||
|
invoke Pressed3D, 6Dh
|
||||||
|
test eax, eax
|
||||||
|
jz jbox_keys_done
|
||||||
|
jbox_do_minus:
|
||||||
|
cmp jboxUserVolume, 0
|
||||||
|
jbe @F
|
||||||
|
dec jboxUserVolume
|
||||||
|
@@:
|
||||||
|
mov jboxHudTimer, 90 ; Show HUD for ~1.5s
|
||||||
|
call UpdateJukeboxAudio3D
|
||||||
|
|
||||||
|
jbox_keys_done:
|
||||||
|
ret
|
||||||
|
HandleJukeboxKeys3D ENDP
|
||||||
|
|
||||||
|
RenderJukeboxVolumeHUD PROC uses ebx esi edi
|
||||||
|
local winX:DWORD, winY:DWORD, barX:DWORD, barY:DWORD, barW:DWORD, barH:DWORD
|
||||||
|
local numBuf[16]:BYTE
|
||||||
|
|
||||||
|
; 1. Update Timer and Animation Position
|
||||||
|
cmp jboxHudTimer, 0
|
||||||
|
je hud_sliding_down
|
||||||
|
|
||||||
|
; Active: decrement timer
|
||||||
|
dec jboxHudTimer
|
||||||
|
; Slide UP towards target Y = 206
|
||||||
|
cmp jboxHudY, 206
|
||||||
|
jbe hud_at_target
|
||||||
|
sub jboxHudY, 4
|
||||||
|
cmp jboxHudY, 206
|
||||||
|
jae hud_at_target
|
||||||
|
mov jboxHudY, 206
|
||||||
|
hud_at_target:
|
||||||
|
jmp hud_draw
|
||||||
|
|
||||||
|
hud_sliding_down:
|
||||||
|
; Timer expired: slide DOWN towards 240 (offscreen)
|
||||||
|
cmp jboxHudY, 240
|
||||||
|
jae hud_hidden
|
||||||
|
add jboxHudY, 3
|
||||||
|
cmp jboxHudY, 240
|
||||||
|
jbe hud_draw
|
||||||
|
mov jboxHudY, 240
|
||||||
|
hud_hidden:
|
||||||
|
ret
|
||||||
|
|
||||||
|
hud_draw:
|
||||||
|
mov eax, jboxHudY
|
||||||
|
mov winY, eax
|
||||||
|
mov winX, 72 ; Centered in 320: width 176
|
||||||
|
|
||||||
|
; 2. Background and Frame
|
||||||
|
; Outer dark shadow (178 x 30)
|
||||||
|
mov eax, winX
|
||||||
|
dec eax
|
||||||
|
mov ecx, winY
|
||||||
|
dec ecx
|
||||||
|
invoke DrawBox, eax, ecx, 178, 30, 0008100Ch
|
||||||
|
|
||||||
|
; Outer bezel (176 x 28)
|
||||||
|
invoke DrawBox, winX, winY, 176, 28, 001C2E24h
|
||||||
|
|
||||||
|
; Inner panel body (174 x 26)
|
||||||
|
mov eax, winX
|
||||||
|
inc eax
|
||||||
|
mov ecx, winY
|
||||||
|
inc ecx
|
||||||
|
invoke DrawBox, eax, ecx, 174, 26, 00101A14h
|
||||||
|
|
||||||
|
; Brass top trim accent (174 x 1)
|
||||||
|
mov eax, winX
|
||||||
|
inc eax
|
||||||
|
mov ecx, winY
|
||||||
|
inc ecx
|
||||||
|
invoke DrawBox, eax, ecx, 174, 1, 008A6B34h
|
||||||
|
|
||||||
|
; 3. Title text: "MUZ. AVTOMAT: " + percentage
|
||||||
|
mov eax, winX
|
||||||
|
add eax, 10
|
||||||
|
mov ecx, winY
|
||||||
|
add ecx, 4
|
||||||
|
invoke DrawString8x8, eax, ecx, OFFSET jboxHudTitle, 0050FF90h
|
||||||
|
|
||||||
|
; Draw percentage or "VYKL"
|
||||||
|
mov eax, winX
|
||||||
|
add eax, 126
|
||||||
|
mov ecx, winY
|
||||||
|
add ecx, 4
|
||||||
|
cmp jboxUserVolume, 0
|
||||||
|
jne hud_draw_pct_num
|
||||||
|
invoke DrawString8x8, eax, ecx, OFFSET jboxHudMute, 008CB8A5h
|
||||||
|
jmp hud_draw_trough
|
||||||
|
|
||||||
|
hud_draw_pct_num:
|
||||||
|
push eax
|
||||||
|
push ecx
|
||||||
|
mov edx, jboxUserVolume
|
||||||
|
imul edx, 10
|
||||||
|
lea edi, numBuf
|
||||||
|
invoke FormatNumber, edx, edi
|
||||||
|
hud_find_term:
|
||||||
|
cmp byte ptr [edi], 0
|
||||||
|
je hud_append_pct
|
||||||
|
inc edi
|
||||||
|
jmp hud_find_term
|
||||||
|
hud_append_pct:
|
||||||
|
mov byte ptr [edi], '%'
|
||||||
|
mov byte ptr [edi+1], 0
|
||||||
|
pop ecx
|
||||||
|
pop eax
|
||||||
|
lea edx, numBuf
|
||||||
|
invoke DrawString8x8, eax, ecx, edx, 00F0E6C3h
|
||||||
|
|
||||||
|
hud_draw_trough:
|
||||||
|
; Progress trough at barX = winX + 24, barY = winY + 15, barW = 128, barH = 7
|
||||||
|
mov eax, winX
|
||||||
|
add eax, 24
|
||||||
|
mov barX, eax
|
||||||
|
mov ecx, winY
|
||||||
|
add ecx, 15
|
||||||
|
mov barY, ecx
|
||||||
|
mov barW, 128
|
||||||
|
mov barH, 7
|
||||||
|
|
||||||
|
; Trough border
|
||||||
|
mov eax, barX
|
||||||
|
dec eax
|
||||||
|
mov ecx, barY
|
||||||
|
dec ecx
|
||||||
|
invoke DrawBox, eax, ecx, 130, 9, 00223828h
|
||||||
|
invoke DrawBox, barX, barY, 128, 7, 000C1610h
|
||||||
|
|
||||||
|
; Draw 10 blocks: i = 0..9
|
||||||
|
xor esi, esi
|
||||||
|
hud_blocks_loop:
|
||||||
|
mov eax, barX
|
||||||
|
add eax, 4
|
||||||
|
mov edx, esi
|
||||||
|
imul edx, 12
|
||||||
|
add eax, edx ; blockX = barX + 4 + i*12
|
||||||
|
|
||||||
|
mov ecx, barY
|
||||||
|
inc ecx ; blockY = barY + 1
|
||||||
|
|
||||||
|
cmp esi, jboxUserVolume
|
||||||
|
jae hud_block_empty
|
||||||
|
|
||||||
|
; Filled block color gradient
|
||||||
|
cmp esi, 7
|
||||||
|
jb hud_col_green
|
||||||
|
cmp esi, 9
|
||||||
|
jb hud_col_gold
|
||||||
|
mov edx, 00465AFFh ; Red/orange accent for 100%
|
||||||
|
jmp hud_draw_block_rect
|
||||||
|
hud_col_green:
|
||||||
|
mov edx, 0050FF90h ; Phosphor green
|
||||||
|
jmp hud_draw_block_rect
|
||||||
|
hud_col_gold:
|
||||||
|
mov edx, 00FFD700h ; Golden amber
|
||||||
|
jmp hud_draw_block_rect
|
||||||
|
|
||||||
|
hud_block_empty:
|
||||||
|
mov edx, 0018261Eh ; Dim background
|
||||||
|
|
||||||
|
hud_draw_block_rect:
|
||||||
|
invoke DrawBox, eax, ecx, 10, 5, edx
|
||||||
|
|
||||||
|
inc esi
|
||||||
|
cmp esi, 10
|
||||||
|
jb hud_blocks_loop
|
||||||
|
|
||||||
|
; Draw '-' and '+' labels
|
||||||
|
mov eax, winX
|
||||||
|
add eax, 8
|
||||||
|
mov ecx, winY
|
||||||
|
add ecx, 15
|
||||||
|
invoke DrawString8x8, eax, ecx, OFFSET jboxHudMinus, 008CB8A5h
|
||||||
|
|
||||||
|
mov eax, winX
|
||||||
|
add eax, 158
|
||||||
|
mov ecx, winY
|
||||||
|
add ecx, 15
|
||||||
|
invoke DrawString8x8, eax, ecx, OFFSET jboxHudPlus, 008CB8A5h
|
||||||
|
|
||||||
|
ret
|
||||||
|
RenderJukeboxVolumeHUD ENDP
|
||||||
87
src_graphics/jackbox_data.inc
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
; ==============================================================================
|
||||||
|
; JACKBOX / JUKEBOX DATA - MASM32 / CP1251
|
||||||
|
; ==============================================================================
|
||||||
|
|
||||||
|
MODE_JACKBOX equ 28
|
||||||
|
INTERACT_JACKBOX equ 48
|
||||||
|
|
||||||
|
JACKBOX_X equ FP_1*306/10
|
||||||
|
JACKBOX_Y equ FP_1*235/10
|
||||||
|
JACKBOX_TRACK_COUNT equ 3
|
||||||
|
|
||||||
|
MATERIAL_JACKBOX_FRONT equ 161
|
||||||
|
MATERIAL_JACKBOX_SIDE equ 121
|
||||||
|
MATERIAL_JACKBOX_BACK equ 163
|
||||||
|
|
||||||
|
LoadJackboxArt3D PROTO
|
||||||
|
FreeJackboxArt3D PROTO
|
||||||
|
ResolveJackboxSurface3D PROTO :DWORD
|
||||||
|
RenderJackboxCabinet3D PROTO
|
||||||
|
JackboxCanWalk3D PROTO :DWORD,:DWORD
|
||||||
|
PlayJukeboxTrack3D PROTO :DWORD
|
||||||
|
StopJukeboxTrack3D PROTO
|
||||||
|
BeginJackbox3D PROTO
|
||||||
|
HandleJackbox3D PROTO
|
||||||
|
RenderJackbox3D PROTO
|
||||||
|
CalculateJukeboxFalloff PROTO
|
||||||
|
UpdateJukeboxAudio3D PROTO
|
||||||
|
HandleJukeboxKeys3D PROTO
|
||||||
|
RenderJukeboxVolumeHUD 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
|
||||||
|
|
||||||
|
; Prompt: "E: Vkljuchit avtomat"
|
||||||
|
jackboxPrompt3D db 69,58,32,194,234,235,254,247,232,242,252,32,224,226,242,238,236,224,242,0
|
||||||
|
|
||||||
|
jboxCurrentTrack dd -1
|
||||||
|
jboxSelectedTrack dd 0
|
||||||
|
jboxIsPlaying dd 0
|
||||||
|
|
||||||
|
; Volume control & 3D sound state
|
||||||
|
jboxUserVolume dd 8
|
||||||
|
jboxEffectiveVolume dd -1
|
||||||
|
jboxHudTimer dd 0
|
||||||
|
jboxHudY dd 240
|
||||||
|
|
||||||
|
jboxCloseCmd db "close jboxmusic",0
|
||||||
|
jboxStopCmd db "stop jboxmusic",0
|
||||||
|
jboxPlayCmd db "play jboxmusic repeat",0
|
||||||
|
jboxVolCmdPrefix db "setaudio jboxmusic volume to ",0
|
||||||
|
jboxVolCmdBuf db 64 dup(0)
|
||||||
|
|
||||||
|
; Popup HUD labels in CP1251:
|
||||||
|
; "MUZ. AVTOMAT: "
|
||||||
|
jboxHudTitle db 204,211,199,46,32,192,194,210,206,204,192,210,58,32,0
|
||||||
|
; "VYKL"
|
||||||
|
jboxHudMute db 194,251,234,235,0
|
||||||
|
jboxHudMinus db "-",0
|
||||||
|
jboxHudPlus db "+",0
|
||||||
|
|
||||||
|
jboxOpen0 db 'open "assets\music\jackbox\BYBOLDEN. - Art Deco.mp3" type mpegvideo alias jboxmusic',0
|
||||||
|
jboxOpen1 db 'open "assets\music\jackbox\BYBOLDEN. - Soft Hours.mp3" type mpegvideo alias jboxmusic',0
|
||||||
|
|
||||||
|
jboxOpenCommands dd OFFSET jboxOpen0, OFFSET jboxOpen1, 0
|
||||||
|
|
||||||
|
jboxName0 db "1. Bolden - Art Deco",0
|
||||||
|
jboxName1 db "2. Bolden - Soft Hours",0
|
||||||
|
; "0. [Vykljuchit zvuk]"
|
||||||
|
jboxName2 db 48,46,32,91,194,251,234,235,254,247,232,242,252,32,231,226,243,234,93,0
|
||||||
|
|
||||||
|
jboxTrackNames dd OFFSET jboxName0, OFFSET jboxName1, OFFSET jboxName2
|
||||||
|
|
||||||
|
; Top Digital LCD Display Slots
|
||||||
|
jboxLcdPlay db 200,195,208,192,197,210,0 ; "IGRAET"
|
||||||
|
jboxLcdStop db 209,210,206,207,0 ; "STOP"
|
||||||
|
jboxLcdExit db 194,219,213,206,196,0 ; "VYHOD"
|
||||||
|
|
||||||
|
; CRT Monitor Screen Text
|
||||||
|
jboxHdrNow db 209,197,201,215,192,209,58,0 ; "SEJCHAS:"
|
||||||
|
jboxHdrStop db 209,210,192,210,211,209,58,32,206,209,210,192,205,206,194,203,197,205,0 ; "STATUS: OSTANOVLEN"
|
||||||
|
jboxCursor db ">",0
|
||||||
|
jboxTagPlay db "[>]",0
|
||||||
|
; "ENTER:Pusk S:Stop R:Miks"
|
||||||
|
jboxFooterHelp db 69,78,84,69,82,58,207,243,241,234,32,32,83,58,209,242,238,239,32,32,82,58,204,232,234,241,0
|
||||||
|
|
@ -1,9 +1,73 @@
|
||||||
|
ResolveHellAssetPath PROC uses ebx esi edi, inPath:DWORD, outPathBuf:DWORD
|
||||||
|
cmp tournamentMode, 1
|
||||||
|
jne resolve_orig
|
||||||
|
mov esi, inPath
|
||||||
|
test esi, esi
|
||||||
|
jz resolve_orig
|
||||||
|
|
||||||
|
xor ecx, ecx
|
||||||
|
xor edx, edx
|
||||||
|
find_dot_loop:
|
||||||
|
mov al, byte ptr [esi+edx]
|
||||||
|
test al, al
|
||||||
|
jz check_dot
|
||||||
|
cmp al, '.'
|
||||||
|
jne next_char
|
||||||
|
mov ecx, edx
|
||||||
|
next_char:
|
||||||
|
inc edx
|
||||||
|
jmp find_dot_loop
|
||||||
|
|
||||||
|
check_dot:
|
||||||
|
test ecx, ecx
|
||||||
|
jz resolve_orig
|
||||||
|
|
||||||
|
mov edi, outPathBuf
|
||||||
|
xor edx, edx
|
||||||
|
copy_base:
|
||||||
|
cmp edx, ecx
|
||||||
|
jae append_suffix
|
||||||
|
mov al, byte ptr [esi+edx]
|
||||||
|
mov byte ptr [edi], al
|
||||||
|
inc edi
|
||||||
|
inc edx
|
||||||
|
jmp copy_base
|
||||||
|
|
||||||
|
append_suffix:
|
||||||
|
mov byte ptr [edi], '-'
|
||||||
|
mov byte ptr [edi+1], 'h'
|
||||||
|
mov byte ptr [edi+2], 'e'
|
||||||
|
mov byte ptr [edi+3], 'l'
|
||||||
|
mov byte ptr [edi+4], 'l'
|
||||||
|
add edi, 5
|
||||||
|
|
||||||
|
copy_ext:
|
||||||
|
mov al, byte ptr [esi+ecx]
|
||||||
|
mov byte ptr [edi], al
|
||||||
|
inc edi
|
||||||
|
inc ecx
|
||||||
|
test al, al
|
||||||
|
jnz copy_ext
|
||||||
|
|
||||||
|
invoke GetFileAttributesA, outPathBuf
|
||||||
|
cmp eax, 0FFFFFFFFh
|
||||||
|
je resolve_orig
|
||||||
|
mov eax, outPathBuf
|
||||||
|
ret
|
||||||
|
|
||||||
|
resolve_orig:
|
||||||
|
mov eax, inPath
|
||||||
|
ret
|
||||||
|
ResolveHellAssetPath ENDP
|
||||||
|
|
||||||
LoadAnimPack3D PROC uses ebx esi edi pack:DWORD,path:DWORD
|
LoadAnimPack3D PROC uses ebx esi edi pack:DWORD,path:DWORD
|
||||||
local io:DWORD,heap:DWORD,allocation:DWORD
|
local io:DWORD,heap:DWORD,allocation:DWORD
|
||||||
|
local resolvedPath[260]:BYTE
|
||||||
mov esi, pack
|
mov esi, pack
|
||||||
cmp [esi].AnimPack3D.pixels, 0
|
cmp [esi].AnimPack3D.pixels, 0
|
||||||
jne anim_load_success
|
jne anim_load_success
|
||||||
invoke CreateFileA, path, 80000000h, 1, 0, 3, 80h, 0
|
invoke ResolveHellAssetPath, path, ADDR resolvedPath
|
||||||
|
invoke CreateFileA, eax, 80000000h, 1, 0, 3, 80h, 0
|
||||||
cmp eax, -1
|
cmp eax, -1
|
||||||
je anim_load_failure
|
je anim_load_failure
|
||||||
mov ebx, eax
|
mov ebx, eax
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
95
tools/fortune_ai/download_model.py
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))
|
||||||
|
MANIFEST_PATH = os.path.join(ROOT, 'ai/model.json')
|
||||||
|
|
||||||
|
with open(MANIFEST_PATH, 'r', encoding='utf-8') as f:
|
||||||
|
manifest = json.load(f)
|
||||||
|
|
||||||
|
dest_path = os.path.join(ROOT, 'ai', manifest['file'])
|
||||||
|
part_path = dest_path + '.part'
|
||||||
|
os.makedirs(os.path.dirname(dest_path), exist_ok=True)
|
||||||
|
|
||||||
|
expected_bytes = manifest['bytes']
|
||||||
|
expected_sha256 = manifest['sha256'].lower()
|
||||||
|
|
||||||
|
if os.path.exists(dest_path):
|
||||||
|
actual_bytes = os.path.getsize(dest_path)
|
||||||
|
if actual_bytes == expected_bytes:
|
||||||
|
print(f"[INFO] Destination file already exists ({actual_bytes} bytes). Verifying SHA256...")
|
||||||
|
h = hashlib.sha256()
|
||||||
|
with open(dest_path, 'rb') as f:
|
||||||
|
while chunk := f.read(1024 * 1024):
|
||||||
|
h.update(chunk)
|
||||||
|
if h.hexdigest().lower() == expected_sha256:
|
||||||
|
print("[SUCCESS] Model already downloaded and verified!")
|
||||||
|
sys.exit(0)
|
||||||
|
else:
|
||||||
|
print("[WARN] Existing file has checksum mismatch, re-downloading...")
|
||||||
|
os.remove(dest_path)
|
||||||
|
|
||||||
|
url = f"https://huggingface.co/{manifest['source']}/resolve/{manifest['revision']}/{manifest['source_file']}?download=true"
|
||||||
|
print(f"Downloading model from:\n{url}")
|
||||||
|
print(f"Destination: {dest_path}")
|
||||||
|
print(f"Expected size: {expected_bytes} bytes (~{expected_bytes / (1024**3):.2f} GB)")
|
||||||
|
|
||||||
|
headers = {'User-Agent': 'Mozilla/5.0'}
|
||||||
|
existing_bytes = 0
|
||||||
|
if os.path.exists(part_path):
|
||||||
|
existing_bytes = os.path.getsize(part_path)
|
||||||
|
if existing_bytes < expected_bytes:
|
||||||
|
print(f"Resuming download from byte {existing_bytes}...")
|
||||||
|
headers['Range'] = f"bytes={existing_bytes}-"
|
||||||
|
else:
|
||||||
|
existing_bytes = 0
|
||||||
|
os.remove(part_path)
|
||||||
|
|
||||||
|
req = urllib.request.Request(url, headers=headers)
|
||||||
|
try:
|
||||||
|
response = urllib.request.urlopen(req, timeout=30)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ERROR] Failed to open URL: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
mode = 'ab' if existing_bytes > 0 else 'wb'
|
||||||
|
downloaded = existing_bytes
|
||||||
|
start_time = time.time()
|
||||||
|
last_report_time = start_time
|
||||||
|
last_report_bytes = downloaded
|
||||||
|
|
||||||
|
with open(part_path, mode) as out:
|
||||||
|
while True:
|
||||||
|
chunk = response.read(1024 * 512) # 512 KB
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
out.write(chunk)
|
||||||
|
downloaded += len(chunk)
|
||||||
|
now = time.time()
|
||||||
|
if now - last_report_time >= 5.0 or downloaded == expected_bytes:
|
||||||
|
speed_mb = (downloaded - last_report_bytes) / (now - last_report_time) / (1024 * 1024)
|
||||||
|
pct = (downloaded / expected_bytes) * 100.0
|
||||||
|
print(f"Progress: {downloaded / (1024**3):.2f} / {expected_bytes / (1024**3):.2f} GB ({pct:.1f}%) | {speed_mb:.2f} MB/s")
|
||||||
|
last_report_time = now
|
||||||
|
last_report_bytes = downloaded
|
||||||
|
|
||||||
|
print("\nDownload finished! Verifying SHA256 checksum...")
|
||||||
|
h = hashlib.sha256()
|
||||||
|
with open(part_path, 'rb') as f:
|
||||||
|
while chunk := f.read(1024 * 1024):
|
||||||
|
h.update(chunk)
|
||||||
|
|
||||||
|
actual_sha256 = h.hexdigest().lower()
|
||||||
|
if actual_sha256 != expected_sha256:
|
||||||
|
print(f"[ERROR] SHA256 mismatch!\nExpected: {expected_sha256}\nGot: {actual_sha256}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if os.path.exists(dest_path):
|
||||||
|
os.remove(dest_path)
|
||||||
|
os.rename(part_path, dest_path)
|
||||||
|
print("[SUCCESS] Model successfully downloaded and verified!")
|
||||||
105
tools/generators/copy_hell_tower_assets.ps1
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
# Copy all assets used by Hell Tower and its 10 games into assets\hell_tower
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
$srcRoot = "assets"
|
||||||
|
$dstRoot = "assets\hell_tower"
|
||||||
|
|
||||||
|
if (-not (Test-Path $dstRoot)) {
|
||||||
|
New-Item -ItemType Directory -Path $dstRoot -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
function Copy-AssetDir($relSrc, $relDst) {
|
||||||
|
$src = Join-Path $srcRoot $relSrc
|
||||||
|
$dst = Join-Path $dstRoot $relDst
|
||||||
|
if (-not (Test-Path $dst)) {
|
||||||
|
New-Item -ItemType Directory -Path $dst -Force | Out-Null
|
||||||
|
}
|
||||||
|
Write-Host "Copying directory $relSrc -> $relDst..."
|
||||||
|
Get-ChildItem -Path $src | ForEach-Object {
|
||||||
|
$destFile = Join-Path $dst $_.Name
|
||||||
|
Copy-Item -Path $_.FullName -Destination $destFile -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Copy-AssetFile($relSrc, $relDst) {
|
||||||
|
$src = Join-Path $srcRoot $relSrc
|
||||||
|
$dst = Join-Path $dstRoot $relDst
|
||||||
|
$dstDir = Split-Path $dst -Parent
|
||||||
|
if (-not (Test-Path $dstDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $dstDir -Force | Out-Null
|
||||||
|
}
|
||||||
|
Write-Host "Copying file $relSrc -> $relDst..."
|
||||||
|
Copy-Item -Path $src -Destination $dst -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
# 1. Animations
|
||||||
|
Copy-AssetDir "animations\buckshot" "animations\buckshot"
|
||||||
|
Copy-AssetDir "animations\coin" "animations\coin"
|
||||||
|
Copy-AssetDir "animations\dice" "animations\dice"
|
||||||
|
Copy-AssetDir "animations\roulette" "animations\roulette"
|
||||||
|
Copy-AssetDir "animations\tables" "animations\tables"
|
||||||
|
Copy-AssetDir "animations\wheel" "animations\wheel"
|
||||||
|
Copy-AssetFile "animations\dbd_hook.ans" "animations\dbd_hook.ans"
|
||||||
|
|
||||||
|
# 2. Sprites
|
||||||
|
Copy-AssetDir "sprites\buckshot" "sprites\buckshot"
|
||||||
|
Copy-AssetDir "sprites\coin" "sprites\coin"
|
||||||
|
Copy-AssetDir "sprites\dice" "sprites\dice"
|
||||||
|
Copy-AssetDir "sprites\roulette" "sprites\roulette"
|
||||||
|
Copy-AssetDir "sprites\tables" "sprites\tables"
|
||||||
|
Copy-AssetDir "sprites\wheel" "sprites\wheel"
|
||||||
|
|
||||||
|
# 3. Music (Tower OST)
|
||||||
|
Copy-AssetDir "music\tower" "music\tower"
|
||||||
|
Copy-AssetDir "music\tower" "music"
|
||||||
|
|
||||||
|
# 4. Textures
|
||||||
|
Copy-AssetFile "textures\card-table.bin" "textures\card-table.bin"
|
||||||
|
Copy-AssetFile "textures\sprite_blackjack_table.png" "textures\sprite_blackjack_table.png"
|
||||||
|
Copy-AssetFile "textures\sprite_roulette_table.png" "textures\sprite_roulette_table.png"
|
||||||
|
Copy-AssetFile "textures\tex_wall_hell.png" "textures\tex_wall_hell.png"
|
||||||
|
|
||||||
|
# 5. Data
|
||||||
|
Copy-AssetFile "data\dealer_phrases.txt" "data\dealer_phrases.txt"
|
||||||
|
Copy-AssetFile "data\poker-ranks.bin" "data\poker-ranks.bin"
|
||||||
|
|
||||||
|
# 6. Audio
|
||||||
|
# Generated SFX
|
||||||
|
$genSfx = @(
|
||||||
|
"buckshot-pump.wav",
|
||||||
|
"dice-roll.wav",
|
||||||
|
"round-loss.wav",
|
||||||
|
"round-win.wav",
|
||||||
|
"skill-hit.wav",
|
||||||
|
"skill-miss.wav",
|
||||||
|
"skill-perfect.wav",
|
||||||
|
"ui-tick.wav"
|
||||||
|
)
|
||||||
|
foreach ($sfx in $genSfx) {
|
||||||
|
Copy-AssetFile "audio\generated\$sfx" "audio\generated\$sfx"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ASCII root audio
|
||||||
|
$rootAudio = @(
|
||||||
|
"b05e1ac8be34248.wav",
|
||||||
|
"hKhoJArfEGAxxMewx0UQiddakTdBjFc.wav",
|
||||||
|
"moving-stone.wav",
|
||||||
|
"playing_cards_deck_flick_through_001_20485.wav",
|
||||||
|
"revolverdryfirec.wav",
|
||||||
|
"roulette-wheel-throw-2.wav",
|
||||||
|
"toy-gun-revolver-close-chamber-spin_f1ruc5nd.wav",
|
||||||
|
"victory-tf2.wav"
|
||||||
|
)
|
||||||
|
foreach ($aud in $rootAudio) {
|
||||||
|
Copy-AssetFile "audio\$aud" "audio\$aud"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Russian audio files matching tower/game sound effects by exact size to avoid codepage issues
|
||||||
|
$dstAudioDir = Join-Path $dstRoot "audio"
|
||||||
|
$targetSizes = @(1466446, 228342, 212174, 757838)
|
||||||
|
Get-ChildItem "assets\audio\*.wav" | Where-Object { $targetSizes -contains $_.Length } | ForEach-Object {
|
||||||
|
Write-Host "Copying audio file $($_.Name) -> audio\$($_.Name)..."
|
||||||
|
Copy-Item -Path $_.FullName -Destination (Join-Path $dstAudioDir $_.Name) -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "All Hell Tower assets successfully copied!"
|
||||||
148
tools/generators/pack_hell_tower_assets.py
Normal file
|
|
@ -0,0 +1,148 @@
|
||||||
|
"""Compile custom Hell Tower assets and menu art into engine-ready binary packs."""
|
||||||
|
import os
|
||||||
|
import struct
|
||||||
|
from pathlib import Path
|
||||||
|
from PIL import Image, ImageOps
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
HELL_DIR = ROOT / "assets/hell_tower"
|
||||||
|
|
||||||
|
|
||||||
|
def write_anm1_pack(dst_path: Path, frames: list):
|
||||||
|
dst_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
width, height = frames[0].size
|
||||||
|
if not (0 < len(frames) <= 64) or not all(f.size == (width, height) for f in frames):
|
||||||
|
raise ValueError(f"Invalid frames for {dst_path.name}")
|
||||||
|
raw_bgra = b"".join(f.tobytes("raw", "BGRA") for f in frames)
|
||||||
|
header = struct.pack("<4sIIII", b"ANM1", width, height, len(frames), len(raw_bgra))
|
||||||
|
dst_path.write_bytes(header + raw_bgra)
|
||||||
|
print(f"[OK] Wrote ANM1 pack: {dst_path} ({len(frames)} frames, {width}x{height}, {len(header + raw_bgra)} bytes)")
|
||||||
|
|
||||||
|
|
||||||
|
def write_cta1_pack(dst_path: Path, image: Image.Image):
|
||||||
|
dst_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
image = image.convert("RGBA").resize((640, 480), Image.Resampling.LANCZOS)
|
||||||
|
raw_bgra = image.tobytes("raw", "BGRA")
|
||||||
|
header = struct.pack("<4sIII", b"CTA1", 640, 480, len(raw_bgra))
|
||||||
|
dst_path.write_bytes(header + raw_bgra)
|
||||||
|
print(f"[OK] Wrote CTA1 pack: {dst_path} (640x480, {len(header + raw_bgra)} bytes)")
|
||||||
|
|
||||||
|
|
||||||
|
def slice_sprite_sheet(path: Path, cols: int, rows: int, cell_size: tuple, props: bool = False):
|
||||||
|
source = Image.open(path).convert("RGBA")
|
||||||
|
cells = []
|
||||||
|
for row in range(rows):
|
||||||
|
for col in range(cols):
|
||||||
|
cell = source.crop((
|
||||||
|
col * source.width // cols,
|
||||||
|
row * source.height // rows,
|
||||||
|
(col + 1) * source.width // cols,
|
||||||
|
(row + 1) * source.height // rows
|
||||||
|
))
|
||||||
|
cell.putalpha(cell.getchannel("A").point(lambda a: 255 if a >= 128 else 0))
|
||||||
|
bounds = cell.getchannel("A").getbbox()
|
||||||
|
if bounds is None:
|
||||||
|
bounds = (0, 0, cell.width, cell.height)
|
||||||
|
cells.append(cell.crop(bounds))
|
||||||
|
|
||||||
|
factor = min((cell_size[0] - 12) / max(c.width for c in cells),
|
||||||
|
(cell_size[1] - 12) / max(c.height for c in cells))
|
||||||
|
frames = []
|
||||||
|
for cell in cells:
|
||||||
|
scale = min((cell_size[0] - 12) / cell.width, (cell_size[1] - 12) / cell.height) if props else factor
|
||||||
|
cell = cell.resize((round(cell.width * scale), round(cell.height * scale)), Image.Resampling.NEAREST)
|
||||||
|
frame = Image.new("RGBA", cell_size)
|
||||||
|
y = (cell_size[1] - cell.height) // 2 if props else cell_size[1] - 6 - cell.height
|
||||||
|
frame.alpha_composite(cell, ((cell_size[0] - cell.width) // 2, y))
|
||||||
|
frames.append(frame)
|
||||||
|
return frames
|
||||||
|
|
||||||
|
|
||||||
|
def pack_all():
|
||||||
|
print("Packing Hell Tower assets...")
|
||||||
|
|
||||||
|
# 1. Hell Tower Menu background
|
||||||
|
menu_candidates = [
|
||||||
|
HELL_DIR / "environment/HellTower_ menu.jpg",
|
||||||
|
HELL_DIR / "environment/HellTower_menu.jpg",
|
||||||
|
HELL_DIR / "environment/HellTower_ menu.png",
|
||||||
|
HELL_DIR / "environment/HellTower_menu.png"
|
||||||
|
]
|
||||||
|
menu_src = None
|
||||||
|
for mc in menu_candidates:
|
||||||
|
if mc.exists():
|
||||||
|
menu_src = mc
|
||||||
|
break
|
||||||
|
if menu_src:
|
||||||
|
menu_im = Image.open(menu_src).convert("RGBA").resize((640, 480), Image.Resampling.LANCZOS)
|
||||||
|
write_anm1_pack(ROOT / "assets/animations/hall/helltower-menu.bin", [menu_im])
|
||||||
|
else:
|
||||||
|
print("[WARN] HellTower_menu image not found!")
|
||||||
|
|
||||||
|
# 2. Coin table hell
|
||||||
|
coin_src = HELL_DIR / "01_coin/table-v1-hell.jpg"
|
||||||
|
if not coin_src.exists():
|
||||||
|
coin_src = HELL_DIR / "01_coin/table-v1-hell.png"
|
||||||
|
if coin_src.exists():
|
||||||
|
bg = Image.open(coin_src).convert("RGBA").resize((640, 480), Image.Resampling.NEAREST)
|
||||||
|
framed = Image.new("RGBA", (640, 480), (20, 25, 29, 255))
|
||||||
|
framed.paste(bg.crop((0, 0, 640, 440)), (0, 40))
|
||||||
|
framed.putalpha(255)
|
||||||
|
write_anm1_pack(ROOT / "assets/animations/coin/table-v1-hell.bin", [framed])
|
||||||
|
|
||||||
|
# 3. Card table hell (Blackjack, Poker, Durak)
|
||||||
|
card_src = HELL_DIR / "04_05_08_card_tables/card-table-pixel-v3-hell.jpg"
|
||||||
|
if not card_src.exists():
|
||||||
|
card_src = HELL_DIR / "04_05_08_card_tables/card-table-pixel-v3-hell.png"
|
||||||
|
if card_src.exists():
|
||||||
|
write_cta1_pack(ROOT / "assets/textures/card-table-hell.bin", Image.open(card_src))
|
||||||
|
|
||||||
|
# 4. Wheel closeup & table hell
|
||||||
|
wheel_closeup = HELL_DIR / "07_casino_roulette/closeup-v1-hell.jpg"
|
||||||
|
if not wheel_closeup.exists():
|
||||||
|
wheel_closeup = HELL_DIR / "07_casino_roulette/closeup-v1-hell.png"
|
||||||
|
if wheel_closeup.exists():
|
||||||
|
w_img = Image.open(wheel_closeup).convert("RGBA").resize((640, 480), Image.Resampling.NEAREST)
|
||||||
|
w_img.putalpha(255)
|
||||||
|
write_anm1_pack(ROOT / "assets/animations/wheel/closeup-v1-hell.bin", [w_img])
|
||||||
|
|
||||||
|
wheel_table = HELL_DIR / "07_casino_roulette/table-v1-hell.jpg"
|
||||||
|
if not wheel_table.exists():
|
||||||
|
wheel_table = HELL_DIR / "07_casino_roulette/table-v1-hell.png"
|
||||||
|
if wheel_table.exists():
|
||||||
|
w_img = Image.open(wheel_table).convert("RGBA").resize((640, 480), Image.Resampling.NEAREST)
|
||||||
|
w_img.putalpha(255)
|
||||||
|
write_anm1_pack(ROOT / "assets/animations/wheel/table-v1-hell.bin", [w_img])
|
||||||
|
|
||||||
|
# 5. Buckshot (and Russian Roulette) assets
|
||||||
|
# Room
|
||||||
|
bs_room = HELL_DIR / "10_buckshot/room-v2-hell.jpg"
|
||||||
|
if not bs_room.exists():
|
||||||
|
bs_room = HELL_DIR / "10_buckshot/room-v2-hell.png"
|
||||||
|
if bs_room.exists():
|
||||||
|
room_im = ImageOps.fit(Image.open(bs_room).convert("RGBA"), (640, 480), method=Image.Resampling.NEAREST)
|
||||||
|
write_anm1_pack(ROOT / "assets/animations/buckshot/room-v2-hell.bin", [room_im])
|
||||||
|
|
||||||
|
# Dealer base
|
||||||
|
bs_dealer = HELL_DIR / "10_buckshot/dealer-source-v2-hell.png"
|
||||||
|
if bs_dealer.exists():
|
||||||
|
frames = slice_sprite_sheet(bs_dealer, 4, 4, (256, 224))
|
||||||
|
write_anm1_pack(ROOT / "assets/animations/buckshot/dealer-v2-hell.bin", frames)
|
||||||
|
|
||||||
|
# Dealer actions
|
||||||
|
bs_actions = HELL_DIR / "10_buckshot/dealer-actions-v3-hell.png"
|
||||||
|
if bs_actions.exists():
|
||||||
|
frames = slice_sprite_sheet(bs_actions, 4, 4, (256, 224))
|
||||||
|
write_anm1_pack(ROOT / "assets/animations/buckshot/dealer-actions-v3-hell.bin", frames)
|
||||||
|
|
||||||
|
# Death
|
||||||
|
bs_death = HELL_DIR / "10_buckshot/death-v3-hell.png"
|
||||||
|
if bs_death.exists():
|
||||||
|
frames = slice_sprite_sheet(bs_death, 4, 3, (256, 224))
|
||||||
|
write_anm1_pack(ROOT / "assets/animations/buckshot/death-v3-hell.bin", frames)
|
||||||
|
|
||||||
|
print("All Hell Tower asset packs generated successfully!")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
pack_all()
|
||||||
168
tools/generators/pack_jackbox.py
Normal file
|
|
@ -0,0 +1,168 @@
|
||||||
|
"""Offline packaging of Jackbox / Jukebox art for the MASM32 assembly renderer.
|
||||||
|
|
||||||
|
Processes high-resolution source images into a solid rectangular cabinet ('прямоугольный шкаф'):
|
||||||
|
- Front: Arched jukebox art framed in rich dark mahogany wood casing (solid 256x512 BGRA).
|
||||||
|
- Side: Cropped rectangular wooden cabinet side panel with chrome accents (solid 256x512 BGRA).
|
||||||
|
- Back: Matching dark walnut cabinet rear panel with ventilation slots (solid 256x512 BGRA).
|
||||||
|
- UI: 640x480 BGRA track selector screen.
|
||||||
|
Eliminates all grey studio backgrounds, transparent gaps, and seam holes.
|
||||||
|
"""
|
||||||
|
import math
|
||||||
|
from pathlib import Path
|
||||||
|
import struct
|
||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
SOURCE = ROOT / "assets/sprites/jackbox"
|
||||||
|
OUT = ROOT / "assets/animations/hall"
|
||||||
|
|
||||||
|
# Deep rich dark walnut / mahogany wood color (BGR)
|
||||||
|
WOOD_COLOR = np.array([16, 26, 48], dtype=np.uint8)
|
||||||
|
|
||||||
|
|
||||||
|
def make_solid_front(img_bgr: np.ndarray) -> np.ndarray:
|
||||||
|
"""Frame the arched jukebox front into a solid rectangular wooden cabinet face."""
|
||||||
|
h, w = img_bgr.shape[:2]
|
||||||
|
|
||||||
|
# Detect studio grey background
|
||||||
|
diff = np.maximum(
|
||||||
|
np.abs(img_bgr[:, :, 0].astype(int) - img_bgr[:, :, 1].astype(int)),
|
||||||
|
np.maximum(np.abs(img_bgr[:, :, 1].astype(int) - img_bgr[:, :, 2].astype(int)),
|
||||||
|
np.abs(img_bgr[:, :, 0].astype(int) - img_bgr[:, :, 2].astype(int)))
|
||||||
|
)
|
||||||
|
bright = np.mean(img_bgr, axis=2)
|
||||||
|
is_bg = (diff < 10) & (bright >= 18) & (bright <= 65)
|
||||||
|
|
||||||
|
# Seed from borders
|
||||||
|
seed_mask = np.zeros((h, w), dtype=np.uint8)
|
||||||
|
seed_mask[0, :] = is_bg[0, :]
|
||||||
|
seed_mask[-1, :] = is_bg[-1, :]
|
||||||
|
seed_mask[:, 0] = is_bg[:, 0]
|
||||||
|
seed_mask[:, -1] = is_bg[:, -1]
|
||||||
|
|
||||||
|
num_labels, labels = cv2.connectedComponents(is_bg.astype(np.uint8))
|
||||||
|
border_labels = np.unique(labels[seed_mask == 1])
|
||||||
|
border_labels = border_labels[border_labels != 0]
|
||||||
|
is_real_bg = np.isin(labels, border_labels)
|
||||||
|
|
||||||
|
# Replace studio grey background with dark mahogany wood casing
|
||||||
|
front_solid = img_bgr.copy()
|
||||||
|
mask = cv2.GaussianBlur(is_real_bg.astype(np.float32), (5, 5), 1.0)
|
||||||
|
for c in range(3):
|
||||||
|
front_solid[:, :, c] = np.clip(
|
||||||
|
front_solid[:, :, c] * (1.0 - mask) + WOOD_COLOR[c] * mask,
|
||||||
|
0, 255
|
||||||
|
).astype(np.uint8)
|
||||||
|
|
||||||
|
# Resize to 256x512 BGRA, 100% opaque (no transparency gaps!)
|
||||||
|
resized = cv2.resize(front_solid, (256, 512), interpolation=cv2.INTER_LANCZOS4)
|
||||||
|
bgra = cv2.cvtColor(resized, cv2.COLOR_BGR2BGRA)
|
||||||
|
bgra[:, :, 3] = 255
|
||||||
|
return bgra
|
||||||
|
|
||||||
|
|
||||||
|
def make_solid_side(img_bgr: np.ndarray) -> np.ndarray:
|
||||||
|
"""Crop side image to a solid rectangular cabinet side panel."""
|
||||||
|
# Crop to actual rectangular cabinet profile:
|
||||||
|
# y: 90..2910 (removes studio top margin and floor shadow)
|
||||||
|
# x: 120..1345 (from front edge to back vertical edge)
|
||||||
|
side_crop = img_bgr[90:2910, 120:1345]
|
||||||
|
h, w = side_crop.shape[:2]
|
||||||
|
|
||||||
|
# Detect studio grey in the top corner outside the curve
|
||||||
|
diff = np.maximum(
|
||||||
|
np.abs(side_crop[:, :, 0].astype(int) - side_crop[:, :, 1].astype(int)),
|
||||||
|
np.maximum(np.abs(side_crop[:, :, 1].astype(int) - side_crop[:, :, 2].astype(int)),
|
||||||
|
np.abs(side_crop[:, :, 0].astype(int) - side_crop[:, :, 2].astype(int)))
|
||||||
|
)
|
||||||
|
bright = np.mean(side_crop, axis=2)
|
||||||
|
is_bg = (diff < 10) & (bright >= 18) & (bright <= 65)
|
||||||
|
|
||||||
|
seed_mask = np.zeros((h, w), dtype=np.uint8)
|
||||||
|
seed_mask[0, :] = is_bg[0, :]
|
||||||
|
seed_mask[-1, :] = is_bg[-1, :]
|
||||||
|
seed_mask[:, 0] = is_bg[:, 0]
|
||||||
|
seed_mask[:, -1] = is_bg[:, -1]
|
||||||
|
|
||||||
|
num_labels, labels = cv2.connectedComponents(is_bg.astype(np.uint8))
|
||||||
|
border_labels = np.unique(labels[seed_mask == 1])
|
||||||
|
border_labels = border_labels[border_labels != 0]
|
||||||
|
is_real_bg = np.isin(labels, border_labels)
|
||||||
|
|
||||||
|
# Fill corner with dark mahogany cabinet wood
|
||||||
|
side_solid = side_crop.copy()
|
||||||
|
mask = cv2.GaussianBlur(is_real_bg.astype(np.float32), (5, 5), 1.0)
|
||||||
|
for c in range(3):
|
||||||
|
side_solid[:, :, c] = np.clip(
|
||||||
|
side_solid[:, :, c] * (1.0 - mask) + WOOD_COLOR[c] * mask,
|
||||||
|
0, 255
|
||||||
|
).astype(np.uint8)
|
||||||
|
|
||||||
|
# Left of texture (x=0) is FRONT edge; right of texture (x=max) is BACK edge.
|
||||||
|
# Matches along_x from x0 (front) to x1 (back against wall).
|
||||||
|
resized = cv2.resize(side_solid, (256, 512), interpolation=cv2.INTER_LANCZOS4)
|
||||||
|
bgra = cv2.cvtColor(resized, cv2.COLOR_BGR2BGRA)
|
||||||
|
bgra[:, :, 3] = 255
|
||||||
|
return bgra
|
||||||
|
|
||||||
|
|
||||||
|
def make_solid_back() -> np.ndarray:
|
||||||
|
"""Generate a solid dark walnut cabinet rear panel with ventilation slots."""
|
||||||
|
rear = np.zeros((512, 256, 4), dtype=np.uint8)
|
||||||
|
for y in range(512):
|
||||||
|
grain = int(math.sin(y * 0.2) * 4 + math.sin(y * 0.05) * 6)
|
||||||
|
rear[y, :, 0] = np.clip(int(WOOD_COLOR[0]) + grain, 0, 255) # B
|
||||||
|
rear[y, :, 1] = np.clip(int(WOOD_COLOR[1]) + grain + 1, 0, 255) # G
|
||||||
|
rear[y, :, 2] = np.clip(int(WOOD_COLOR[2]) + grain + 3, 0, 255) # R
|
||||||
|
rear[y, :, 3] = 255 # Fully opaque
|
||||||
|
for y in range(120, 240, 12):
|
||||||
|
rear[y:y+3, 32:224, :3] = 10 # Dark ventilation slots
|
||||||
|
return rear
|
||||||
|
|
||||||
|
|
||||||
|
def pack_bgra(path: Path, frames: list[np.ndarray], w: int, h: int):
|
||||||
|
"""Pack an array of (H, W, 4) BGRA numpy uint8 images into an ANM1 binary pack."""
|
||||||
|
raw = b"".join(f.tobytes() for f in frames)
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
header = struct.pack("<4s4I", b"ANM1", w, h, len(frames), len(raw))
|
||||||
|
path.write_bytes(header + 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}"
|
||||||
|
|
||||||
|
print("Building solid rectangular Jukebox cabinet textures...")
|
||||||
|
|
||||||
|
# 1. Front Texture: solid rectangular face
|
||||||
|
front_bgr = cv2.imread(str(front_path))
|
||||||
|
front_tex = make_solid_front(front_bgr)
|
||||||
|
|
||||||
|
# 2. Side Texture: solid rectangular side panel
|
||||||
|
side_bgr = cv2.imread(str(side_path))
|
||||||
|
side_tex = make_solid_side(side_bgr)
|
||||||
|
|
||||||
|
# 3. Rear Texture: solid dark walnut panel
|
||||||
|
rear_tex = make_solid_back()
|
||||||
|
|
||||||
|
# Pack 3D world frames: Frame 0 = Front, Frame 1 = Side, Frame 2 = Rear
|
||||||
|
pack_bgra(OUT / "jackbox-v1.bin", [front_tex, side_tex, rear_tex], 256, 512)
|
||||||
|
|
||||||
|
# 4. Screen UI Pack: 640x480 BGRA
|
||||||
|
ui_bgr = cv2.imread(str(ui_path))
|
||||||
|
ui_bgra = cv2.cvtColor(ui_bgr, cv2.COLOR_BGR2BGRA)
|
||||||
|
ui_tex = cv2.resize(ui_bgra, (640, 480), interpolation=cv2.INTER_LANCZOS4)
|
||||||
|
pack_bgra(OUT / "jackbox-ui-v1.bin", [ui_tex], 640, 480)
|
||||||
|
|
||||||
|
print("Solid Jukebox asset packaging complete.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
78
tools/generators/setup_hell_tower_sprites.ps1
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
# Clean assets\hell_tower to contain ONLY PNG sprites for Hell Tower games
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
$towerDir = "assets\hell_tower"
|
||||||
|
|
||||||
|
# 1. Clean out previous contents of assets\hell_tower
|
||||||
|
if (Test-Path $towerDir) {
|
||||||
|
Get-ChildItem -Path $towerDir | Remove-Item -Recurse -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
New-Item -ItemType Directory -Path $towerDir -Force | Out-Null
|
||||||
|
|
||||||
|
# 2. Define game sprite mappings
|
||||||
|
$games = @{
|
||||||
|
"01_coin" = @(
|
||||||
|
"assets\sprites\coin\faces-v1.png",
|
||||||
|
"assets\sprites\coin\hands-v1.png",
|
||||||
|
"assets\sprites\coin\hands-v2.png",
|
||||||
|
"assets\sprites\coin\table-v1.png"
|
||||||
|
)
|
||||||
|
"02_dice" = @(
|
||||||
|
"assets\sprites\dice\hands-v1.png",
|
||||||
|
"assets\sprites\dice\hands-v2.png"
|
||||||
|
)
|
||||||
|
"04_05_08_card_tables" = @(
|
||||||
|
"assets\sprites\tables\blackjack-guests-v1.png",
|
||||||
|
"assets\sprites\tables\blackjack-room-v1.png",
|
||||||
|
"assets\sprites\tables\card-table-active-v2.png",
|
||||||
|
"assets\sprites\tables\card-table-pixel-v3.png",
|
||||||
|
"assets\sprites\tables\card-table-v1.png",
|
||||||
|
"assets\textures\sprite_blackjack_table.png"
|
||||||
|
)
|
||||||
|
"06_russian_roulette" = @(
|
||||||
|
"assets\sprites\roulette\gman-talk-v1.png",
|
||||||
|
"assets\sprites\roulette\hands-v5.png",
|
||||||
|
"assets\sprites\roulette\participants-v1.png",
|
||||||
|
"assets\sprites\roulette\revolver-actions-v1.png"
|
||||||
|
)
|
||||||
|
"07_casino_roulette" = @(
|
||||||
|
"assets\sprites\wheel\closeup-v1.png",
|
||||||
|
"assets\sprites\wheel\table-v1.png",
|
||||||
|
"assets\textures\sprite_roulette_table.png"
|
||||||
|
)
|
||||||
|
"10_buckshot" = @(
|
||||||
|
"assets\sprites\buckshot\blood-v3.png",
|
||||||
|
"assets\sprites\buckshot\dealer-actions-v3.png",
|
||||||
|
"assets\sprites\buckshot\dealer-source-v2.png",
|
||||||
|
"assets\sprites\buckshot\death-v3.png",
|
||||||
|
"assets\sprites\buckshot\items-v2.png",
|
||||||
|
"assets\sprites\buckshot\loading-v2.png",
|
||||||
|
"assets\sprites\buckshot\medical-dealer-v5.png",
|
||||||
|
"assets\sprites\buckshot\medical-player-v5.png",
|
||||||
|
"assets\sprites\buckshot\medical-v3.png",
|
||||||
|
"assets\sprites\buckshot\props-v2.png",
|
||||||
|
"assets\sprites\buckshot\room-v2.png",
|
||||||
|
"assets\sprites\buckshot\self-flash-v4.png",
|
||||||
|
"assets\sprites\buckshot\self-v3.png",
|
||||||
|
"assets\sprites\buckshot\shots-v2.png"
|
||||||
|
)
|
||||||
|
"environment" = @(
|
||||||
|
"assets\textures\tex_wall_hell.png"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($folder in $games.Keys) {
|
||||||
|
$targetFolder = Join-Path $towerDir $folder
|
||||||
|
New-Item -ItemType Directory -Path $targetFolder -Force | Out-Null
|
||||||
|
foreach ($file in $games[$folder]) {
|
||||||
|
if (Test-Path $file) {
|
||||||
|
Copy-Item -Path $file -Destination $targetFolder -Force
|
||||||
|
Write-Host "Copied $file -> $folder"
|
||||||
|
} else {
|
||||||
|
Write-Warning "File not found: $file"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Setup finished successfully!"
|
||||||
93
tools/tests/check_hell_tower.py
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
"""Validation test suite for Hell Tower custom assets and redesigned menu."""
|
||||||
|
import os
|
||||||
|
import struct
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
|
||||||
|
|
||||||
|
def test_asset_packs():
|
||||||
|
print("[1] Verifying Hell Tower binary packs...")
|
||||||
|
anm1_packs = [
|
||||||
|
(ROOT / "assets/animations/hall/helltower-menu.bin", 640, 480, 1, 1228800),
|
||||||
|
(ROOT / "assets/animations/coin/table-v1-hell.bin", 640, 480, 1, 1228800),
|
||||||
|
(ROOT / "assets/animations/wheel/closeup-v1-hell.bin", 640, 480, 1, 1228800),
|
||||||
|
(ROOT / "assets/animations/wheel/table-v1-hell.bin", 640, 480, 1, 1228800),
|
||||||
|
(ROOT / "assets/animations/buckshot/room-v2-hell.bin", 640, 480, 1, 1228800),
|
||||||
|
(ROOT / "assets/animations/buckshot/dealer-v2-hell.bin", 256, 224, 16, 16 * 256 * 224 * 4),
|
||||||
|
(ROOT / "assets/animations/buckshot/dealer-actions-v3-hell.bin", 256, 224, 16, 16 * 256 * 224 * 4),
|
||||||
|
(ROOT / "assets/animations/buckshot/death-v3-hell.bin", 256, 224, 12, 12 * 256 * 224 * 4),
|
||||||
|
]
|
||||||
|
|
||||||
|
for path, exp_w, exp_h, exp_frames, exp_bytes in anm1_packs:
|
||||||
|
assert path.exists(), f"Missing pack: {path}"
|
||||||
|
data = path.read_bytes()
|
||||||
|
assert len(data) >= 20, f"File too small: {path}"
|
||||||
|
magic, w, h, frames, byte_count = struct.unpack("<4sIIII", data[:20])
|
||||||
|
assert magic == b"ANM1", f"Bad magic {magic} for {path}"
|
||||||
|
assert w == exp_w, f"Width mismatch: expected {exp_w}, got {w} for {path}"
|
||||||
|
assert h == exp_h, f"Height mismatch: expected {exp_h}, got {h} for {path}"
|
||||||
|
assert frames == exp_frames, f"Frames mismatch: expected {exp_frames}, got {frames} for {path}"
|
||||||
|
assert byte_count == exp_bytes, f"Bytes mismatch: expected {exp_bytes}, got {byte_count} for {path}"
|
||||||
|
assert len(data) == 20 + exp_bytes, f"File size mismatch: {len(data)} != {20 + exp_bytes}"
|
||||||
|
print(f" [OK] ANM1 {path.name}: {w}x{h}, {frames} frames, {len(data)} bytes")
|
||||||
|
|
||||||
|
# CTA1 card table pack
|
||||||
|
card_table = ROOT / "assets/textures/card-table-hell.bin"
|
||||||
|
assert card_table.exists(), "Missing card-table-hell.bin"
|
||||||
|
c_data = card_table.read_bytes()
|
||||||
|
c_magic, c_w, c_h, c_bytes = struct.unpack("<4sIII", c_data[:16])
|
||||||
|
assert c_magic == b"CTA1", f"Bad CTA1 magic: {c_magic}"
|
||||||
|
assert c_w == 640 and c_h == 480, f"Bad size: {c_w}x{c_h}"
|
||||||
|
assert c_bytes == 1228800, f"Bad bytes: {c_bytes}"
|
||||||
|
assert len(c_data) == 16 + 1228800
|
||||||
|
print(f" [OK] CTA1 card-table-hell.bin: {c_w}x{c_h}, {len(c_data)} bytes")
|
||||||
|
|
||||||
|
|
||||||
|
def test_code_integrations():
|
||||||
|
print("[2] Verifying ASM code integrations...")
|
||||||
|
# Check hell_tower_data.inc
|
||||||
|
ht_data = (ROOT / "src_graphics/hell_tower_data.inc").read_text(encoding="cp1251")
|
||||||
|
assert "hellTowerMenuArt3D" in ht_data
|
||||||
|
assert "hellTowerMenuPath3D" in ht_data
|
||||||
|
assert "towerCursorY" in ht_data
|
||||||
|
assert "towerCursorTargetY" in ht_data
|
||||||
|
assert "towerCalloutAnim" in ht_data
|
||||||
|
assert "ResolveHellAssetPath PROTO" in ht_data
|
||||||
|
assert "FreeTowerMinigameAssets PROTO" in ht_data
|
||||||
|
print(" [OK] hell_tower_data.inc verified.")
|
||||||
|
|
||||||
|
# Check scene_animation.inc
|
||||||
|
scene_anim = (ROOT / "src_graphics/scene_animation.inc").read_text(encoding="cp1251")
|
||||||
|
assert "ResolveHellAssetPath PROC" in scene_anim
|
||||||
|
assert "invoke ResolveHellAssetPath" in scene_anim
|
||||||
|
print(" [OK] scene_animation.inc verified.")
|
||||||
|
|
||||||
|
# Check card_table_assets.inc
|
||||||
|
card_assets = (ROOT / "src_graphics/card_table_assets.inc").read_text(encoding="cp1251")
|
||||||
|
assert "invoke ResolveHellAssetPath" in card_assets
|
||||||
|
print(" [OK] card_table_assets.inc verified.")
|
||||||
|
|
||||||
|
# Check hell_tower.inc
|
||||||
|
ht_code = (ROOT / "src_graphics/hell_tower.inc").read_text(encoding="cp1251")
|
||||||
|
assert "FreeTowerMinigameAssets PROC" in ht_code
|
||||||
|
assert "call FreeTowerMinigameAssets" in ht_code
|
||||||
|
assert "hellTowerMenuArt3D.pixels" in ht_code
|
||||||
|
assert "towerCursorTargetY" in ht_code
|
||||||
|
assert "towerCalloutAnim" in ht_code
|
||||||
|
print(" [OK] hell_tower.inc verified.")
|
||||||
|
|
||||||
|
|
||||||
|
def test_executable():
|
||||||
|
print("[3] Verifying poker.exe build...")
|
||||||
|
exe = ROOT / "poker.exe"
|
||||||
|
assert exe.exists(), "poker.exe does not exist"
|
||||||
|
size = exe.stat().st_size
|
||||||
|
print(f" [OK] poker.exe verified ({size} bytes)")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_asset_packs()
|
||||||
|
test_code_integrations()
|
||||||
|
test_executable()
|
||||||
|
print("\nALL HELL TOWER CUSTOM ASSET AND MENU CHECKS PASSED SUCCESSFULLY!")
|
||||||
125
tools/tests/check_jackbox.py
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
"""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, 3 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, 3), f"Bad dimensions: {w}x{h}, {frames} frames"
|
||||||
|
assert len(wdata) == 20 + raw_len == 20 + 256 * 512 * 4 * 3
|
||||||
|
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) == 2, f"Expected 2 audio tracks, got {len(open_cmds)}"
|
||||||
|
for rel_path, media_type in open_cmds:
|
||||||
|
assert "assets\\music\\jackbox" in rel_path or "assets/music/jackbox" in rel_path, f"Track must be from jackbox directory: {rel_path}"
|
||||||
|
|
||||||
|
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 FP_1*306/10" in data_inc
|
||||||
|
assert "JACKBOX_Y equ FP_1*235/10" in data_inc
|
||||||
|
assert "MATERIAL_JACKBOX_SIDE equ 121" in data_inc
|
||||||
|
assert "MATERIAL_JACKBOX_BACK equ 163" in data_inc
|
||||||
|
|
||||||
|
# Check procs
|
||||||
|
procs = [
|
||||||
|
"LoadJackboxArt3D", "FreeJackboxArt3D", "ResolveJackboxSurface3D",
|
||||||
|
"DrawJackboxCabinet3D", "RenderJackboxCabinet3D", "JackboxCanWalk3D",
|
||||||
|
"PlayJukeboxTrack3D", "StopJukeboxTrack3D", "BeginJackbox3D",
|
||||||
|
"HandleJackbox3D", "RenderJackbox3D",
|
||||||
|
"CalculateJukeboxFalloff", "UpdateJukeboxAudio3D",
|
||||||
|
"HandleJukeboxKeys3D", "RenderJukeboxVolumeHUD"
|
||||||
|
]
|
||||||
|
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 HandleJukeboxKeys3D" in casino_inc
|
||||||
|
assert "call UpdateJukeboxAudio3D" in casino_inc
|
||||||
|
assert "call RenderJukeboxVolumeHUD" in casino_inc
|
||||||
|
assert "jboxUserVolume dd" in data_inc
|
||||||
|
assert "setaudio jboxmusic volume to" in data_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!")
|
||||||
115
tools/tests/render_hell_tower_preview.py
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
"""Render full simulation of Hell Tower menu at floors 1, 5, 10."""
|
||||||
|
import struct
|
||||||
|
from pathlib import Path
|
||||||
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
MENU_BIN = ROOT / "assets/animations/hall/helltower-menu.bin"
|
||||||
|
ART_DIR = Path(r"C:\Users\IKER\.gemini\antigravity-cli\brain\c2d69e9d-8822-4f06-923a-36a9194468d6")
|
||||||
|
|
||||||
|
FLOOR_Y = [205, 186, 167, 148, 129, 110, 91, 72, 53, 34]
|
||||||
|
|
||||||
|
BOSSES = [
|
||||||
|
"БЕС АЗАРТА", "ДЕМОН КОСТЕЙ", "ЧЕРНОКНИЖНИК", "КРУПЬЕ ПРЕИСПОДНЕЙ", "ДЕМОН БЛЕФА",
|
||||||
|
"ОБРЕЧЕННЫЙ ГРЕШНИК", "КОЛЕСО СУДЬБЫ", "КАРТОЧНЫЙ ЧЕРТ", "ПАЛАЧ КРЮКА", "ВЛАДЫКА БАШНИ"
|
||||||
|
]
|
||||||
|
|
||||||
|
GAMES = [
|
||||||
|
"Орел и решка", "Кости", "Черепа и шарик", "21 / Очко", "Покер",
|
||||||
|
"Русская рулетка", "Казино-рулетка", "Дурак", "Крюк: Skill Check", "Buckshot Roulette"
|
||||||
|
]
|
||||||
|
|
||||||
|
DESCS = [
|
||||||
|
"Угадай сторону монеты.",
|
||||||
|
"Перебрось демона в сумме костей.",
|
||||||
|
"Найди душу под черепом.",
|
||||||
|
"Собери 21 очко и победи крупье.",
|
||||||
|
"Вскрой карты и победи в покер.",
|
||||||
|
"1 патрон в барабане: выживи.",
|
||||||
|
"Сделай ставку на рулетке.",
|
||||||
|
"Оставь чёрта в дураках.",
|
||||||
|
"Попади в зону и сорвись с крюка.",
|
||||||
|
"Дуэль на дробовиках за жизнь!"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def render_menu_frame(stage: int, callout_anim: int = 100):
|
||||||
|
# 1. Load background from ANM1 pack
|
||||||
|
data = MENU_BIN.read_bytes()
|
||||||
|
magic, w, h, frames, b_len = struct.unpack("<4sIIII", data[:20])
|
||||||
|
raw_bgra = data[20:20 + b_len]
|
||||||
|
bg = Image.frombytes("RGBA", (w, h), raw_bgra, "raw", "BGRA")
|
||||||
|
|
||||||
|
draw = ImageDraw.Draw(bg)
|
||||||
|
|
||||||
|
floor_idx = stage - 1
|
||||||
|
target_y = FLOOR_Y[floor_idx]
|
||||||
|
|
||||||
|
# Calculate sliding card box
|
||||||
|
card_h = 208
|
||||||
|
card_y = 20
|
||||||
|
card_w = max(10, 206 * callout_anim // 100)
|
||||||
|
card_x = 214 - card_w
|
||||||
|
|
||||||
|
# Connector line
|
||||||
|
draw.line([(214, target_y), (224, target_y)], fill=(255, 68, 68), width=2)
|
||||||
|
# Cursor chevron/diamond
|
||||||
|
draw.rectangle([224, target_y - 3, 230, target_y + 3], fill=(255, 51, 0), outline=(255, 221, 68))
|
||||||
|
# Pulsing flame pip
|
||||||
|
draw.rectangle([234, target_y - 2, 238, target_y + 2], fill=(255, 170, 34))
|
||||||
|
|
||||||
|
# Outer and inner border of card
|
||||||
|
draw.rectangle([card_x, card_y, card_x + card_w, card_y + card_h], outline=(204, 34, 34), width=2)
|
||||||
|
draw.rectangle([card_x + 2, card_y + 2, card_x + card_w - 2, card_y + card_h - 2], outline=(85, 17, 17), width=1)
|
||||||
|
|
||||||
|
if card_w >= 120:
|
||||||
|
# Header banner
|
||||||
|
draw.rectangle([card_x + 6, 26, card_x + card_w - 6, 44], fill=(34, 4, 4), outline=(255, 68, 68))
|
||||||
|
draw.text((card_x + 12, 29), "КАЗИНО 3D - АДСКАЯ БАШНЯ", fill=(255, 221, 102))
|
||||||
|
|
||||||
|
# Floor indicator
|
||||||
|
draw.text((card_x + 10, 48), f"ЭТАЖ {stage} / 10", fill=(255, 102, 102))
|
||||||
|
|
||||||
|
# Boss & Game sub-card
|
||||||
|
draw.rectangle([card_x + 6, 64, card_x + card_w - 6, 126], fill=(30, 4, 4), outline=(136, 24, 24))
|
||||||
|
draw.text((card_x + 10, 68), f"ВРАГ: {BOSSES[floor_idx]}", fill=(255, 255, 255))
|
||||||
|
draw.text((card_x + 10, 82), f"ИГРА: {GAMES[floor_idx]}", fill=(255, 221, 102))
|
||||||
|
draw.text((card_x + 10, 98), DESCS[floor_idx], fill=(204, 204, 204))
|
||||||
|
|
||||||
|
# Rules & warning
|
||||||
|
draw.text((card_x + 10, 132), "10 битв подряд без права на ошибку", fill=(255, 170, 51))
|
||||||
|
draw.text((card_x + 10, 146), "Поражение - возврат в холл казино", fill=(238, 68, 68))
|
||||||
|
|
||||||
|
# Bankroll & Prize
|
||||||
|
draw.text((card_x + 10, 162), "БАНК: $10,000", fill=(102, 255, 102))
|
||||||
|
draw.text((card_x + 110, 162), "КУШ: $66,600", fill=(255, 221, 68))
|
||||||
|
|
||||||
|
# Buttons
|
||||||
|
draw.rectangle([card_x + 8, 184, card_x + 100, 206], fill=(61, 12, 12), outline=(255, 68, 68))
|
||||||
|
draw.text((card_x + 14, 191), "[ПРОБЕЛ] В БОЙ", fill=(255, 255, 255))
|
||||||
|
|
||||||
|
draw.rectangle([card_x + 106, 184, card_x + 198, 206], fill=(34, 6, 6), outline=(136, 34, 34))
|
||||||
|
draw.text((card_x + 114, 191), "[ESC] СБЕЖАТЬ", fill=(170, 170, 170))
|
||||||
|
|
||||||
|
# Scale 2x for high quality artifact preview (640x480)
|
||||||
|
bg_hd = bg.resize((640, 480), Image.Resampling.NEAREST)
|
||||||
|
return bg_hd
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ART_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
f1 = render_menu_frame(1, 100)
|
||||||
|
f1.save(ART_DIR / "helltower_rendered_floor1.png")
|
||||||
|
print("[OK] Saved helltower_rendered_floor1.png")
|
||||||
|
|
||||||
|
f5 = render_menu_frame(5, 100)
|
||||||
|
f5.save(ART_DIR / "helltower_rendered_floor5.png")
|
||||||
|
print("[OK] Saved helltower_rendered_floor5.png")
|
||||||
|
|
||||||
|
f10 = render_menu_frame(10, 100)
|
||||||
|
f10.save(ART_DIR / "helltower_rendered_floor10.png")
|
||||||
|
print("[OK] Saved helltower_rendered_floor10.png")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
45
tools/tests/test_hell_tower_runthrough.py
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
"""Automated smoke test for Hell Tower 10-floor run-through."""
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
POKER_ASM = ROOT / "poker.asm"
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("[1/4] Preparing test harness in poker.asm...")
|
||||||
|
code = POKER_ASM.read_text(encoding="cp1251")
|
||||||
|
target = "\nIFDEF CASINO_ACTION_TEST\n"
|
||||||
|
if "CASINO_ACTION_TEST equ 1\n" not in code:
|
||||||
|
code = code.replace(target, "\nCASINO_ACTION_TEST equ 1\nIFDEF CASINO_ACTION_TEST\n")
|
||||||
|
POKER_ASM.write_text(code, encoding="cp1251")
|
||||||
|
|
||||||
|
try:
|
||||||
|
print("[2/4] Building poker.exe with test harness...")
|
||||||
|
build_res = subprocess.run(["cmd.exe", "/c", "build.bat"], cwd=ROOT, capture_output=True, text=True)
|
||||||
|
if build_res.returncode != 0:
|
||||||
|
print("[FAIL] Build failed:", build_res.stdout, build_res.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
print("[3/4] Running Hell Tower 10-floor run-through...")
|
||||||
|
run_res = subprocess.run([str(ROOT / "poker.exe")], cwd=ROOT, capture_output=True, timeout=15)
|
||||||
|
print(f"Hell Tower test completed with exit code: {run_res.returncode}")
|
||||||
|
if run_res.returncode != 0:
|
||||||
|
print(f"[FAIL] Hell Tower run-through failed with code {run_res.returncode}")
|
||||||
|
return run_res.returncode
|
||||||
|
print("[SUCCESS] All 10 floors of Hell Tower completed without errors or crashes!")
|
||||||
|
finally:
|
||||||
|
print("[4/4] Restoring normal poker.asm and rebuilding...")
|
||||||
|
restored = POKER_ASM.read_text(encoding="cp1251")
|
||||||
|
if "CASINO_ACTION_TEST equ 1\n" in restored:
|
||||||
|
restored = restored.replace("CASINO_ACTION_TEST equ 1\n", "")
|
||||||
|
POKER_ASM.write_text(restored, encoding="cp1251")
|
||||||
|
subprocess.run(["cmd.exe", "/c", "build.bat"], cwd=ROOT, capture_output=True, text=True)
|
||||||
|
print("[OK] Production poker.exe rebuilt cleanly.")
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
367
tools/tests/test_jukebox_render_all_angles.py
Normal file
|
|
@ -0,0 +1,367 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Visual simulation of the solid rectangular 3D Jukebox cabinet beside billiards.
|
||||||
|
Loads assets/animations/hall/jackbox-v1.bin directly (3 frames: Front, Side, Back).
|
||||||
|
Renders pixel-perfect 640x480 views matching MASM32 DrawCityVertical3D and DrawCityHorizontal3D.
|
||||||
|
"""
|
||||||
|
import math
|
||||||
|
import struct
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
import numpy as np
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
DOCS_DIR = REPO_ROOT / 'docs' / 'previews_minigames'
|
||||||
|
DOCS_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
FP_1 = 65536
|
||||||
|
SCREEN_W = 640
|
||||||
|
SCREEN_H = 480
|
||||||
|
HALF_W = 320
|
||||||
|
HALF_H = 240
|
||||||
|
HALL_STANDING_EYE = FP_1 * 21 // 20
|
||||||
|
|
||||||
|
# 1. Load map
|
||||||
|
with open(REPO_ROOT / 'casino3d_data.inc', 'r', encoding='latin1') as f:
|
||||||
|
text = f.read()
|
||||||
|
|
||||||
|
lines = text.splitlines()
|
||||||
|
map_lines = []
|
||||||
|
capturing = False
|
||||||
|
for l in lines:
|
||||||
|
if 'casinoMap db' in l:
|
||||||
|
capturing = True
|
||||||
|
if capturing:
|
||||||
|
map_lines.append(l)
|
||||||
|
if len(map_lines) == 32:
|
||||||
|
break
|
||||||
|
|
||||||
|
casinoMap = []
|
||||||
|
for l in map_lines:
|
||||||
|
vals = [int(x.strip()) for x in re.findall(r'\d+', l)]
|
||||||
|
casinoMap.extend(vals)
|
||||||
|
|
||||||
|
# 2. Load HD textures
|
||||||
|
tex_raw = open(REPO_ROOT / 'assets/textures/casino3d-hd.bin', 'rb').read()
|
||||||
|
slot_size = 128 * 128 * 4
|
||||||
|
|
||||||
|
def get_tex_pixel(slot, tx, ty):
|
||||||
|
idx = slot * slot_size + (ty * 128 + tx) * 4
|
||||||
|
if idx + 4 > len(tex_raw):
|
||||||
|
return (50, 50, 50, 255)
|
||||||
|
b, g, r, a = tex_raw[idx:idx+4]
|
||||||
|
return (r, g, b, a)
|
||||||
|
|
||||||
|
# 3. Load Jackbox Art pack (ANM1)
|
||||||
|
jackbox_raw = open(REPO_ROOT / 'assets/animations/hall/jackbox-v1.bin', 'rb').read()
|
||||||
|
magic, jw, jh, jframes, jlen = struct.unpack('<4s4I', jackbox_raw[:20])
|
||||||
|
assert magic == b'ANM1' and (jw, jh, jframes) == (256, 512, 3)
|
||||||
|
jpixels = jackbox_raw[20:]
|
||||||
|
|
||||||
|
# 4. Load Terminals Art pack (ANM1) for authentic wooden side panels (Frame 3)
|
||||||
|
term_raw = open(REPO_ROOT / 'assets/animations/hall/terminals-v2.bin', 'rb').read()
|
||||||
|
tmagic, tw, th, tframes, tlen = struct.unpack('<4s4I', term_raw[:20])
|
||||||
|
tpixels = term_raw[20:]
|
||||||
|
|
||||||
|
def get_terminal_pixel(frame, tx, ty):
|
||||||
|
idx = (frame * tw * th + ty * tw + tx) * 4
|
||||||
|
b, g, r, a = tpixels[idx:idx+4]
|
||||||
|
return (r, g, b, a)
|
||||||
|
|
||||||
|
def get_jackbox_pixel(frame, tx, ty):
|
||||||
|
idx = frame * (jw * jh * 4) + (ty * jw + tx) * 4
|
||||||
|
b, g, r, a = jpixels[idx:idx+4]
|
||||||
|
return (r, g, b, a)
|
||||||
|
|
||||||
|
tableCos = [round(math.cos(i * 2 * math.pi / 512) * FP_1) for i in range(512)]
|
||||||
|
tableSin = [round(math.sin(i * 2 * math.pi / 512) * FP_1) for i in range(512)]
|
||||||
|
|
||||||
|
def render_scene(cam_x, cam_y, cam_angle, out_path):
|
||||||
|
playerX = int(cam_x * FP_1)
|
||||||
|
playerY = int(cam_y * FP_1)
|
||||||
|
playerAngle = cam_angle % 512
|
||||||
|
|
||||||
|
playerDirX = tableCos[playerAngle]
|
||||||
|
playerDirY = tableSin[playerAngle]
|
||||||
|
playerPlaneX = -66 * playerDirY // 100
|
||||||
|
playerPlaneY = 66 * playerDirX // 100
|
||||||
|
|
||||||
|
screen_pixels = [(20, 20, 25)] * (SCREEN_W * SCREEN_H)
|
||||||
|
zBuffer = [0x7FFFFFFF] * SCREEN_W
|
||||||
|
depthBuffer = [0x7FFFFFFF] * (SCREEN_W * SCREEN_H)
|
||||||
|
|
||||||
|
# Floor & Ceiling
|
||||||
|
for y in range(HALF_H, SCREEN_H):
|
||||||
|
pDist = (HALF_H * FP_1) // (y - HALF_H + 1)
|
||||||
|
shade = max(35, min(255, 255 - (pDist >> 13)))
|
||||||
|
cShade = max(20, shade - 25)
|
||||||
|
|
||||||
|
fStepX = (playerPlaneX * 2 * pDist // FP_1) // SCREEN_W
|
||||||
|
fStepY = (playerPlaneY * 2 * pDist // FP_1) // SCREEN_W
|
||||||
|
|
||||||
|
fX = playerX + ((playerDirX - playerPlaneX) * pDist // FP_1)
|
||||||
|
fY = playerY + ((playerDirY - playerPlaneY) * pDist // FP_1)
|
||||||
|
|
||||||
|
for x in range(SCREEN_W):
|
||||||
|
tx = (fX >> 9) & 127
|
||||||
|
ty = (fY >> 9) & 127
|
||||||
|
r, g, b, _ = get_tex_pixel(0, tx, ty)
|
||||||
|
screen_pixels[y * SCREEN_W + x] = ((r * shade) >> 8, (g * shade) >> 8, (b * shade) >> 8)
|
||||||
|
|
||||||
|
cx = (fX >> 10) & 127
|
||||||
|
cy = (fY >> 10) & 127
|
||||||
|
cr, cg, cb, _ = get_tex_pixel(5, cx, cy)
|
||||||
|
screen_pixels[(SCREEN_H - 1 - y) * SCREEN_W + x] = ((cr * cShade) >> 8, (cg * cShade) >> 8, (cb * cShade) >> 8)
|
||||||
|
|
||||||
|
fX += fStepX
|
||||||
|
fY += fStepY
|
||||||
|
|
||||||
|
cityRayX = [0] * SCREEN_W
|
||||||
|
cityRayY = [0] * SCREEN_W
|
||||||
|
for col in range(SCREEN_W):
|
||||||
|
camX = (2 * col - SCREEN_W) * FP_1 // SCREEN_W
|
||||||
|
cityRayX[col] = playerDirX + (playerPlaneX * camX // FP_1)
|
||||||
|
cityRayY[col] = playerDirY + (playerPlaneY * camX // FP_1)
|
||||||
|
|
||||||
|
# Walls
|
||||||
|
for col in range(SCREEN_W):
|
||||||
|
rayDirX = cityRayX[col]
|
||||||
|
rayDirY = cityRayY[col]
|
||||||
|
mapX = playerX >> 16
|
||||||
|
mapY = playerY >> 16
|
||||||
|
deltaDistX = (1 << 32) // abs(rayDirX) if rayDirX != 0 else 0x7FFFFFFF
|
||||||
|
deltaDistY = (1 << 32) // abs(rayDirY) if rayDirY != 0 else 0x7FFFFFFF
|
||||||
|
|
||||||
|
if rayDirX < 0:
|
||||||
|
stepX = -1
|
||||||
|
sideDistX = ((playerX - (mapX << 16)) * deltaDistX) >> 16
|
||||||
|
else:
|
||||||
|
stepX = 1
|
||||||
|
sideDistX = (((mapX + 1 << 16) - playerX) * deltaDistX) >> 16
|
||||||
|
|
||||||
|
if rayDirY < 0:
|
||||||
|
stepY = -1
|
||||||
|
sideDistY = ((playerY - (mapY << 16)) * deltaDistY) >> 16
|
||||||
|
else:
|
||||||
|
stepY = 1
|
||||||
|
sideDistY = (((mapY + 1 << 16) - playerY) * deltaDistY) >> 16
|
||||||
|
|
||||||
|
side = 0
|
||||||
|
hit = 0
|
||||||
|
steps = 0
|
||||||
|
while hit == 0 and steps < 64:
|
||||||
|
steps += 1
|
||||||
|
if sideDistX < sideDistY:
|
||||||
|
sideDistX += deltaDistX
|
||||||
|
mapX += stepX
|
||||||
|
side = 0
|
||||||
|
else:
|
||||||
|
sideDistY += deltaDistY
|
||||||
|
mapY += stepY
|
||||||
|
side = 1
|
||||||
|
if 0 <= mapX < 32 and 0 <= mapY < 32:
|
||||||
|
tile = casinoMap[mapY * 32 + mapX]
|
||||||
|
if tile != 0:
|
||||||
|
hit = tile
|
||||||
|
else:
|
||||||
|
hit = 1
|
||||||
|
|
||||||
|
perpWallDist = sideDistX - deltaDistX if side == 0 else sideDistY - deltaDistY
|
||||||
|
perpWallDist = max(2048, perpWallDist)
|
||||||
|
zBuffer[col] = perpWallDist
|
||||||
|
|
||||||
|
lineH = (SCREEN_H * FP_1) // perpWallDist
|
||||||
|
drawStart = max(0, HALF_H - lineH // 2)
|
||||||
|
drawEnd = min(SCREEN_H - 1, HALF_H + lineH // 2)
|
||||||
|
|
||||||
|
wallHit = playerY + ((perpWallDist * rayDirY) >> 16) if side == 0 else playerX + ((perpWallDist * rayDirX) >> 16)
|
||||||
|
texX = ((wallHit & 0xFFFF) * 128) >> 16
|
||||||
|
if (side == 0 and rayDirX > 0) or (side == 1 and rayDirY < 0):
|
||||||
|
texX = 127 - texX
|
||||||
|
|
||||||
|
shade = max(40, min(255, 255 - (perpWallDist >> 12)))
|
||||||
|
if side == 1:
|
||||||
|
shade = (shade * 180) >> 8
|
||||||
|
|
||||||
|
texId = hit if hit != 0 else 1
|
||||||
|
for y in range(drawStart, drawEnd):
|
||||||
|
texY = (((y * 2 - SCREEN_H + lineH) * 128) // (lineH * 2)) & 127
|
||||||
|
r, g, b, _ = get_tex_pixel(texId, texX, texY)
|
||||||
|
screen_pixels[y * SCREEN_W + col] = ((r * shade) >> 8, (g * shade) >> 8, (b * shade) >> 8)
|
||||||
|
depthBuffer[y * SCREEN_W + col] = perpWallDist
|
||||||
|
|
||||||
|
def draw_vert(axis, coord, loBound, hiBound, zLow, zHigh, frame_idx):
|
||||||
|
for col in range(SCREEN_W):
|
||||||
|
ray_dir = cityRayX[col] if axis == 0 else cityRayY[col]
|
||||||
|
if abs(ray_dir) < 256:
|
||||||
|
continue
|
||||||
|
orig = playerX if axis == 0 else playerY
|
||||||
|
num = (coord - orig)
|
||||||
|
depth = (num << 16) // ray_dir
|
||||||
|
if depth <= 4096 or depth >= zBuffer[col]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
along = playerY + ((depth * cityRayY[col]) >> 16) if axis == 0 else playerX + ((depth * cityRayX[col]) >> 16)
|
||||||
|
if along < loBound or along >= hiBound:
|
||||||
|
continue
|
||||||
|
|
||||||
|
srcX = (along - loBound) * jw // (hiBound - loBound)
|
||||||
|
if srcX < 0 or srcX >= jw:
|
||||||
|
continue
|
||||||
|
|
||||||
|
top = (HALL_STANDING_EYE - zHigh) * SCREEN_H // depth + HALF_H
|
||||||
|
bottom = (HALL_STANDING_EYE - zLow) * SCREEN_H // depth + HALF_H
|
||||||
|
span = bottom - top
|
||||||
|
if span <= 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
stepY = (jh << 16) // span
|
||||||
|
r_start = max(0, top)
|
||||||
|
r_end = min(SCREEN_H, bottom)
|
||||||
|
curY = (r_start - top) * stepY
|
||||||
|
|
||||||
|
shade = max(40, min(255, 255 - (depth >> 12)))
|
||||||
|
for row in range(r_start, r_end):
|
||||||
|
srcY = (curY >> 16)
|
||||||
|
curY += stepY
|
||||||
|
if srcY < 0 or srcY >= jh:
|
||||||
|
continue
|
||||||
|
r, g, b, a = get_jackbox_pixel(frame_idx, srcX, srcY)
|
||||||
|
if depth < depthBuffer[row * SCREEN_W + col]:
|
||||||
|
depthBuffer[row * SCREEN_W + col] = depth
|
||||||
|
screen_pixels[row * SCREEN_W + col] = ((r * shade) >> 8, (g * shade) >> 8, (b * shade) >> 8)
|
||||||
|
|
||||||
|
# 1. Pool table geometry: X in [26.0, 28.0], Y in [24.0, 26.0], Z in [0, 0.85m]
|
||||||
|
def draw_pool_side(axis, coord, lo, hi, z0, z1):
|
||||||
|
for col in range(SCREEN_W):
|
||||||
|
ray_dir = cityRayX[col] if axis == 0 else cityRayY[col]
|
||||||
|
if abs(ray_dir) < 256:
|
||||||
|
continue
|
||||||
|
orig = playerX if axis == 0 else playerY
|
||||||
|
num = (coord - orig)
|
||||||
|
depth = (num << 16) // ray_dir
|
||||||
|
if depth <= 4096 or depth >= zBuffer[col]:
|
||||||
|
continue
|
||||||
|
along = playerY + ((depth * cityRayY[col]) >> 16) if axis == 0 else playerX + ((depth * cityRayX[col]) >> 16)
|
||||||
|
if along < lo or along >= hi:
|
||||||
|
continue
|
||||||
|
top = (HALL_STANDING_EYE - z1) * SCREEN_H // depth + HALF_H
|
||||||
|
bottom = (HALL_STANDING_EYE - z0) * SCREEN_H // depth + HALF_H
|
||||||
|
r_start = max(0, top)
|
||||||
|
r_end = min(SCREEN_H, bottom)
|
||||||
|
shade = max(40, min(255, 255 - (depth >> 12)))
|
||||||
|
for row in range(r_start, r_end):
|
||||||
|
if depth < depthBuffer[row * SCREEN_W + col]:
|
||||||
|
depthBuffer[row * SCREEN_W + col] = depth
|
||||||
|
screen_pixels[row * SCREEN_W + col] = ((50 * shade) >> 8, (25 * shade) >> 8, (15 * shade) >> 8)
|
||||||
|
|
||||||
|
draw_pool_side(1, int(24.0*FP_1), int(26.0*FP_1), int(28.0*FP_1), 0, int(0.85*FP_1))
|
||||||
|
draw_pool_side(1, int(26.0*FP_1), int(26.0*FP_1), int(28.0*FP_1), 0, int(0.85*FP_1))
|
||||||
|
draw_pool_side(0, int(26.0*FP_1), int(24.0*FP_1), int(26.0*FP_1), 0, int(0.85*FP_1))
|
||||||
|
draw_pool_side(0, int(28.0*FP_1), int(24.0*FP_1), int(26.0*FP_1), 0, int(0.85*FP_1))
|
||||||
|
|
||||||
|
# 2. Wall Art / Painting 116 on East Wall: X = 30.98m, Y in [25.0m .. 26.5m], Z in [1.0m .. 2.0m]
|
||||||
|
def draw_painting(axis, coord, lo, hi, z0, z1, slot):
|
||||||
|
for col in range(SCREEN_W):
|
||||||
|
ray_dir = cityRayX[col] if axis == 0 else cityRayY[col]
|
||||||
|
if abs(ray_dir) < 256:
|
||||||
|
continue
|
||||||
|
orig = playerX if axis == 0 else playerY
|
||||||
|
num = (coord - orig)
|
||||||
|
depth = (num << 16) // ray_dir
|
||||||
|
if depth <= 4096 or depth >= zBuffer[col]:
|
||||||
|
continue
|
||||||
|
along = playerY + ((depth * cityRayY[col]) >> 16) if axis == 0 else playerX + ((depth * cityRayX[col]) >> 16)
|
||||||
|
if along < lo or along >= hi:
|
||||||
|
continue
|
||||||
|
top = (HALL_STANDING_EYE - z1) * SCREEN_H // depth + HALF_H
|
||||||
|
bottom = (HALL_STANDING_EYE - z0) * SCREEN_H // depth + HALF_H
|
||||||
|
span = bottom - top
|
||||||
|
if span <= 0:
|
||||||
|
continue
|
||||||
|
r_start = max(0, top)
|
||||||
|
r_end = min(SCREEN_H, bottom)
|
||||||
|
span_along = hi - lo
|
||||||
|
tx = int((along - lo) * 128 / span_along) % 128
|
||||||
|
shade = max(40, min(255, 255 - (depth >> 12)))
|
||||||
|
for row in range(r_start, r_end):
|
||||||
|
ty = int((row - top) * 128 / span) % 128
|
||||||
|
r, g, b, a = get_tex_pixel(slot, tx, ty)
|
||||||
|
if depth < depthBuffer[row * SCREEN_W + col]:
|
||||||
|
depthBuffer[row * SCREEN_W + col] = depth
|
||||||
|
screen_pixels[row * SCREEN_W + col] = ((r * shade) >> 8, (g * shade) >> 8, (b * shade) >> 8)
|
||||||
|
|
||||||
|
def draw_term_vert(axis, coord, lo, hi, zLow, zHigh, frame_idx):
|
||||||
|
for col in range(SCREEN_W):
|
||||||
|
ray_dir = cityRayX[col] if axis == 0 else cityRayY[col]
|
||||||
|
if abs(ray_dir) < 256:
|
||||||
|
continue
|
||||||
|
orig = playerX if axis == 0 else playerY
|
||||||
|
num = (coord - orig)
|
||||||
|
depth = (num << 16) // ray_dir
|
||||||
|
if depth <= 4096 or depth >= zBuffer[col]:
|
||||||
|
continue
|
||||||
|
along = playerY + ((depth * cityRayY[col]) >> 16) if axis == 0 else playerX + ((depth * cityRayX[col]) >> 16)
|
||||||
|
if along < lo or along >= hi:
|
||||||
|
continue
|
||||||
|
span_along = hi - lo
|
||||||
|
srcX = int((along - lo) * tw / span_along)
|
||||||
|
if srcX < 0 or srcX >= tw:
|
||||||
|
continue
|
||||||
|
top = (HALL_STANDING_EYE - zHigh) * SCREEN_H // depth + HALF_H
|
||||||
|
bottom = (HALL_STANDING_EYE - zLow) * SCREEN_H // depth + HALF_H
|
||||||
|
span = bottom - top
|
||||||
|
if span <= 0:
|
||||||
|
continue
|
||||||
|
stepY = (th << 16) // span
|
||||||
|
r_start = max(0, top)
|
||||||
|
r_end = min(SCREEN_H, bottom)
|
||||||
|
curY = (r_start - top) * stepY
|
||||||
|
shade = max(40, min(255, 255 - (depth >> 12)))
|
||||||
|
for row in range(r_start, r_end):
|
||||||
|
srcY = (curY >> 16)
|
||||||
|
curY += stepY
|
||||||
|
if srcY < 0 or srcY >= th:
|
||||||
|
continue
|
||||||
|
r, g, b, a = get_terminal_pixel(frame_idx, srcX, srcY)
|
||||||
|
if depth < depthBuffer[row * SCREEN_W + col]:
|
||||||
|
depthBuffer[row * SCREEN_W + col] = depth
|
||||||
|
screen_pixels[row * SCREEN_W + col] = ((r * shade) >> 8, (g * shade) >> 8, (b * shade) >> 8)
|
||||||
|
|
||||||
|
draw_painting(0, int(30.98 * FP_1), int(25.0 * FP_1), int(26.5 * FP_1), FP_1, 2 * FP_1, 116)
|
||||||
|
|
||||||
|
# 3. Solid Rectangular Jukebox Cabinet (at East wall, Y = 23.5m, clearing Painting 116!)
|
||||||
|
# Depth: X in [30.40m .. 30.95m] (back against East wall at 31.0m)
|
||||||
|
# Width: Y in [23.075m .. 23.925m] (centered at Y = 23.5m)
|
||||||
|
# Height: Z in [0 .. 1.85m]
|
||||||
|
x0 = int(30.40 * FP_1)
|
||||||
|
x1 = int(30.95 * FP_1)
|
||||||
|
y0 = int(23.075 * FP_1)
|
||||||
|
y1 = int(23.925 * FP_1)
|
||||||
|
zHigh = int(1.85 * FP_1)
|
||||||
|
|
||||||
|
# Back Face (against wall: X = x1) - Frame 2
|
||||||
|
draw_vert(0, x1, y0, y1, 0, zHigh, 2)
|
||||||
|
# North Side (Y = y0) - Authentic terminal wooden panel (Frame 3)
|
||||||
|
draw_term_vert(1, y0, x0, x1, 0, zHigh, 3)
|
||||||
|
# South Side (Y = y1) - Authentic terminal wooden panel (Frame 3)
|
||||||
|
draw_term_vert(1, y1, x0, x1, 0, zHigh, 3)
|
||||||
|
# Front Face (facing West towards billiards table: X = x0) - Frame 0
|
||||||
|
draw_vert(0, x0, y0, y1, 0, zHigh, 0)
|
||||||
|
|
||||||
|
img = Image.new('RGB', (SCREEN_W, SCREEN_H))
|
||||||
|
img.putdata(screen_pixels)
|
||||||
|
img.save(str(out_path))
|
||||||
|
print(f'Rendered: {out_path.name}')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# 1. Front View: Standing near billiards at (26.0, 25.0) looking East (angle=0) past table at Jukebox
|
||||||
|
render_scene(26.0, 24.5, 0, DOCS_DIR / 'jukebox_3d_front.png')
|
||||||
|
# 2. Angle View: Standing at (27.5, 22.5) looking South-East (angle=480) at pool table and Jukebox
|
||||||
|
render_scene(28.8, 25.0, 960, DOCS_DIR / 'jukebox_3d_angle.png')
|
||||||
|
# 3. Close-up View: Standing at (29.2, 25.0) looking East (angle=0) right at Jukebox
|
||||||
|
render_scene(29.2, 23.5, 0, DOCS_DIR / 'jukebox_3d_close.png')
|
||||||
|
# 4. Side View: Standing at (30.0, 27.5) looking North (angle=384) at South side of Jukebox
|
||||||
|
render_scene(30.6, 25.5, 768, DOCS_DIR / 'jukebox_3d_side.png')
|
||||||
|
print('All Jukebox 3D preview frames rendered to docs/previews_minigames/!')
|
||||||