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