KAZIK/src_graphics/pyramid_rules.inc
Игорь Федосеев ca62db53e9 feat: implement native billiards, scratch tickets, first-person dice, and city wake scene
- 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
2026-10-16 12:37:40 +03:00

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