diff --git a/build/graphics/poker.exe b/build/graphics/poker.exe index 8cf5231..98b455f 100644 Binary files a/build/graphics/poker.exe and b/build/graphics/poker.exe differ diff --git a/casino3d.inc b/casino3d.inc index 8d3fe0c..f8b768f 100644 --- a/casino3d.inc +++ b/casino3d.inc @@ -5346,6 +5346,7 @@ check_walk_mode: call UpdateCamera3D call CheckInteractions call HandlePlayerInput + call HandleJukeboxKeys3D call UpdateInteriorTravel3D call UpdateInteriorEye3D call UpdateCamera3D @@ -5433,6 +5434,7 @@ check_darts_done: cmp engineMode, MODE_STAIRS jne @F call HandleStairs3D + call HandleJukeboxKeys3D call RenderStairs3D jmp present_frame @@: @@ -5594,7 +5596,9 @@ pause_low_spec_saved: call SaveProgress3D jmp engine_shutdown present_frame: + call UpdateJukeboxAudio3D call RenderTowerInGameBadge + call RenderJukeboxVolumeHUD mov eax, engineMode cmp eax, lastPersistMode3D je @F diff --git a/poker.exe b/poker.exe index 8cf5231..98b455f 100644 Binary files a/poker.exe and b/poker.exe differ diff --git a/src_graphics/jackbox.inc b/src_graphics/jackbox.inc index 9e0fcca..e763334 100644 --- a/src_graphics/jackbox.inc +++ b/src_graphics/jackbox.inc @@ -121,9 +121,12 @@ PlayJukeboxTrack3D PROC uses ebx trackIndex:DWORD invoke mciSendStringA, OFFSET jboxPlayCmd, 0, 0, 0 mov jboxCurrentTrack, ebx mov jboxIsPlaying, 1 + mov jboxEffectiveVolume, -1 + call UpdateJukeboxAudio3D ret jbox_play_stop_selected: mov jboxCurrentTrack, -1 + call UpdateJukeboxAudio3D ret PlayJukeboxTrack3D ENDP @@ -132,6 +135,7 @@ StopJukeboxTrack3D PROC invoke mciSendStringA, OFFSET jboxCloseCmd, 0, 0, 0 mov jboxIsPlaying, 0 mov jboxCurrentTrack, -1 + call UpdateJukeboxAudio3D ret StopJukeboxTrack3D ENDP @@ -410,4 +414,472 @@ jbox_row_next: invoke DrawString8x8, 56, 170, OFFSET jboxFooterHelp, 0070AA90h ret -RenderJackbox3D ENDP +RenderJackbox3D ENDP + +; ============================================================================== +; 3D SOUND PROPAGATION & MULTI-FLOOR ATTENUATION +; ============================================================================== + +CalculateJukeboxFalloff PROC uses ebx esi edi + local dxDist:DWORD, dyDist:DWORD, distSq:DWORD, distVal:DWORD + local factor:DWORD + + ; If music is not playing, factor = 0 + cmp jboxIsPlaying, 1 + jne falloff_silent + cmp jboxCurrentTrack, -1 + je falloff_silent + cmp jboxCurrentTrack, 2 + jae falloff_silent + + ; Check if on stairs (MODE_STAIRS) + cmp engineMode, MODE_STAIRS + je falloff_stairs + + ; Check floor level + mov eax, hallLevel3D + cmp eax, 0 + je falloff_ground + cmp eax, 1 + je falloff_bunker + cmp eax, 2 + je falloff_upper + cmp eax, 3 + je falloff_fortune + jmp falloff_silent + +falloff_ground: + ; Level 0 (Ground Hall) - standard quadratic 2D distance falloff from Jackbox + ; Jackbox: JACKBOX_X = 30.6m, JACKBOX_Y = 23.5m + mov eax, playerX + sub eax, JACKBOX_X + test eax, eax + jns @F + neg eax +@@: + shr eax, 16 ; integer meters + mov dxDist, eax + + mov eax, playerY + sub eax, JACKBOX_Y + test eax, eax + jns @F + neg eax +@@: + shr eax, 16 ; integer meters + mov dyDist, eax + + ; distSq = dx*dx + dy*dy + mov eax, dxDist + imul eax, eax + mov ebx, dyDist + imul ebx, ebx + add eax, ebx + mov distSq, eax + + ; distVal = sqrt(distSq) + fild distSq + fsqrt + fistp distVal + + ; Acoustic curve: + ; dist <= 2m -> factor = 1000 + ; dist >= 35m -> factor = 0 + ; in between: rem = 35 - dist; factor = 1000 * rem * rem / 1089 + mov eax, distVal + cmp eax, 2 + jbe falloff_full + cmp eax, 35 + jae falloff_silent + + mov ecx, 35 + sub ecx, eax ; ecx = 35 - distVal (in 1..32) + imul eax, ecx, 1000 + imul eax, ecx ; eax = 1000 * rem * rem + xor edx, edx + mov ebx, 1089 ; 33 * 33 + div ebx + mov factor, eax + jmp falloff_done + +falloff_full: + mov factor, 1000 + jmp falloff_done + +falloff_upper: + ; Level 2 (Upper Lounge) - sound travels up the open stairs (stairwell at X=16, Y=21 on upper map) + ; Base attenuation through floor + distance from stairwell opening + mov eax, playerX + sub eax, 16*FP_1 + test eax, eax + jns @F + neg eax +@@: + shr eax, 16 + mov dxDist, eax + + mov eax, playerY + sub eax, 21*FP_1 + test eax, eax + jns @F + neg eax +@@: + shr eax, 16 + mov dyDist, eax + + mov eax, dxDist + imul eax, eax + mov ebx, dyDist + imul ebx, ebx + add eax, ebx + mov distSq, eax + + fild distSq + fsqrt + fistp distVal + + ; At stair opening: factor = 420 (muffled from below) + ; Drops by 18 per meter, min 90 + mov eax, distVal + imul eax, 18 + mov ecx, 420 + sub ecx, eax + cmp ecx, 90 + jge @F + mov ecx, 90 +@@: + mov factor, ecx + jmp falloff_done + +falloff_bunker: + ; Level 1 (Bunker) - underground, heavily muffled concrete absorption (~160) + mov factor, 160 + jmp falloff_done + +falloff_fortune: + ; Level 3 (Fortune Room) - secret room on opposite side of casino (~90) + mov factor, 90 + jmp falloff_done + +falloff_stairs: + ; On stairs between floors + mov eax, stairRoute3D + cmp eax, 2 + je falloff_stairs_upper + cmp eax, 1 + je falloff_stairs_bunker + mov factor, 150 + jmp falloff_done + +falloff_stairs_upper: + ; Route 2 (Upper stairs) + ; playerX goes from ~3 (bottom, beside jukebox) to ~16 (top, upper floor) + ; At bottom: 850. At top: 420. + mov eax, playerX + shr eax, 16 ; integer X (3..16) + cmp eax, 3 + jge @F + mov eax, 3 +@@: + cmp eax, 16 + jle @F + mov eax, 16 +@@: + ; Interpolate: factor = 850 - (eax - 3) * (850 - 420) / 13 + sub eax, 3 + imul eax, 430 + xor edx, edx + mov ebx, 13 + div ebx + mov ecx, 850 + sub ecx, eax + mov factor, ecx + jmp falloff_done + +falloff_stairs_bunker: + ; Route 1 (Basement stairs) + ; Smooth blend between 350 (top) and 160 (bottom) + mov factor, 250 + jmp falloff_done + +falloff_silent: + mov factor, 0 + +falloff_done: + mov eax, factor + ret +CalculateJukeboxFalloff ENDP + +UpdateJukeboxAudio3D PROC uses ebx esi edi + local falloff:DWORD, targetVol:DWORD + + ; Check if music is playing + cmp jboxIsPlaying, 1 + jne jbox_audio_mute + + ; If user volume is 0 -> mute + mov eax, jboxUserVolume + test eax, eax + jz jbox_audio_mute + + ; Calculate 3D attenuation factor (0..1000) + call CalculateJukeboxFalloff + mov falloff, eax + test eax, eax + jz jbox_audio_mute + + ; targetVol = (jboxUserVolume * 100) * falloff / 1000 + ; jboxUserVolume is 0..10 (0..1000 base) + mov eax, jboxUserVolume + imul eax, 100 + imul eax, falloff + xor edx, edx + mov ebx, 1000 + div ebx + mov targetVol, eax + jmp jbox_apply_vol + +jbox_audio_mute: + mov targetVol, 0 + +jbox_apply_vol: + ; If targetVol matches last applied volume, skip redundant MCI call + mov eax, targetVol + cmp eax, jboxEffectiveVolume + je jbox_audio_done + + mov jboxEffectiveVolume, eax + + ; Format: "setaudio jboxmusic volume to " + ; Copy prefix to jboxVolCmdBuf + lea esi, jboxVolCmdPrefix + lea edi, jboxVolCmdBuf +jbox_vol_copy: + lodsb + stosb + test al, al + jnz jbox_vol_copy + dec edi ; point at null terminator + + ; Append decimal number targetVol + invoke FormatNumber, targetVol, edi + + ; Send to MCI + invoke mciSendStringA, OFFSET jboxVolCmdBuf, 0, 0, 0 + +jbox_audio_done: + ret +UpdateJukeboxAudio3D ENDP + +; ============================================================================== +; VOLUME CONTROL KEYS & POP-UP SLIDING HUD BAR +; ============================================================================== + +HandleJukeboxKeys3D PROC + ; '+' key: VK_OEM_PLUS (0BBh) or VK_ADD (6Bh - numpad +) + invoke Pressed3D, 0BBh + test eax, eax + jnz jbox_do_plus + invoke Pressed3D, 6Bh + test eax, eax + jz jbox_check_minus +jbox_do_plus: + cmp jboxUserVolume, 10 + jae @F + inc jboxUserVolume +@@: + mov jboxHudTimer, 90 ; Show HUD for ~1.5s + call UpdateJukeboxAudio3D + ret + +jbox_check_minus: + ; '-' key: VK_OEM_MINUS (0BDh) or VK_SUBTRACT (6Dh - numpad -) + invoke Pressed3D, 0BDh + test eax, eax + jnz jbox_do_minus + invoke Pressed3D, 6Dh + test eax, eax + jz jbox_keys_done +jbox_do_minus: + cmp jboxUserVolume, 0 + jbe @F + dec jboxUserVolume +@@: + mov jboxHudTimer, 90 ; Show HUD for ~1.5s + call UpdateJukeboxAudio3D + +jbox_keys_done: + ret +HandleJukeboxKeys3D ENDP + +RenderJukeboxVolumeHUD PROC uses ebx esi edi + local winX:DWORD, winY:DWORD, barX:DWORD, barY:DWORD, barW:DWORD, barH:DWORD + local numBuf[16]:BYTE + + ; 1. Update Timer and Animation Position + cmp jboxHudTimer, 0 + je hud_sliding_down + + ; Active: decrement timer + dec jboxHudTimer + ; Slide UP towards target Y = 206 + cmp jboxHudY, 206 + jbe hud_at_target + sub jboxHudY, 4 + cmp jboxHudY, 206 + jae hud_at_target + mov jboxHudY, 206 +hud_at_target: + jmp hud_draw + +hud_sliding_down: + ; Timer expired: slide DOWN towards 240 (offscreen) + cmp jboxHudY, 240 + jae hud_hidden + add jboxHudY, 3 + cmp jboxHudY, 240 + jbe hud_draw + mov jboxHudY, 240 +hud_hidden: + ret + +hud_draw: + mov eax, jboxHudY + mov winY, eax + mov winX, 72 ; Centered in 320: width 176 + + ; 2. Background and Frame + ; Outer dark shadow (178 x 30) + mov eax, winX + dec eax + mov ecx, winY + dec ecx + invoke DrawBox, eax, ecx, 178, 30, 0008100Ch + + ; Outer bezel (176 x 28) + invoke DrawBox, winX, winY, 176, 28, 001C2E24h + + ; Inner panel body (174 x 26) + mov eax, winX + inc eax + mov ecx, winY + inc ecx + invoke DrawBox, eax, ecx, 174, 26, 00101A14h + + ; Brass top trim accent (174 x 1) + mov eax, winX + inc eax + mov ecx, winY + inc ecx + invoke DrawBox, eax, ecx, 174, 1, 008A6B34h + + ; 3. Title text: "MUZ. AVTOMAT: " + percentage + mov eax, winX + add eax, 10 + mov ecx, winY + add ecx, 4 + invoke DrawString8x8, eax, ecx, OFFSET jboxHudTitle, 0050FF90h + + ; Draw percentage or "VYKL" + mov eax, winX + add eax, 126 + mov ecx, winY + add ecx, 4 + cmp jboxUserVolume, 0 + jne hud_draw_pct_num + invoke DrawString8x8, eax, ecx, OFFSET jboxHudMute, 008CB8A5h + jmp hud_draw_trough + +hud_draw_pct_num: + push eax + push ecx + mov edx, jboxUserVolume + imul edx, 10 + lea edi, numBuf + invoke FormatNumber, edx, edi +hud_find_term: + cmp byte ptr [edi], 0 + je hud_append_pct + inc edi + jmp hud_find_term +hud_append_pct: + mov byte ptr [edi], '%' + mov byte ptr [edi+1], 0 + pop ecx + pop eax + lea edx, numBuf + invoke DrawString8x8, eax, ecx, edx, 00F0E6C3h + +hud_draw_trough: + ; Progress trough at barX = winX + 24, barY = winY + 15, barW = 128, barH = 7 + mov eax, winX + add eax, 24 + mov barX, eax + mov ecx, winY + add ecx, 15 + mov barY, ecx + mov barW, 128 + mov barH, 7 + + ; Trough border + mov eax, barX + dec eax + mov ecx, barY + dec ecx + invoke DrawBox, eax, ecx, 130, 9, 00223828h + invoke DrawBox, barX, barY, 128, 7, 000C1610h + + ; Draw 10 blocks: i = 0..9 + xor esi, esi +hud_blocks_loop: + mov eax, barX + add eax, 4 + mov edx, esi + imul edx, 12 + add eax, edx ; blockX = barX + 4 + i*12 + + mov ecx, barY + inc ecx ; blockY = barY + 1 + + cmp esi, jboxUserVolume + jae hud_block_empty + + ; Filled block color gradient + cmp esi, 7 + jb hud_col_green + cmp esi, 9 + jb hud_col_gold + mov edx, 00465AFFh ; Red/orange accent for 100% + jmp hud_draw_block_rect +hud_col_green: + mov edx, 0050FF90h ; Phosphor green + jmp hud_draw_block_rect +hud_col_gold: + mov edx, 00FFD700h ; Golden amber + jmp hud_draw_block_rect + +hud_block_empty: + mov edx, 0018261Eh ; Dim background + +hud_draw_block_rect: + invoke DrawBox, eax, ecx, 10, 5, edx + + inc esi + cmp esi, 10 + jb hud_blocks_loop + + ; Draw '-' and '+' labels + mov eax, winX + add eax, 8 + mov ecx, winY + add ecx, 15 + invoke DrawString8x8, eax, ecx, OFFSET jboxHudMinus, 008CB8A5h + + mov eax, winX + add eax, 158 + mov ecx, winY + add ecx, 15 + invoke DrawString8x8, eax, ecx, OFFSET jboxHudPlus, 008CB8A5h + + ret +RenderJukeboxVolumeHUD ENDP diff --git a/src_graphics/jackbox_data.inc b/src_graphics/jackbox_data.inc index 410bfdb..d010247 100644 --- a/src_graphics/jackbox_data.inc +++ b/src_graphics/jackbox_data.inc @@ -23,6 +23,10 @@ StopJukeboxTrack3D PROTO BeginJackbox3D PROTO HandleJackbox3D PROTO RenderJackbox3D PROTO +CalculateJukeboxFalloff PROTO +UpdateJukeboxAudio3D PROTO +HandleJukeboxKeys3D PROTO +RenderJukeboxVolumeHUD PROTO .data jackboxWorldArt3D AnimPack3D <> @@ -37,9 +41,25 @@ jboxCurrentTrack dd -1 jboxSelectedTrack dd 0 jboxIsPlaying dd 0 +; Volume control & 3D sound state +jboxUserVolume dd 8 +jboxEffectiveVolume dd -1 +jboxHudTimer dd 0 +jboxHudY dd 240 + jboxCloseCmd db "close jboxmusic",0 jboxStopCmd db "stop jboxmusic",0 jboxPlayCmd db "play jboxmusic repeat",0 +jboxVolCmdPrefix db "setaudio jboxmusic volume to ",0 +jboxVolCmdBuf db 64 dup(0) + +; Popup HUD labels in CP1251: +; "MUZ. AVTOMAT: " +jboxHudTitle db 204,211,199,46,32,192,194,210,206,204,192,210,58,32,0 +; "VYKL" +jboxHudMute db 194,251,234,235,0 +jboxHudMinus db "-",0 +jboxHudPlus db "+",0 jboxOpen0 db 'open "assets\music\jackbox\BYBOLDEN. - Art Deco.mp3" type mpegvideo alias jboxmusic',0 jboxOpen1 db 'open "assets\music\jackbox\BYBOLDEN. - Soft Hours.mp3" type mpegvideo alias jboxmusic',0 diff --git a/tools/tests/check_jackbox.py b/tools/tests/check_jackbox.py index 9334f22..6543d83 100644 --- a/tools/tests/check_jackbox.py +++ b/tools/tests/check_jackbox.py @@ -75,7 +75,9 @@ def test_code_structure(): "LoadJackboxArt3D", "FreeJackboxArt3D", "ResolveJackboxSurface3D", "DrawJackboxCabinet3D", "RenderJackboxCabinet3D", "JackboxCanWalk3D", "PlayJukeboxTrack3D", "StopJukeboxTrack3D", "BeginJackbox3D", - "HandleJackbox3D", "RenderJackbox3D" + "HandleJackbox3D", "RenderJackbox3D", + "CalculateJukeboxFalloff", "UpdateJukeboxAudio3D", + "HandleJukeboxKeys3D", "RenderJukeboxVolumeHUD" ] for p in procs: assert f"{p} PROC" in code_inc, f"Missing proc: {p}" @@ -86,6 +88,11 @@ def test_code_structure(): assert "MODE_JACKBOX" in casino_inc assert "HandleJackbox3D" in casino_inc assert "RenderJackbox3D" in casino_inc + assert "call HandleJukeboxKeys3D" in casino_inc + assert "call UpdateJukeboxAudio3D" in casino_inc + assert "call RenderJukeboxVolumeHUD" in casino_inc + assert "jboxUserVolume dd" in data_inc + assert "setaudio jboxmusic volume to" in data_inc assert "call LoadJackboxArt3D" in world_inc assert "call FreeJackboxArt3D" in world_inc