diff --git a/assets/animations/buckshot/dealer-actions-v3-hell.bin b/assets/animations/buckshot/dealer-actions-v3-hell.bin new file mode 100644 index 0000000..882fb60 Binary files /dev/null and b/assets/animations/buckshot/dealer-actions-v3-hell.bin differ diff --git a/assets/animations/buckshot/dealer-v2-hell.bin b/assets/animations/buckshot/dealer-v2-hell.bin new file mode 100644 index 0000000..b1d17bc Binary files /dev/null and b/assets/animations/buckshot/dealer-v2-hell.bin differ diff --git a/assets/animations/buckshot/death-v3-hell.bin b/assets/animations/buckshot/death-v3-hell.bin new file mode 100644 index 0000000..05090ab Binary files /dev/null and b/assets/animations/buckshot/death-v3-hell.bin differ diff --git a/assets/animations/buckshot/room-v2-hell.bin b/assets/animations/buckshot/room-v2-hell.bin new file mode 100644 index 0000000..2580001 Binary files /dev/null and b/assets/animations/buckshot/room-v2-hell.bin differ diff --git a/assets/animations/coin/table-v1-hell.bin b/assets/animations/coin/table-v1-hell.bin new file mode 100644 index 0000000..ec88a65 Binary files /dev/null and b/assets/animations/coin/table-v1-hell.bin differ diff --git a/assets/animations/hall/helltower-menu.bin b/assets/animations/hall/helltower-menu.bin new file mode 100644 index 0000000..412e0da Binary files /dev/null and b/assets/animations/hall/helltower-menu.bin differ diff --git a/assets/animations/wheel/closeup-v1-hell.bin b/assets/animations/wheel/closeup-v1-hell.bin new file mode 100644 index 0000000..ea1d192 Binary files /dev/null and b/assets/animations/wheel/closeup-v1-hell.bin differ diff --git a/assets/animations/wheel/table-v1-hell.bin b/assets/animations/wheel/table-v1-hell.bin new file mode 100644 index 0000000..750e936 Binary files /dev/null and b/assets/animations/wheel/table-v1-hell.bin differ diff --git a/assets/hell_tower/01_coin/faces-v1.png b/assets/hell_tower/01_coin/faces-v1.png new file mode 100644 index 0000000..2fdbb47 Binary files /dev/null and b/assets/hell_tower/01_coin/faces-v1.png differ diff --git a/assets/hell_tower/01_coin/hands-v1.png b/assets/hell_tower/01_coin/hands-v1.png new file mode 100644 index 0000000..566c1df Binary files /dev/null and b/assets/hell_tower/01_coin/hands-v1.png differ diff --git a/assets/hell_tower/01_coin/hands-v2.png b/assets/hell_tower/01_coin/hands-v2.png new file mode 100644 index 0000000..01a8b50 Binary files /dev/null and b/assets/hell_tower/01_coin/hands-v2.png differ diff --git a/assets/hell_tower/01_coin/table-v1-hell.jpg b/assets/hell_tower/01_coin/table-v1-hell.jpg new file mode 100644 index 0000000..b768dbd Binary files /dev/null and b/assets/hell_tower/01_coin/table-v1-hell.jpg differ diff --git a/assets/hell_tower/02_dice/hands-v1.png b/assets/hell_tower/02_dice/hands-v1.png new file mode 100644 index 0000000..5cd6d10 Binary files /dev/null and b/assets/hell_tower/02_dice/hands-v1.png differ diff --git a/assets/hell_tower/02_dice/hands-v2.png b/assets/hell_tower/02_dice/hands-v2.png new file mode 100644 index 0000000..4b3febe Binary files /dev/null and b/assets/hell_tower/02_dice/hands-v2.png differ diff --git a/assets/hell_tower/04_05_08_card_tables/card-table-pixel-v3-hell.jpg b/assets/hell_tower/04_05_08_card_tables/card-table-pixel-v3-hell.jpg new file mode 100644 index 0000000..4a30ad6 Binary files /dev/null and b/assets/hell_tower/04_05_08_card_tables/card-table-pixel-v3-hell.jpg differ diff --git a/assets/hell_tower/06_russian_roulette/dealer-source-v2-hell.png b/assets/hell_tower/06_russian_roulette/dealer-source-v2-hell.png new file mode 100644 index 0000000..c6a63fa Binary files /dev/null and b/assets/hell_tower/06_russian_roulette/dealer-source-v2-hell.png differ diff --git a/assets/hell_tower/06_russian_roulette/gman-talk-v1.png b/assets/hell_tower/06_russian_roulette/gman-talk-v1.png new file mode 100644 index 0000000..8be8a99 Binary files /dev/null and b/assets/hell_tower/06_russian_roulette/gman-talk-v1.png differ diff --git a/assets/hell_tower/06_russian_roulette/hands-v5.png b/assets/hell_tower/06_russian_roulette/hands-v5.png new file mode 100644 index 0000000..11fe1e9 Binary files /dev/null and b/assets/hell_tower/06_russian_roulette/hands-v5.png differ diff --git a/assets/hell_tower/06_russian_roulette/revolver-actions-v1.png b/assets/hell_tower/06_russian_roulette/revolver-actions-v1.png new file mode 100644 index 0000000..04cb85a Binary files /dev/null and b/assets/hell_tower/06_russian_roulette/revolver-actions-v1.png differ diff --git a/assets/hell_tower/06_russian_roulette/room-v2-hell.jpg b/assets/hell_tower/06_russian_roulette/room-v2-hell.jpg new file mode 100644 index 0000000..db958a2 Binary files /dev/null and b/assets/hell_tower/06_russian_roulette/room-v2-hell.jpg differ diff --git a/assets/hell_tower/07_casino_roulette/closeup-v1-hell.jpg b/assets/hell_tower/07_casino_roulette/closeup-v1-hell.jpg new file mode 100644 index 0000000..1ebdf53 Binary files /dev/null and b/assets/hell_tower/07_casino_roulette/closeup-v1-hell.jpg differ diff --git a/assets/hell_tower/07_casino_roulette/table-v1-hell.jpg b/assets/hell_tower/07_casino_roulette/table-v1-hell.jpg new file mode 100644 index 0000000..8d1f911 Binary files /dev/null and b/assets/hell_tower/07_casino_roulette/table-v1-hell.jpg differ diff --git a/assets/hell_tower/10_buckshot/blood-v3.png b/assets/hell_tower/10_buckshot/blood-v3.png new file mode 100644 index 0000000..aa8b195 Binary files /dev/null and b/assets/hell_tower/10_buckshot/blood-v3.png differ diff --git a/assets/hell_tower/10_buckshot/dealer-actions-v3-hell.png b/assets/hell_tower/10_buckshot/dealer-actions-v3-hell.png new file mode 100644 index 0000000..e78a509 Binary files /dev/null and b/assets/hell_tower/10_buckshot/dealer-actions-v3-hell.png differ diff --git a/assets/hell_tower/10_buckshot/dealer-source-v2-hell.png b/assets/hell_tower/10_buckshot/dealer-source-v2-hell.png new file mode 100644 index 0000000..c6a63fa Binary files /dev/null and b/assets/hell_tower/10_buckshot/dealer-source-v2-hell.png differ diff --git a/assets/hell_tower/10_buckshot/death-v3-hell.png b/assets/hell_tower/10_buckshot/death-v3-hell.png new file mode 100644 index 0000000..864eac9 Binary files /dev/null and b/assets/hell_tower/10_buckshot/death-v3-hell.png differ diff --git a/assets/hell_tower/10_buckshot/items-v2.png b/assets/hell_tower/10_buckshot/items-v2.png new file mode 100644 index 0000000..ed25580 Binary files /dev/null and b/assets/hell_tower/10_buckshot/items-v2.png differ diff --git a/assets/hell_tower/10_buckshot/loading-v2.png b/assets/hell_tower/10_buckshot/loading-v2.png new file mode 100644 index 0000000..c668c4c Binary files /dev/null and b/assets/hell_tower/10_buckshot/loading-v2.png differ diff --git a/assets/hell_tower/10_buckshot/medical-dealer-v5.png b/assets/hell_tower/10_buckshot/medical-dealer-v5.png new file mode 100644 index 0000000..48ee5ed Binary files /dev/null and b/assets/hell_tower/10_buckshot/medical-dealer-v5.png differ diff --git a/assets/hell_tower/10_buckshot/medical-player-v5.png b/assets/hell_tower/10_buckshot/medical-player-v5.png new file mode 100644 index 0000000..10276ac Binary files /dev/null and b/assets/hell_tower/10_buckshot/medical-player-v5.png differ diff --git a/assets/hell_tower/10_buckshot/medical-v3.png b/assets/hell_tower/10_buckshot/medical-v3.png new file mode 100644 index 0000000..5bdad47 Binary files /dev/null and b/assets/hell_tower/10_buckshot/medical-v3.png differ diff --git a/assets/hell_tower/10_buckshot/props-v2.png b/assets/hell_tower/10_buckshot/props-v2.png new file mode 100644 index 0000000..6afabd0 Binary files /dev/null and b/assets/hell_tower/10_buckshot/props-v2.png differ diff --git a/assets/hell_tower/10_buckshot/room-v2-hell.jpg b/assets/hell_tower/10_buckshot/room-v2-hell.jpg new file mode 100644 index 0000000..db958a2 Binary files /dev/null and b/assets/hell_tower/10_buckshot/room-v2-hell.jpg differ diff --git a/assets/hell_tower/10_buckshot/self-flash-v4.png b/assets/hell_tower/10_buckshot/self-flash-v4.png new file mode 100644 index 0000000..da9d196 Binary files /dev/null and b/assets/hell_tower/10_buckshot/self-flash-v4.png differ diff --git a/assets/hell_tower/10_buckshot/self-v3.png b/assets/hell_tower/10_buckshot/self-v3.png new file mode 100644 index 0000000..15f3a38 Binary files /dev/null and b/assets/hell_tower/10_buckshot/self-v3.png differ diff --git a/assets/hell_tower/10_buckshot/shots-v2.png b/assets/hell_tower/10_buckshot/shots-v2.png new file mode 100644 index 0000000..952c1b4 Binary files /dev/null and b/assets/hell_tower/10_buckshot/shots-v2.png differ diff --git a/assets/hell_tower/README.md b/assets/hell_tower/README.md new file mode 100644 index 0000000..893efc6 --- /dev/null +++ b/assets/hell_tower/README.md @@ -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`).* diff --git a/assets/hell_tower/environment/HellTower_ menu.jpg b/assets/hell_tower/environment/HellTower_ menu.jpg new file mode 100644 index 0000000..a517600 Binary files /dev/null and b/assets/hell_tower/environment/HellTower_ menu.jpg differ diff --git a/assets/hell_tower/environment/tex_wall_hell.png b/assets/hell_tower/environment/tex_wall_hell.png new file mode 100644 index 0000000..0ffd8ee Binary files /dev/null and b/assets/hell_tower/environment/tex_wall_hell.png differ diff --git a/assets/textures/card-table-hell.bin b/assets/textures/card-table-hell.bin new file mode 100644 index 0000000..55be3c1 Binary files /dev/null and b/assets/textures/card-table-hell.bin differ diff --git a/build/graphics/poker.exe b/build/graphics/poker.exe index 98b455f..6a31bf6 100644 Binary files a/build/graphics/poker.exe and b/build/graphics/poker.exe differ diff --git a/poker.asm b/poker.asm index 09a7a09..4c5070b 100644 --- a/poker.asm +++ b/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 diff --git a/poker.exe b/poker.exe index 98b455f..6a31bf6 100644 Binary files a/poker.exe and b/poker.exe differ diff --git a/poker.obj b/poker.obj index 46e5782..169bba0 100644 Binary files a/poker.obj and b/poker.obj differ diff --git a/src_graphics/card_table_assets.inc b/src_graphics/card_table_assets.inc index d080275..97ab45f 100644 --- a/src_graphics/card_table_assets.inc +++ b/src_graphics/card_table_assets.inc @@ -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 diff --git a/src_graphics/hell_tower.inc b/src_graphics/hell_tower.inc index 543ce9b..5fd3d83 100644 --- a/src_graphics/hell_tower.inc +++ b/src_graphics/hell_tower.inc @@ -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 diff --git a/src_graphics/hell_tower_data.inc b/src_graphics/hell_tower_data.inc index 6be9ac0..eda5e96 100644 --- a/src_graphics/hell_tower_data.inc +++ b/src_graphics/hell_tower_data.inc @@ -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 diff --git a/src_graphics/scene_animation.inc b/src_graphics/scene_animation.inc index d70c3ff..336b281 100644 --- a/src_graphics/scene_animation.inc +++ b/src_graphics/scene_animation.inc @@ -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 diff --git a/tools/generators/copy_hell_tower_assets.ps1 b/tools/generators/copy_hell_tower_assets.ps1 new file mode 100644 index 0000000..b3bdca3 --- /dev/null +++ b/tools/generators/copy_hell_tower_assets.ps1 @@ -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!" diff --git a/tools/generators/pack_hell_tower_assets.py b/tools/generators/pack_hell_tower_assets.py new file mode 100644 index 0000000..cf1aa07 --- /dev/null +++ b/tools/generators/pack_hell_tower_assets.py @@ -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() diff --git a/tools/generators/setup_hell_tower_sprites.ps1 b/tools/generators/setup_hell_tower_sprites.ps1 new file mode 100644 index 0000000..d1e70c6 --- /dev/null +++ b/tools/generators/setup_hell_tower_sprites.ps1 @@ -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!" diff --git a/tools/tests/check_hell_tower.py b/tools/tests/check_hell_tower.py new file mode 100644 index 0000000..d6d5ed8 --- /dev/null +++ b/tools/tests/check_hell_tower.py @@ -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!") diff --git a/tools/tests/render_hell_tower_preview.py b/tools/tests/render_hell_tower_preview.py new file mode 100644 index 0000000..ca7b5e9 --- /dev/null +++ b/tools/tests/render_hell_tower_preview.py @@ -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() diff --git a/tools/tests/test_hell_tower_runthrough.py b/tools/tests/test_hell_tower_runthrough.py new file mode 100644 index 0000000..5caf65d --- /dev/null +++ b/tools/tests/test_hell_tower_runthrough.py @@ -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())