- Billiards: add native Russian Pyramid & American Eight-Ball pool with physics prediction, controls, and regression tests - Scratch Tickets: add dedicated counter, mat presentation, elliptical coin scraping, 4 ticket types, and payout effects - First-Person Dice: add native dice scene, hand anchor frames, throwing animation, and table perspective - City Wake & Weather: add arrival cutscene, alley geometry, weather/rain effects, and lighter/rocker animation - World Dressing: add hall paintings, furniture packs, hall lighting, and scene regression tests - Build & Tooling: update build.bat for modern MSVC toolchain, update launcher hash, and refresh shipping binaries
607 lines
13 KiB
PHP
607 lines
13 KiB
PHP
InitPyramidState3D PROC uses esi edi
|
|
mov pyrCue3D,0
|
|
mov poolFirst3D,-1
|
|
mov pyrScore3D,0
|
|
mov pyrScore3D[4],0
|
|
mov pyrFine3D,0
|
|
mov pyrFineActive3D,0
|
|
mov pyrAimCarry3D,0
|
|
mov pyrPickClick3D,0
|
|
mov pyrSettled3D,0
|
|
mov pyrFoulReason3D,0
|
|
mov pyrIllegalBreak3D,0
|
|
mov pyrValidEnd3D,0
|
|
xor esi,esi
|
|
pyr_init_records:
|
|
mov pyrOwner3D[esi*4],-1
|
|
mov pyrRoll3D[esi*4],0
|
|
mov pyrHistoryRails3D[esi*4],0
|
|
mov pyrHistoryCross3D[esi*4],0
|
|
mov pyrRailHalf3D[esi*4],0
|
|
inc esi
|
|
cmp esi,16
|
|
jb pyr_init_records
|
|
invoke LoadAnimPack3D,OFFSET pyrTablePack3D,OFFSET pyrTablePath3D
|
|
invoke LoadAnimPack3D,OFFSET pyrBallsPack3D,OFFSET pyrBallsPath3D
|
|
invoke LoadAnimPack3D,OFFSET poolEightPack3D,OFFSET poolEightPath3D
|
|
ret
|
|
InitPyramidState3D ENDP
|
|
|
|
FreePyramidAssets3D PROC
|
|
invoke FreeAnimPack3D,OFFSET pyrTablePack3D
|
|
invoke FreeAnimPack3D,OFFSET pyrBallsPack3D
|
|
invoke FreeAnimPack3D,OFFSET poolEightPack3D
|
|
ret
|
|
FreePyramidAssets3D ENDP
|
|
|
|
; Q4 length calculation avoids the signed overflow of long Q8 table vectors.
|
|
PyramidDistance3D PROC uses ebx deltaX:DWORD,deltaY:DWORD
|
|
mov eax,deltaX
|
|
sar eax,12
|
|
imul eax,eax
|
|
mov ebx,eax
|
|
mov eax,deltaY
|
|
sar eax,12
|
|
imul eax,eax
|
|
add eax,ebx
|
|
invoke PoolSqrt3D,eax
|
|
shl eax,12
|
|
ret
|
|
PyramidDistance3D ENDP
|
|
|
|
PyramidContact3D PROC uses ebx esi edi a:DWORD,b:DWORD
|
|
mov esi,a
|
|
mov edi,b
|
|
cmp poolFirst3D,-1
|
|
jne pyr_contact_events
|
|
cmp esi,pyrCue3D
|
|
jne @F
|
|
mov poolFirst3D,edi
|
|
jmp pyr_contact_events
|
|
@@:
|
|
cmp edi,pyrCue3D
|
|
jne pyr_contact_done
|
|
mov poolFirst3D,esi
|
|
pyr_contact_events:
|
|
; Ordered cushion/crossing history follows impulse transfers, not all balls.
|
|
invoke PyramidContactEnd3D,esi,edi
|
|
invoke PyramidContactEnd3D,edi,esi
|
|
mov eax,pyrHistoryRails3D[esi*4]
|
|
or eax,pyrHistoryRails3D[edi*4]
|
|
mov pyrHistoryRails3D[esi*4],eax
|
|
mov pyrHistoryRails3D[edi*4],eax
|
|
mov eax,pyrHistoryCross3D[esi*4]
|
|
or eax,pyrHistoryCross3D[edi*4]
|
|
mov pyrHistoryCross3D[esi*4],eax
|
|
mov pyrHistoryCross3D[edi*4],eax
|
|
mov eax,pyrRailHalf3D[esi*4]
|
|
or eax,pyrRailHalf3D[edi*4]
|
|
mov pyrRailHalf3D[esi*4],eax
|
|
mov pyrRailHalf3D[edi*4],eax
|
|
pyr_contact_done:
|
|
ret
|
|
PyramidContact3D ENDP
|
|
|
|
PyramidContactEnd3D PROC uses ebx esi edi moving:DWORD,other:DWORD
|
|
mov esi,moving
|
|
mov edi,other
|
|
test pyrRailHalf3D[esi*4],1
|
|
jz @F
|
|
cmp poolX3D[edi*4],160*FP_1
|
|
jge pyr_contact_valid
|
|
@@:
|
|
test pyrRailHalf3D[esi*4],2
|
|
jz @F
|
|
cmp poolX3D[edi*4],160*FP_1
|
|
jle pyr_contact_valid
|
|
@@:
|
|
; A frozen-ball contact counts only against an eligible cushion history.
|
|
mov ebx,0
|
|
cmp poolY3D[edi*4],50*FP_1+POOL_RADIUS+256
|
|
jle pyr_contact_frozen
|
|
mov ebx,1
|
|
cmp poolY3D[edi*4],180*FP_1-POOL_RADIUS-256
|
|
jge pyr_contact_frozen
|
|
mov ebx,2
|
|
cmp poolX3D[edi*4],30*FP_1+POOL_RADIUS+256
|
|
jle pyr_contact_frozen
|
|
mov ebx,3
|
|
cmp poolX3D[edi*4],290*FP_1-POOL_RADIUS-256
|
|
jl pyr_contact_end_done
|
|
pyr_contact_frozen:
|
|
cmp pyrHistoryCross3D[esi*4],0
|
|
jne pyr_contact_valid
|
|
mov eax,pyrHistoryRails3D[esi*4]
|
|
mov ecx,ebx
|
|
btr eax,ecx
|
|
test eax,eax
|
|
jz pyr_contact_end_done
|
|
pyr_contact_valid:
|
|
mov pyrValidEnd3D,1
|
|
pyr_contact_end_done:
|
|
ret
|
|
PyramidContactEnd3D ENDP
|
|
|
|
PyramidRail3D PROC uses ebx ball:DWORD,side:DWORD
|
|
cmp poolFirst3D,-1
|
|
je pyr_rail_done
|
|
mov ebx,ball
|
|
mov eax,pyrHistoryRails3D[ebx*4]
|
|
mov ecx,side
|
|
btr eax,ecx
|
|
test eax,eax
|
|
jnz pyr_rail_valid
|
|
cmp pyrHistoryCross3D[ebx*4],0
|
|
je @F
|
|
pyr_rail_valid:
|
|
mov pyrValidEnd3D,1
|
|
@@:
|
|
mov ecx,side
|
|
bts pyrHistoryRails3D[ebx*4],ecx
|
|
mov eax,1
|
|
cmp poolX3D[ebx*4],160*FP_1
|
|
jl @F
|
|
mov eax,2
|
|
@@:
|
|
or pyrRailHalf3D[ebx*4],eax
|
|
pyr_rail_done:
|
|
ret
|
|
PyramidRail3D ENDP
|
|
|
|
PyramidCross3D PROC uses ebx ball:DWORD
|
|
cmp poolFirst3D,-1
|
|
je pyr_cross_done
|
|
mov ebx,ball
|
|
mov eax,pyrPreviousX3D[ebx*4]
|
|
cmp eax,160*FP_1
|
|
je pyr_cross_done
|
|
jl pyr_cross_from_left
|
|
cmp poolX3D[ebx*4],160*FP_1
|
|
jge pyr_cross_done
|
|
jmp pyr_cross_record
|
|
pyr_cross_from_left:
|
|
cmp poolX3D[ebx*4],160*FP_1
|
|
jle pyr_cross_done
|
|
pyr_cross_record:
|
|
mov pyrHistoryCross3D[ebx*4],1
|
|
cmp pyrHistoryRails3D[ebx*4],0
|
|
je pyr_cross_done
|
|
mov pyrValidEnd3D,1
|
|
pyr_cross_done:
|
|
ret
|
|
PyramidCross3D ENDP
|
|
|
|
PyramidCanPlace3D PROC uses ebx esi ball:DWORD,px:DWORD,py:DWORD
|
|
cmp px,30*FP_1+POOL_RADIUS
|
|
jl pyr_place_bad
|
|
cmp px,290*FP_1-POOL_RADIUS
|
|
jg pyr_place_bad
|
|
cmp py,50*FP_1+POOL_RADIUS
|
|
jl pyr_place_bad
|
|
cmp py,180*FP_1-POOL_RADIUS
|
|
jg pyr_place_bad
|
|
xor esi,esi
|
|
pyr_place_scan:
|
|
cmp esi,ball
|
|
je pyr_place_next
|
|
cmp poolActive3D[esi*4],0
|
|
je pyr_place_next
|
|
mov eax,px
|
|
sub eax,poolX3D[esi*4]
|
|
sar eax,12
|
|
imul eax,eax
|
|
mov ebx,eax
|
|
mov eax,py
|
|
sub eax,poolY3D[esi*4]
|
|
sar eax,12
|
|
imul eax,eax
|
|
add eax,ebx
|
|
cmp eax,25*16*16
|
|
jbe pyr_place_bad
|
|
pyr_place_next:
|
|
inc esi
|
|
cmp esi,16
|
|
jb pyr_place_scan
|
|
mov eax,1
|
|
ret
|
|
pyr_place_bad:
|
|
xor eax,eax
|
|
ret
|
|
PyramidCanPlace3D ENDP
|
|
|
|
PyramidRespot3D PROC uses ebx esi ball:DWORD
|
|
mov ebx,225*FP_1
|
|
pyr_respot_foot:
|
|
invoke PyramidCanPlace3D,ball,ebx,115*FP_1
|
|
test eax,eax
|
|
jnz pyr_respot_found
|
|
add ebx,FP_1*501/100
|
|
cmp ebx,287*FP_1
|
|
jle pyr_respot_foot
|
|
mov ebx,225*FP_1-FP_1*501/100
|
|
pyr_respot_front:
|
|
invoke PyramidCanPlace3D,ball,ebx,115*FP_1
|
|
test eax,eax
|
|
jnz pyr_respot_found
|
|
sub ebx,FP_1*501/100
|
|
cmp ebx,33*FP_1
|
|
jge pyr_respot_front
|
|
; Pathological layouts still keep every ball: bounded free-cloth fallback.
|
|
mov esi,55*FP_1
|
|
pyr_respot_row:
|
|
mov ebx,35*FP_1
|
|
pyr_respot_grid:
|
|
invoke PyramidCanPlace3D,ball,ebx,esi
|
|
test eax,eax
|
|
jnz pyr_respot_grid_found
|
|
add ebx,6*FP_1
|
|
cmp ebx,285*FP_1
|
|
jle pyr_respot_grid
|
|
add esi,6*FP_1
|
|
cmp esi,175*FP_1
|
|
jle pyr_respot_row
|
|
xor eax,eax
|
|
ret
|
|
pyr_respot_found:
|
|
mov esi,115*FP_1
|
|
pyr_respot_grid_found:
|
|
mov ecx,ball
|
|
mov poolX3D[ecx*4],ebx
|
|
mov poolY3D[ecx*4],esi
|
|
mov poolVX3D[ecx*4],0
|
|
mov poolVY3D[ecx*4],0
|
|
mov poolActive3D[ecx*4],1
|
|
mov poolPocket3D[ecx*4],-1
|
|
mov poolSinkTime3D[ecx*4],0
|
|
mov pyrOwner3D[ecx*4],-1
|
|
mov eax,1
|
|
ret
|
|
PyramidRespot3D ENDP
|
|
|
|
PoolPlaceCue3D PROC px:DWORD,py:DWORD
|
|
invoke PyramidCanPlace3D,0,px,py
|
|
test eax,eax
|
|
jz @F
|
|
mov eax,px
|
|
mov poolX3D,eax
|
|
mov eax,py
|
|
mov poolY3D,eax
|
|
mov poolActive3D,1
|
|
mov poolPocket3D,-1
|
|
mov poolSinkTime3D,0
|
|
mov poolVX3D,0
|
|
mov poolVY3D,0
|
|
mov eax,1
|
|
@@:
|
|
ret
|
|
PoolPlaceCue3D ENDP
|
|
|
|
PoolBallLegal3D PROC ball:DWORD
|
|
cmp poolVariant3D,1
|
|
jne @F
|
|
invoke EightLegal3D,ball
|
|
ret
|
|
@@:
|
|
mov ecx,ball
|
|
xor eax,eax
|
|
cmp ecx,15
|
|
ja @F
|
|
cmp ecx,pyrCue3D
|
|
je @F
|
|
cmp poolActive3D[ecx*4],1
|
|
sete al
|
|
@@:
|
|
ret
|
|
PoolBallLegal3D ENDP
|
|
|
|
PoolCountGroup3D PROC
|
|
cmp poolVariant3D,1
|
|
jne @F
|
|
call EightCount3D
|
|
ret
|
|
@@:
|
|
mov poolAtStart3D,0
|
|
ret
|
|
PoolCountGroup3D ENDP
|
|
|
|
PoolFinishShot3D PROC uses ebx esi edi
|
|
cmp poolVariant3D,1
|
|
jne @F
|
|
call EightFinish3D
|
|
ret
|
|
@@:
|
|
cmp pyrSettled3D,0
|
|
jne pyr_finish_done
|
|
mov pyrSettled3D,1
|
|
mov poolFoul3D,0
|
|
mov pyrFoulReason3D,0
|
|
mov pyrIllegalBreak3D,0
|
|
cmp poolFirst3D,-1
|
|
jne pyr_finish_contact
|
|
mov pyrFoulReason3D,1
|
|
jmp pyr_finish_foul
|
|
pyr_finish_contact:
|
|
cmp poolBreak3D,0
|
|
je pyr_finish_normal
|
|
cmp poolPotted3D,0
|
|
jne pyr_finish_score
|
|
mov eax,poolBreakRails3D
|
|
mov ecx,pyrCue3D
|
|
btr eax,ecx
|
|
xor ebx,ebx
|
|
pyr_finish_rail_count:
|
|
test eax,eax
|
|
jz @F
|
|
lea ecx,[eax-1]
|
|
and eax,ecx
|
|
inc ebx
|
|
jmp pyr_finish_rail_count
|
|
@@:
|
|
cmp ebx,3
|
|
jae pyr_finish_turn
|
|
cmp ebx,2
|
|
jb pyr_finish_bad_break
|
|
xor esi,esi
|
|
pyr_finish_break_cross:
|
|
cmp esi,pyrCue3D
|
|
je @F
|
|
cmp pyrHistoryCross3D[esi*4],0
|
|
jne pyr_finish_turn
|
|
@@:
|
|
inc esi
|
|
cmp esi,16
|
|
jb pyr_finish_break_cross
|
|
pyr_finish_bad_break:
|
|
mov pyrFoulReason3D,2
|
|
jmp pyr_finish_foul
|
|
pyr_finish_normal:
|
|
cmp poolPotted3D,0
|
|
jne pyr_finish_score
|
|
cmp pyrValidEnd3D,0
|
|
jne pyr_finish_turn
|
|
mov pyrFoulReason3D,3
|
|
pyr_finish_foul:
|
|
mov poolFoul3D,1
|
|
mov eax,poolBreak3D
|
|
mov pyrIllegalBreak3D,eax
|
|
xor esi,esi
|
|
pyr_finish_restore:
|
|
bt poolPotted3D,esi
|
|
jnc @F
|
|
invoke PyramidRespot3D,esi
|
|
@@:
|
|
inc esi
|
|
cmp esi,16
|
|
jb pyr_finish_restore
|
|
mov eax,poolPlayer3D
|
|
mov pyrFouler3D,eax
|
|
xor eax,1
|
|
mov poolPlayer3D,eax
|
|
mov pyrChoiceActor3D,eax
|
|
mov poolState3D,POOL_PENALTY
|
|
mov poolTime3D,0
|
|
mov poolBreak3D,0
|
|
ret
|
|
pyr_finish_score:
|
|
mov ebx,poolPlayer3D
|
|
xor esi,esi
|
|
pyr_finish_credit:
|
|
bt poolPotted3D,esi
|
|
jnc @F
|
|
mov pyrOwner3D[esi*4],ebx
|
|
inc pyrScore3D[ebx*4]
|
|
@@:
|
|
inc esi
|
|
cmp esi,16
|
|
jb pyr_finish_credit
|
|
cmp pyrScore3D[ebx*4],8
|
|
jb pyr_finish_aim
|
|
mov poolWinner3D,ebx
|
|
mov poolState3D,POOL_RESULT
|
|
ret
|
|
pyr_finish_turn:
|
|
xor poolPlayer3D,1
|
|
pyr_finish_aim:
|
|
mov poolBreak3D,0
|
|
mov poolState3D,POOL_AIM
|
|
mov poolTime3D,0
|
|
invoke PyramidSelect3D,-1
|
|
pyr_finish_done:
|
|
ret
|
|
PoolFinishShot3D ENDP
|
|
|
|
PyramidPenalty3D PROC uses ebx ball:DWORD
|
|
cmp poolState3D,POOL_PENALTY
|
|
jne pyr_penalty_done
|
|
mov ecx,ball
|
|
cmp ecx,15
|
|
ja pyr_penalty_done
|
|
cmp poolActive3D[ecx*4],1
|
|
jne pyr_penalty_done
|
|
mov ebx,poolPlayer3D
|
|
mov pyrOwner3D[ecx*4],ebx
|
|
mov poolActive3D[ecx*4],0
|
|
mov poolPocket3D[ecx*4],-2
|
|
mov poolSinkTime3D[ecx*4],0
|
|
mov poolVX3D[ecx*4],0
|
|
mov poolVY3D[ecx*4],0
|
|
inc pyrScore3D[ebx*4]
|
|
cmp pyrScore3D[ebx*4],8
|
|
jb @F
|
|
mov poolWinner3D,ebx
|
|
mov poolState3D,POOL_RESULT
|
|
ret
|
|
@@:
|
|
mov poolState3D,POOL_CHOICE
|
|
mov poolTime3D,0
|
|
pyr_penalty_done:
|
|
ret
|
|
PyramidPenalty3D ENDP
|
|
|
|
PyramidChoice3D PROC uses ebx esi edi choice:DWORD
|
|
cmp poolState3D,POOL_CHOICE
|
|
jne pyr_choice_done
|
|
cmp choice,3
|
|
ja pyr_choice_done
|
|
cmp poolVariant3D,1
|
|
jne pyr_choice_pyramid
|
|
mov ebx,pyrChoiceActor3D
|
|
test choice,1
|
|
jz @F
|
|
xor ebx,1
|
|
@@:
|
|
cmp choice,2
|
|
jb eight_choice_accept
|
|
call ResetPool3D
|
|
mov poolPlayer3D,ebx
|
|
ret
|
|
eight_choice_accept:
|
|
mov poolPlayer3D,ebx
|
|
mov poolBreak3D,0
|
|
mov poolState3D,POOL_AIM
|
|
mov poolTime3D,0
|
|
ret
|
|
pyr_choice_pyramid:
|
|
cmp choice,2
|
|
jb pyr_choice_actor
|
|
cmp pyrIllegalBreak3D,0
|
|
je pyr_choice_done
|
|
; The penalty score survives a partial re-rack.
|
|
cmp pyrOwner3D, -1
|
|
je @F
|
|
mov esi,1
|
|
pyr_rack_exchange:
|
|
cmp pyrOwner3D[esi*4],-1
|
|
je pyr_rack_swap
|
|
inc esi
|
|
cmp esi,16
|
|
jb pyr_rack_exchange
|
|
jmp pyr_choice_done
|
|
pyr_rack_swap:
|
|
mov eax,pyrOwner3D
|
|
mov pyrOwner3D[esi*4],eax
|
|
mov poolActive3D[esi*4],0
|
|
mov poolPocket3D[esi*4],-2
|
|
mov pyrOwner3D,-1
|
|
@@:
|
|
mov poolActive3D,1
|
|
mov poolPocket3D,-1
|
|
mov poolX3D,82*FP_1
|
|
mov poolY3D,115*FP_1
|
|
mov poolVX3D,0
|
|
mov poolVY3D,0
|
|
mov poolBreak3D,1
|
|
mov pyrCue3D,0
|
|
call PyramidRack3D
|
|
pyr_choice_actor:
|
|
mov eax,pyrChoiceActor3D
|
|
test choice,1
|
|
jz @F
|
|
xor eax,1
|
|
@@:
|
|
mov poolPlayer3D,eax
|
|
mov poolState3D,POOL_AIM
|
|
mov poolTime3D,0
|
|
mov pyrFineActive3D,0
|
|
mov poolAngle3D,0
|
|
invoke PyramidSelect3D,-1
|
|
pyr_choice_done:
|
|
ret
|
|
PyramidChoice3D ENDP
|
|
|
|
; Center a partial base; move the apex to the base if parity requires it.
|
|
PyramidRack3D PROC uses ebx esi edi
|
|
local remaining:DWORD,rowCount:DWORD,startSlot:DWORD,apex:DWORD
|
|
local apexX:DWORD,apexY:DWORD,moveApex:DWORD
|
|
mov remaining,0
|
|
mov apex,-1
|
|
mov moveApex,0
|
|
mov esi,1
|
|
pyr_rack_count:
|
|
cmp pyrOwner3D[esi*4],-1
|
|
jne @F
|
|
inc remaining
|
|
@@:
|
|
inc esi
|
|
cmp esi,16
|
|
jb pyr_rack_count
|
|
xor ebx,ebx
|
|
mov esi,1
|
|
pyr_rack_row:
|
|
lea eax,[ebx+1]
|
|
cmp eax,remaining
|
|
jbe @F
|
|
mov eax,remaining
|
|
@@:
|
|
mov rowCount,eax
|
|
mov ecx,ebx
|
|
inc ecx
|
|
sub ecx,eax
|
|
test ecx,1
|
|
jz pyr_rack_even
|
|
; One apex ball joins the last row instead of creating an asymmetric base.
|
|
dec ecx
|
|
shr ecx,1
|
|
mov startSlot,ecx
|
|
imul eax,ebx,FP_1*43302/10000
|
|
add eax,225*FP_1
|
|
mov apexX,eax
|
|
imul eax,ecx,5*FP_1
|
|
imul edx,ebx,FP_1*5/2
|
|
sub eax,edx
|
|
add eax,115*FP_1
|
|
mov apexY,eax
|
|
mov moveApex,1
|
|
inc startSlot
|
|
jmp pyr_rack_row_balls
|
|
pyr_rack_even:
|
|
shr ecx,1
|
|
mov startSlot,ecx
|
|
pyr_rack_row_balls:
|
|
xor edi,edi
|
|
pyr_rack_find:
|
|
cmp pyrOwner3D[esi*4],-1
|
|
je pyr_rack_place
|
|
inc esi
|
|
cmp esi,16
|
|
jb pyr_rack_find
|
|
jmp pyr_rack_complete
|
|
pyr_rack_place:
|
|
cmp apex,-1
|
|
jne @F
|
|
mov apex,esi
|
|
@@:
|
|
imul eax,ebx,FP_1*43302/10000
|
|
add eax,225*FP_1
|
|
mov poolX3D[esi*4],eax
|
|
mov eax,edi
|
|
add eax,startSlot
|
|
imul eax,5*FP_1
|
|
imul ecx,ebx,FP_1*5/2
|
|
sub eax,ecx
|
|
add eax,115*FP_1
|
|
mov poolY3D[esi*4],eax
|
|
mov poolActive3D[esi*4],1
|
|
mov poolPocket3D[esi*4],-1
|
|
mov poolVX3D[esi*4],0
|
|
mov poolVY3D[esi*4],0
|
|
mov poolSinkTime3D[esi*4],0
|
|
inc esi
|
|
inc edi
|
|
dec remaining
|
|
cmp edi,rowCount
|
|
jb pyr_rack_find
|
|
inc ebx
|
|
cmp remaining,0
|
|
jne pyr_rack_row
|
|
pyr_rack_complete:
|
|
cmp moveApex,0
|
|
je @F
|
|
mov ecx,apex
|
|
mov eax,apexX
|
|
mov poolX3D[ecx*4],eax
|
|
mov eax,apexY
|
|
mov poolY3D[ecx*4],eax
|
|
@@:
|
|
ret
|
|
PyramidRack3D ENDP
|