feat(hell_tower): integrate custom hell sprites, redesign tower menu, and fix entrance crash
- Add ResolveHellAssetPath to automatically load *-hell variants during tournamentMode with fallback to vanilla assets - Rebuild Hell Tower briefing menu using HellTower_menu background with smooth floor cursor and sliding callout info card - Fix buffer overrun in RenderHellTower3D by packing helltower-menu at native 640x480 resolution with bounds-checked blit - Temporarily disable tower background music per user request - Add asset packing pipeline, verification tools, and automated 10-floor run-through smoke test
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/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/textures/card-table-hell.bin
Normal file
120
poker.asm
|
|
@ -514,28 +514,9 @@ StopTowerMusic PROC
|
|||
ret
|
||||
StopTowerMusic ENDP
|
||||
|
||||
PlayTowerRandomMusic PROC uses eax
|
||||
PlayTowerRandomMusic PROC
|
||||
; Tower music disabled for now per user request
|
||||
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
|
||||
PlayTowerRandomMusic ENDP
|
||||
|
||||
|
|
@ -1923,6 +1904,103 @@ CheckDurakEnd ENDP
|
|||
|
||||
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
|
||||
IFDEF CASINO_ACTION_TEST
|
||||
call RunCasinoActionsTest3D
|
||||
|
|
|
|||
BIN
poker.exe
BIN
poker.obj
|
|
@ -16,9 +16,11 @@ FreeCardTableAssets3D ENDP
|
|||
|
||||
LoadCardTableAssets3D PROC uses ebx
|
||||
local count:DWORD
|
||||
local resolvedPath[260]:BYTE
|
||||
cmp cardTablePixels3D, 0
|
||||
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
|
||||
je card_load_ranks
|
||||
mov ebx, eax
|
||||
|
|
|
|||
|
|
@ -1,12 +1,30 @@
|
|||
; --- hell_tower.inc: Implementation of Hell Tower (10-floor Boss Rush) ---
|
||||
.code
|
||||
|
||||
FreeTowerMinigameAssets PROC
|
||||
call FreeBuckshotAssets3D
|
||||
call FreeRussianAssets3D
|
||||
call FreeCardTableAssets3D
|
||||
call FreeNativeCoinArt3D
|
||||
call FreeNativeWheelArt3D
|
||||
ret
|
||||
FreeTowerMinigameAssets ENDP
|
||||
|
||||
InitHellTower3D PROC
|
||||
call FreeTowerMinigameAssets
|
||||
mov tournamentMode, 1
|
||||
mov towerStage, 1
|
||||
mov towerFloorState, TOWER_STATE_BRIEFING
|
||||
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
|
||||
call ClearKeys3D
|
||||
ret
|
||||
|
|
@ -14,6 +32,7 @@ InitHellTower3D ENDP
|
|||
|
||||
TowerExitToHall PROC
|
||||
call StopTowerMusic
|
||||
call FreeTowerMinigameAssets
|
||||
mov tournamentMode, 0
|
||||
mov engineMode, MODE_WALK
|
||||
call ClearKeys3D
|
||||
|
|
@ -110,6 +129,14 @@ TowerReportRoundOutcome PROC uses eax outcome:DWORD
|
|||
jae report_champion
|
||||
|
||||
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
|
||||
mov engineMode, MODE_HELL_TOWER
|
||||
call ClearKeys3D
|
||||
|
|
@ -194,6 +221,14 @@ input_clear:
|
|||
advance_stage:
|
||||
inc towerStage
|
||||
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
|
||||
ret
|
||||
|
||||
|
|
@ -227,132 +262,321 @@ exit_to_hall:
|
|||
HandleHellTowerInput ENDP
|
||||
|
||||
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
|
||||
mov ecx, SCREEN_SIZE
|
||||
mov eax, 00100202h
|
||||
rep stosd
|
||||
|
||||
; 2. Outer and inner border
|
||||
invoke DrawBoxBorder, 8, 8, 304, 224, 00120202h, 00CC2222h, 2
|
||||
invoke DrawBoxBorder, 12, 12, 296, 216, 000C0202h, 00881111h, 1
|
||||
bg_done:
|
||||
; 2. Update towerAnimClock
|
||||
inc towerAnimClock
|
||||
|
||||
; 3. Header Banner (Centered: 192px wide, (320 - 192)/2 = 64)
|
||||
invoke DrawBoxBorder, 18, 16, 284, 20, 00220404h, 00FF4444h, 1
|
||||
invoke DrawString8x8, 64, 22, OFFSET towerHeader, 00FFDD66h
|
||||
; 3. Smooth cursor Y movement towards towerCursorTargetY (exponential damping)
|
||||
mov eax, towerCursorTargetY
|
||||
cmp eax, towerCursorY
|
||||
je cursor_update_done
|
||||
jg cursor_move_down
|
||||
|
||||
; 4. Floor Header & Segment Progress Bar
|
||||
invoke DrawString8x8, 26, 42, OFFSET towerFloorPrefix, 00FF6666h
|
||||
; Moving UP towards tower peak (target < current)
|
||||
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 DrawString8x8, 66, 42, OFFSET hudNumBuffer, 00FFFFFFh
|
||||
invoke DrawString8x8, 74, 42, OFFSET towerOfTen, 00AAAAAAh
|
||||
invoke DrawCharmsHUD, 130, 42
|
||||
mov eax, cardX
|
||||
add eax, 50
|
||||
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
|
||||
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
|
||||
; Specific state content inside the card
|
||||
mov eax, towerFloorState
|
||||
cmp eax, TOWER_STATE_BRIEFING
|
||||
je render_briefing
|
||||
je render_card_briefing
|
||||
cmp eax, TOWER_STATE_CLEAR
|
||||
je render_clear
|
||||
je render_card_clear
|
||||
cmp eax, TOWER_STATE_DEFEAT
|
||||
je render_defeat
|
||||
je render_card_defeat
|
||||
cmp eax, TOWER_STATE_CHAMPION
|
||||
je render_champion
|
||||
ret
|
||||
je render_card_champion
|
||||
jmp render_tower_done
|
||||
|
||||
render_briefing:
|
||||
; Boss & Game Card
|
||||
invoke DrawBoxBorder, 20, 66, 280, 58, 001E0404h, 00AA2222h, 1
|
||||
invoke DrawString8x8, 28, 72, OFFSET towerEnemyLabel, 00FFAA55h
|
||||
render_card_briefing:
|
||||
; Boss & Game sub-card
|
||||
mov eax, cardX
|
||||
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
|
||||
dec eax
|
||||
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
|
||||
dec eax
|
||||
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
|
||||
dec eax
|
||||
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)
|
||||
invoke DrawString8x8, 24, 132, OFFSET towerRuleText, 00FFAA33h
|
||||
invoke DrawString8x8, 24, 146, OFFSET towerLossWarning, 00EE4444h
|
||||
; Rules & warning
|
||||
mov eax, cardX
|
||||
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
|
||||
invoke DrawString8x8, 24, 164, OFFSET textBank3D, 00AAAAAAh
|
||||
; Bankroll & Prize
|
||||
mov eax, cardX
|
||||
add eax, 10
|
||||
invoke DrawString8x8, eax, 164, OFFSET textBank3D, 00AAAAAAh
|
||||
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)
|
||||
invoke DrawBoxBorder, 22, 184, 132, 24, 003D0C0Ch, 00FF4444h, 1
|
||||
invoke DrawString8x8, 32, 192, OFFSET towerStartBtn, 00FFFFFFh
|
||||
mov eax, cardX
|
||||
add eax, 110
|
||||
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
|
||||
invoke DrawString8x8, 178, 192, OFFSET towerEscBtn, 00AAAAAAh
|
||||
ret
|
||||
; Action Buttons (Start & Escape)
|
||||
mov eax, cardX
|
||||
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:
|
||||
invoke DrawBoxBorder, 20, 76, 280, 74, 00082208h, 0033CC33h, 2
|
||||
invoke DrawString8x8, 48, 90, OFFSET towerClearTitle, 0066FF66h
|
||||
invoke DrawString8x8, 24, 112, OFFSET towerClearDesc, 00FFFFFFh
|
||||
mov eax, cardX
|
||||
add eax, 106
|
||||
invoke DrawBoxBorder, eax, 186, 92, 22, 00220606h, 00882222h, 1
|
||||
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
|
||||
invoke DrawString8x8, 100, 183, OFFSET towerNextBtn, 00FFFFFFh
|
||||
ret
|
||||
render_card_clear:
|
||||
mov eax, cardX
|
||||
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:
|
||||
invoke DrawBoxBorder, 20, 76, 280, 74, 002A0404h, 00FF2222h, 2
|
||||
invoke DrawString8x8, 60, 90, OFFSET towerDefeatTitle, 00FF4444h
|
||||
invoke DrawString8x8, 36, 112, OFFSET towerDefeatDesc, 00FFFFFFh
|
||||
mov eax, cardX
|
||||
add eax, 18
|
||||
mov ecx, cardW
|
||||
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
|
||||
invoke DrawString8x8, 48, 183, OFFSET towerReturnBtn, 00FFFFFFh
|
||||
ret
|
||||
render_card_defeat:
|
||||
mov eax, cardX
|
||||
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:
|
||||
invoke DrawBoxBorder, 18, 70, 284, 86, 00332200h, 00FFD700h, 2
|
||||
invoke DrawString8x8, 40, 82, OFFSET towerChampTitle, 00FFE066h
|
||||
invoke DrawString8x8, 56, 100, OFFSET towerChampSubtitle, 00FFFFFFh
|
||||
invoke DrawString8x8, 36, 122, OFFSET towerPrizeText, 0066FF66h
|
||||
mov eax, cardX
|
||||
add eax, 16
|
||||
mov ecx, cardW
|
||||
sub ecx, 32
|
||||
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
|
||||
invoke DrawString8x8, 48, 183, OFFSET towerReturnBtn, 00FFFFFFh
|
||||
render_card_champion:
|
||||
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
|
||||
RenderHellTower3D ENDP
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ InitHellTower3D PROTO
|
|||
TowerExitToHall PROTO
|
||||
TowerReportRoundOutcome PROTO :DWORD
|
||||
RenderTowerInGameBadge PROTO
|
||||
ResolveHellAssetPath PROTO :DWORD, :DWORD
|
||||
FreeTowerMinigameAssets PROTO
|
||||
|
||||
.data
|
||||
towerStage dd 1
|
||||
|
|
@ -19,6 +21,16 @@ towerCurrentGame dd 0
|
|||
towerPrizeAmount dd 66600
|
||||
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
|
||||
|
||||
towerBoss1 db 193,197,209,32,192,199,192,208,210,192,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
|
||||
local io:DWORD,heap:DWORD,allocation:DWORD
|
||||
local resolvedPath[260]:BYTE
|
||||
mov esi, pack
|
||||
cmp [esi].AnimPack3D.pixels, 0
|
||||
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
|
||||
je anim_load_failure
|
||||
mov ebx, eax
|
||||
|
|
|
|||
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()
|
||||
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!")
|
||||
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())
|
||||