Implement 3D positional audio across floors and popup volume HUD for Jukebox

This commit is contained in:
Игорь Федосеев 2026-10-25 18:27:51 +03:00
parent d4436674b4
commit 75d594ec79
6 changed files with 505 additions and 2 deletions

Binary file not shown.

View file

@ -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

BIN
poker.exe

Binary file not shown.

View file

@ -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
@ -411,3 +415,471 @@ jbox_row_next:
ret
RenderJackbox3D ENDP
; ==============================================================================
; 3D SOUND PROPAGATION & MULTI-FLOOR ATTENUATION
; ==============================================================================
CalculateJukeboxFalloff PROC uses ebx esi edi
local dxDist:DWORD, dyDist:DWORD, distSq:DWORD, distVal:DWORD
local factor:DWORD
; If music is not playing, factor = 0
cmp jboxIsPlaying, 1
jne falloff_silent
cmp jboxCurrentTrack, -1
je falloff_silent
cmp jboxCurrentTrack, 2
jae falloff_silent
; Check if on stairs (MODE_STAIRS)
cmp engineMode, MODE_STAIRS
je falloff_stairs
; Check floor level
mov eax, hallLevel3D
cmp eax, 0
je falloff_ground
cmp eax, 1
je falloff_bunker
cmp eax, 2
je falloff_upper
cmp eax, 3
je falloff_fortune
jmp falloff_silent
falloff_ground:
; Level 0 (Ground Hall) - standard quadratic 2D distance falloff from Jackbox
; Jackbox: JACKBOX_X = 30.6m, JACKBOX_Y = 23.5m
mov eax, playerX
sub eax, JACKBOX_X
test eax, eax
jns @F
neg eax
@@:
shr eax, 16 ; integer meters
mov dxDist, eax
mov eax, playerY
sub eax, JACKBOX_Y
test eax, eax
jns @F
neg eax
@@:
shr eax, 16 ; integer meters
mov dyDist, eax
; distSq = dx*dx + dy*dy
mov eax, dxDist
imul eax, eax
mov ebx, dyDist
imul ebx, ebx
add eax, ebx
mov distSq, eax
; distVal = sqrt(distSq)
fild distSq
fsqrt
fistp distVal
; Acoustic curve:
; dist <= 2m -> factor = 1000
; dist >= 35m -> factor = 0
; in between: rem = 35 - dist; factor = 1000 * rem * rem / 1089
mov eax, distVal
cmp eax, 2
jbe falloff_full
cmp eax, 35
jae falloff_silent
mov ecx, 35
sub ecx, eax ; ecx = 35 - distVal (in 1..32)
imul eax, ecx, 1000
imul eax, ecx ; eax = 1000 * rem * rem
xor edx, edx
mov ebx, 1089 ; 33 * 33
div ebx
mov factor, eax
jmp falloff_done
falloff_full:
mov factor, 1000
jmp falloff_done
falloff_upper:
; Level 2 (Upper Lounge) - sound travels up the open stairs (stairwell at X=16, Y=21 on upper map)
; Base attenuation through floor + distance from stairwell opening
mov eax, playerX
sub eax, 16*FP_1
test eax, eax
jns @F
neg eax
@@:
shr eax, 16
mov dxDist, eax
mov eax, playerY
sub eax, 21*FP_1
test eax, eax
jns @F
neg eax
@@:
shr eax, 16
mov dyDist, eax
mov eax, dxDist
imul eax, eax
mov ebx, dyDist
imul ebx, ebx
add eax, ebx
mov distSq, eax
fild distSq
fsqrt
fistp distVal
; At stair opening: factor = 420 (muffled from below)
; Drops by 18 per meter, min 90
mov eax, distVal
imul eax, 18
mov ecx, 420
sub ecx, eax
cmp ecx, 90
jge @F
mov ecx, 90
@@:
mov factor, ecx
jmp falloff_done
falloff_bunker:
; Level 1 (Bunker) - underground, heavily muffled concrete absorption (~160)
mov factor, 160
jmp falloff_done
falloff_fortune:
; Level 3 (Fortune Room) - secret room on opposite side of casino (~90)
mov factor, 90
jmp falloff_done
falloff_stairs:
; On stairs between floors
mov eax, stairRoute3D
cmp eax, 2
je falloff_stairs_upper
cmp eax, 1
je falloff_stairs_bunker
mov factor, 150
jmp falloff_done
falloff_stairs_upper:
; Route 2 (Upper stairs)
; playerX goes from ~3 (bottom, beside jukebox) to ~16 (top, upper floor)
; At bottom: 850. At top: 420.
mov eax, playerX
shr eax, 16 ; integer X (3..16)
cmp eax, 3
jge @F
mov eax, 3
@@:
cmp eax, 16
jle @F
mov eax, 16
@@:
; Interpolate: factor = 850 - (eax - 3) * (850 - 420) / 13
sub eax, 3
imul eax, 430
xor edx, edx
mov ebx, 13
div ebx
mov ecx, 850
sub ecx, eax
mov factor, ecx
jmp falloff_done
falloff_stairs_bunker:
; Route 1 (Basement stairs)
; Smooth blend between 350 (top) and 160 (bottom)
mov factor, 250
jmp falloff_done
falloff_silent:
mov factor, 0
falloff_done:
mov eax, factor
ret
CalculateJukeboxFalloff ENDP
UpdateJukeboxAudio3D PROC uses ebx esi edi
local falloff:DWORD, targetVol:DWORD
; Check if music is playing
cmp jboxIsPlaying, 1
jne jbox_audio_mute
; If user volume is 0 -> mute
mov eax, jboxUserVolume
test eax, eax
jz jbox_audio_mute
; Calculate 3D attenuation factor (0..1000)
call CalculateJukeboxFalloff
mov falloff, eax
test eax, eax
jz jbox_audio_mute
; targetVol = (jboxUserVolume * 100) * falloff / 1000
; jboxUserVolume is 0..10 (0..1000 base)
mov eax, jboxUserVolume
imul eax, 100
imul eax, falloff
xor edx, edx
mov ebx, 1000
div ebx
mov targetVol, eax
jmp jbox_apply_vol
jbox_audio_mute:
mov targetVol, 0
jbox_apply_vol:
; If targetVol matches last applied volume, skip redundant MCI call
mov eax, targetVol
cmp eax, jboxEffectiveVolume
je jbox_audio_done
mov jboxEffectiveVolume, eax
; Format: "setaudio jboxmusic volume to <targetVol>"
; Copy prefix to jboxVolCmdBuf
lea esi, jboxVolCmdPrefix
lea edi, jboxVolCmdBuf
jbox_vol_copy:
lodsb
stosb
test al, al
jnz jbox_vol_copy
dec edi ; point at null terminator
; Append decimal number targetVol
invoke FormatNumber, targetVol, edi
; Send to MCI
invoke mciSendStringA, OFFSET jboxVolCmdBuf, 0, 0, 0
jbox_audio_done:
ret
UpdateJukeboxAudio3D ENDP
; ==============================================================================
; VOLUME CONTROL KEYS & POP-UP SLIDING HUD BAR
; ==============================================================================
HandleJukeboxKeys3D PROC
; '+' key: VK_OEM_PLUS (0BBh) or VK_ADD (6Bh - numpad +)
invoke Pressed3D, 0BBh
test eax, eax
jnz jbox_do_plus
invoke Pressed3D, 6Bh
test eax, eax
jz jbox_check_minus
jbox_do_plus:
cmp jboxUserVolume, 10
jae @F
inc jboxUserVolume
@@:
mov jboxHudTimer, 90 ; Show HUD for ~1.5s
call UpdateJukeboxAudio3D
ret
jbox_check_minus:
; '-' key: VK_OEM_MINUS (0BDh) or VK_SUBTRACT (6Dh - numpad -)
invoke Pressed3D, 0BDh
test eax, eax
jnz jbox_do_minus
invoke Pressed3D, 6Dh
test eax, eax
jz jbox_keys_done
jbox_do_minus:
cmp jboxUserVolume, 0
jbe @F
dec jboxUserVolume
@@:
mov jboxHudTimer, 90 ; Show HUD for ~1.5s
call UpdateJukeboxAudio3D
jbox_keys_done:
ret
HandleJukeboxKeys3D ENDP
RenderJukeboxVolumeHUD PROC uses ebx esi edi
local winX:DWORD, winY:DWORD, barX:DWORD, barY:DWORD, barW:DWORD, barH:DWORD
local numBuf[16]:BYTE
; 1. Update Timer and Animation Position
cmp jboxHudTimer, 0
je hud_sliding_down
; Active: decrement timer
dec jboxHudTimer
; Slide UP towards target Y = 206
cmp jboxHudY, 206
jbe hud_at_target
sub jboxHudY, 4
cmp jboxHudY, 206
jae hud_at_target
mov jboxHudY, 206
hud_at_target:
jmp hud_draw
hud_sliding_down:
; Timer expired: slide DOWN towards 240 (offscreen)
cmp jboxHudY, 240
jae hud_hidden
add jboxHudY, 3
cmp jboxHudY, 240
jbe hud_draw
mov jboxHudY, 240
hud_hidden:
ret
hud_draw:
mov eax, jboxHudY
mov winY, eax
mov winX, 72 ; Centered in 320: width 176
; 2. Background and Frame
; Outer dark shadow (178 x 30)
mov eax, winX
dec eax
mov ecx, winY
dec ecx
invoke DrawBox, eax, ecx, 178, 30, 0008100Ch
; Outer bezel (176 x 28)
invoke DrawBox, winX, winY, 176, 28, 001C2E24h
; Inner panel body (174 x 26)
mov eax, winX
inc eax
mov ecx, winY
inc ecx
invoke DrawBox, eax, ecx, 174, 26, 00101A14h
; Brass top trim accent (174 x 1)
mov eax, winX
inc eax
mov ecx, winY
inc ecx
invoke DrawBox, eax, ecx, 174, 1, 008A6B34h
; 3. Title text: "MUZ. AVTOMAT: " + percentage
mov eax, winX
add eax, 10
mov ecx, winY
add ecx, 4
invoke DrawString8x8, eax, ecx, OFFSET jboxHudTitle, 0050FF90h
; Draw percentage or "VYKL"
mov eax, winX
add eax, 126
mov ecx, winY
add ecx, 4
cmp jboxUserVolume, 0
jne hud_draw_pct_num
invoke DrawString8x8, eax, ecx, OFFSET jboxHudMute, 008CB8A5h
jmp hud_draw_trough
hud_draw_pct_num:
push eax
push ecx
mov edx, jboxUserVolume
imul edx, 10
lea edi, numBuf
invoke FormatNumber, edx, edi
hud_find_term:
cmp byte ptr [edi], 0
je hud_append_pct
inc edi
jmp hud_find_term
hud_append_pct:
mov byte ptr [edi], '%'
mov byte ptr [edi+1], 0
pop ecx
pop eax
lea edx, numBuf
invoke DrawString8x8, eax, ecx, edx, 00F0E6C3h
hud_draw_trough:
; Progress trough at barX = winX + 24, barY = winY + 15, barW = 128, barH = 7
mov eax, winX
add eax, 24
mov barX, eax
mov ecx, winY
add ecx, 15
mov barY, ecx
mov barW, 128
mov barH, 7
; Trough border
mov eax, barX
dec eax
mov ecx, barY
dec ecx
invoke DrawBox, eax, ecx, 130, 9, 00223828h
invoke DrawBox, barX, barY, 128, 7, 000C1610h
; Draw 10 blocks: i = 0..9
xor esi, esi
hud_blocks_loop:
mov eax, barX
add eax, 4
mov edx, esi
imul edx, 12
add eax, edx ; blockX = barX + 4 + i*12
mov ecx, barY
inc ecx ; blockY = barY + 1
cmp esi, jboxUserVolume
jae hud_block_empty
; Filled block color gradient
cmp esi, 7
jb hud_col_green
cmp esi, 9
jb hud_col_gold
mov edx, 00465AFFh ; Red/orange accent for 100%
jmp hud_draw_block_rect
hud_col_green:
mov edx, 0050FF90h ; Phosphor green
jmp hud_draw_block_rect
hud_col_gold:
mov edx, 00FFD700h ; Golden amber
jmp hud_draw_block_rect
hud_block_empty:
mov edx, 0018261Eh ; Dim background
hud_draw_block_rect:
invoke DrawBox, eax, ecx, 10, 5, edx
inc esi
cmp esi, 10
jb hud_blocks_loop
; Draw '-' and '+' labels
mov eax, winX
add eax, 8
mov ecx, winY
add ecx, 15
invoke DrawString8x8, eax, ecx, OFFSET jboxHudMinus, 008CB8A5h
mov eax, winX
add eax, 158
mov ecx, winY
add ecx, 15
invoke DrawString8x8, eax, ecx, OFFSET jboxHudPlus, 008CB8A5h
ret
RenderJukeboxVolumeHUD ENDP

View file

@ -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

View file

@ -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