diff --git a/bin/poker.exe b/bin/poker.exe index b5929a1..7749556 100644 Binary files a/bin/poker.exe and b/bin/poker.exe differ diff --git a/build/AssemblerCasinoLauncher.cs b/build/AssemblerCasinoLauncher.cs index 782a01c..6d5178e 100644 --- a/build/AssemblerCasinoLauncher.cs +++ b/build/AssemblerCasinoLauncher.cs @@ -8,7 +8,7 @@ namespace AssemblerCasinoLauncher { static class Program { - const string ExpectedPokerSha256 = "D25606FE934E0D447DC4791218F8213BB1A21B0F2F7C8AFC0B61D419C03E92C0"; + const string ExpectedPokerSha256 = "2357F5876DA47FD6CD7FAE7216C71FCEDE6CCA79C1591E2BA6CFD2F3DB61694E"; [STAThread] static void Main() diff --git a/build/PokerGameSetupGui.cs b/build/PokerGameSetupGui.cs index 4264a1d..fbfd740 100644 --- a/build/PokerGameSetupGui.cs +++ b/build/PokerGameSetupGui.cs @@ -27,7 +27,7 @@ namespace PokerGameSetup sealed class InstallerForm : Form { const string Marker = "<>"; - const string ExpectedPayloadSha256 = "1A2AC9A543AAF354EAD5BFA4716A8677143E27D994D55120AAF4ACF8296AAA6B"; + const string ExpectedPayloadSha256 = "D44B44805C80AA6107F2F0226D0B98F0C7B085031911CB97FB89729F9B2510E6"; readonly Color bg = Color.FromArgb(15, 12, 10); readonly Color panel = Color.FromArgb(28, 22, 18); readonly Color gold = Color.FromArgb(236, 186, 75); diff --git a/build/graphics/poker.exe b/build/graphics/poker.exe index b5929a1..7749556 100644 Binary files a/build/graphics/poker.exe and b/build/graphics/poker.exe differ diff --git a/casino3d.inc b/casino3d.inc index c70ad8c..71f7b91 100644 --- a/casino3d.inc +++ b/casino3d.inc @@ -5367,6 +5367,11 @@ pause_interior_walk: xor paused3D, 1 jmp engine_tick leave_table: + cmp engineMode,MODE_BOWLING + jne @F + call LeaveBowlingPlay3D + jmp engine_tick +@@: cmp engineMode,MODE_FORTUNE jne @F call StopFortuneWorker3D @@ -5474,6 +5479,14 @@ engine_backrooms: call RenderBackrooms3D jmp present_frame check_slots_mode: + cmp engineMode,MODE_BOWLING + jne @F + call HandleBowlingPlay3D + cmp engineMode,MODE_WALK + je check_walk_mode + call RenderBowlingPlay3D + jmp present_frame +@@: cmp engineMode,MODE_JACKBOX jne check_jackbox_done call HandleJackbox3D diff --git a/dist/Assembler Casino.exe b/dist/Assembler Casino.exe index a3f424d..673dcbd 100644 Binary files a/dist/Assembler Casino.exe and b/dist/Assembler Casino.exe differ diff --git a/docs/BOWLING.md b/docs/BOWLING.md index 9f4782a..4d28057 100644 --- a/docs/BOWLING.md +++ b/docs/BOWLING.md @@ -1,6 +1,6 @@ # Bowling Lounge -The bowling lounge is a walkable room, not a player-controlled bowling minigame. +The bowling lounge is a walkable room with playable ten-frame bowling against a bot. Enter through the wide opening in the north wall of the main casino, near the slot-machine side of the hall. Walk through to enter or leave; no interaction button or timed stair/elevator animation is used. @@ -19,7 +19,36 @@ All room geometry, rendering, input, scoring and visitor simulation run in MASM. Furniture is built from fixed world-space surfaces, not rotating room billboards. Human sprites retain the game's existing 2.5D presentation. -## Simulation +## Player Game + +Approach a free lane's foul line and press E on the play prompt. Occupied visitor +lanes stay unavailable. There is no entry fee or bankroll change. The camera and +room position are restored on exit; leaving abandons the current lane match. + +The throw has two separate timing gates, both accepting either left mouse click +or a fresh Space press. First stop the oscillating direction arrow on its curved +horizontal scale. Then stop the vertical power indicator. The second press +commits the launch. Holding Space cannot accidentally consume both stages. +The arrow and meter update from elapsed time, not frame count. The ball follows +the committed parameters without additional random error. + +Player and bot alternate complete frames. A second ball uses the remaining rack; +strikes, spares and tenth-frame bonuses use the shared native bowling scorer. +The lane monitor shows the two scores and current phase. Tab toggles a cumulative +ten-frame score sheet; unresolved bonuses stay blank rather than inventing a total. +The sheet pauses the current throw. Escape or the Exit command returns to the room. +After a completed match, click or Space starts a fresh match. + +The player game uses an arcade, not regulation-grade, collision approximation: +16-ms fixed steps, a rolling ball disc, ten pin discs, transferred pin impulses +and pin friction. Gutters latch once entered. Pins knocked by the ball can knock +other pins; removed pins cannot be scored again. The opponent uses the identical +solver, with private lane RNG choosing only its launch aim and power. The camera +follows the rolling ball; the bot uses a full-body shared-cast throwing animation. +The blue ball is a native shaded sphere with a procedural marble surface and +rotating finger-hole details, not a new neural or image runtime. + +## Visitor Simulation Each occupied lane owns a timer, rack mask, two players, frame counters and roll history. The sequence is pick-up, approach/release, ball travel, pin impact, @@ -55,6 +84,10 @@ Run the offline packer with Python, then build the MASM executable normally. checks entrance visibility, walkable access, both room transitions, lane/furniture collision, sitting/restoration, 600 simulated seconds of lane lifecycles, perfect game/all-spare/open-frame score vectors, and captures room/throw screenshots. +The additional `bowling_play_test.inc` covers LMB/Space input through native window +events, stage separation, deterministic impacts at two frame deltas, gutters, +empty/remaining racks, alternating turns, spare feedback, perfect games and the +tenth-frame bonuses, unresolved score cells, scene captures and exit restoration. The complete hall suite includes the same bowling checks alongside existing stairs, doors, rooftop, fortune-room, bar and cabaret regressions. `--parity` compares the tested procedures with the shipping assembly object. diff --git a/docs/SHARED_CHARACTERS.md b/docs/SHARED_CHARACTERS.md index 8ef7db5..ffdefe5 100644 --- a/docs/SHARED_CHARACTERS.md +++ b/docs/SHARED_CHARACTERS.md @@ -24,6 +24,9 @@ casino visitor pauses movement while standing up from a chair. The canvas height is 1.3 world units (192:224 aspect), calibrated against the hall's 1.05-unit standing eye height. The former 1.95-unit canvas made visitors oversized compared with the camera, staff and furniture. +The rooftop uses a 1.65-unit standing eye height. Its visitor canvas, width and +foot anchor are scaled by the rooftop/hall standing-eye ratio (about 1.57), +independently of sitting or leaning. Indoor visitor sizes remain unchanged. Walking uses replacement `person-*-walk-source-v2.png` sheets with contact and passing poses in front, rear and right profile. The left profile is mirrored. @@ -72,6 +75,8 @@ wide contact and narrow passing foot spans. Distinct pixel hashes alone did not catch the old sheets' repeated leading-leg poses. The full hall regression includes room navigation, roof, bars, doors and stairs. Shipping/tested assembly parity is checked separately. +`roof_character_scale_test.inc` checks all eight rooftop visitors in standing and +walking states against that ratio and captures their native grounded silhouettes. The older guest and bowling source packs are retained as historical assets; the visitor renderers no longer load or use them. diff --git a/poker.exe b/poker.exe index b5929a1..7749556 100644 Binary files a/poker.exe and b/poker.exe differ diff --git a/poker.obj b/poker.obj index e91f7a5..7a43fe8 100644 Binary files a/poker.obj and b/poker.obj differ diff --git a/src_graphics/bowling.inc b/src_graphics/bowling.inc index 5dc190b..2fee45e 100644 --- a/src_graphics/bowling.inc +++ b/src_graphics/bowling.inc @@ -1,10 +1,22 @@ ; Native room, automatic lane simulation and full-body visitors; no AI service. InitBowling3D PROC uses esi edi + mov bpActive3D,0 + call InitBowlingBallSurface3D mov bwClock3D,-1 mov bwMonitorClock3D,-1 mov bwSeat3D,0 xor esi,esi bw_init_lane: + mov bwBusy3D[esi*4],0 + cmp esi,0 + je bw_init_busy + cmp esi,3 + je bw_init_busy + cmp esi,6 + jne bw_init_state +bw_init_busy: + mov bwBusy3D[esi*4],1 +bw_init_state: mov bwState3D[esi*4],0 imul eax,esi,349 mov bwTime3D[esi*4],eax @@ -50,6 +62,13 @@ FreeBowling3D ENDP ResolveBowlingSurface3D PROC material:DWORD mov eax,material + cmp eax,253 + jne @F + mov eax,OFFSET bpBallSurface3D + mov ecx,64 + mov edx,64 + ret +@@: cmp eax,252 jne @F mov eax,OFFSET bwSignPixels3D @@ -185,6 +204,22 @@ CheckBowlingInteractions3D PROC uses esi edi mov interactPromptPtr,OFFSET bwStandPrompt3D ret bw_check_seats: + xor edi,edi +bw_check_play: + cmp bwBusy3D[edi*4],0 + jne bw_check_play_next + imul eax,edi,BW_LANE_STEP + add eax,3*FP_1 + mov interactTargetId,0 + invoke HallTarget3D,eax,FP_1*237/10,INTERACT_BOWL_PLAY,OFFSET bpPlayPrompt + cmp interactTargetId,INTERACT_BOWL_PLAY + jne bw_check_play_next + mov bpTarget3D,edi + ret +bw_check_play_next: + inc edi + cmp edi,BW_LANES + jb bw_check_play lea esi,bwSeats3D xor edi,edi bw_check_seat: @@ -212,6 +247,11 @@ CheckBowlingInteractions3D ENDP TryBowlingInteraction3D PROC uses esi cmp hallLevel3D,HALL_BOWLING jne bw_try_none + cmp interactTargetId,INTERACT_BOWL_PLAY + jne @F + invoke BeginBowlingPlay3D,bpTarget3D + ret +@@: cmp interactTargetId,INTERACT_BOWL_SEAT jne bw_try_none cmp bwSeat3D,0 @@ -252,3 +292,4 @@ TryBowlingInteraction3D ENDP include src_graphics\bowling_simulation.inc include src_graphics\bowling_render.inc +include src_graphics\bowling_play.inc diff --git a/src_graphics/bowling_data.inc b/src_graphics/bowling_data.inc index 44eb7eb..d039eeb 100644 --- a/src_graphics/bowling_data.inc +++ b/src_graphics/bowling_data.inc @@ -2,7 +2,7 @@ HALL_BOWLING equ 5 INTERACT_BOWL_SEAT equ 53 INTERACT_BOWL_WATCH equ 54 BW_MATERIAL equ 230 -BW_LAST_MATERIAL equ 252 +BW_LAST_MATERIAL equ 253 BW_LANES equ 8 BW_LANE_STEP equ FP_1*19/10 InitBowling3D PROTO @@ -96,3 +96,4 @@ bwMonitors3D dd BW_LANES*128*64 dup(?) bwSignPixels3D dd 128*64 dup(?) .code include src_graphics\bowling_layout.inc +include src_graphics\bowling_play_data.inc diff --git a/src_graphics/bowling_play.inc b/src_graphics/bowling_play.inc new file mode 100644 index 0000000..a1e6ab7 --- /dev/null +++ b/src_graphics/bowling_play.inc @@ -0,0 +1,507 @@ +; Two timing gates commit one immutable launch; the same solver serves both players. +BeginBowlingPlay3D PROC uses esi lane:DWORD + cmp bpActive3D,0 + jne bp_begin_no + mov esi,lane + cmp esi,BW_LANES + jae bp_begin_no + cmp bwBusy3D[esi*4],0 + jne bp_begin_no + mov eax,playerX + mov bpHomeX3D,eax + mov eax,playerY + mov bpHomeY3D,eax + mov eax,playerAngle + mov bpHomeAngle3D,eax + mov bpLane3D,esi + imul eax,esi,BW_LANE_STEP + add eax,3*FP_1 + mov bpCentre3D,eax + mov bpActive3D,1 + mov bwBusy3D[esi*4],2 + mov engineMode,MODE_BOWLING + mov paused3D,0 + mov bwSeat3D,0 + call ResetBowlingPlay3D + mov eax,1 + ret +bp_begin_no: + xor eax,eax + ret +BeginBowlingPlay3D ENDP + +ResetBowlingPlay3D PROC uses ebx esi edi + mov esi,bpLane3D + lea ebx,[esi*8] + mov bwFrames3D[ebx],0 + mov bwFrames3D[ebx+4],0 + mov bwRollCount3D[ebx],0 + mov bwRollCount3D[ebx+4],0 + mov bwScores3D[ebx],0 + mov bwScores3D[ebx+4],0 + imul eax,esi,192 + lea edi,bwRolls3D[eax] + xor eax,eax + mov ecx,48 + rep stosd + mov bwTurn3D[esi*4],0 + mov bwBall3D[esi*4],0 + mov bwRack3D[esi*4],1023 + mov bwState3D[esi*4],0 + mov bwHit3D[esi*4],0 + mov bwTime3D[esi*4],0 + mov bpState3D,BP_DIRECTION + mov bpTime3D,0 + call BowlingPlayStartAim3D + mov bpScoreOpen3D,0 + mov bpLast3D,0 + mov bpLastKind3D,0 + call BowlingPlayRack3D + call ClearKeys3D + mov mouseClick3D,0 + ret +ResetBowlingPlay3D ENDP + +; Randomize only the visible starting phase, never the committed trajectory. +BowlingPlayStartAim3D PROC + mov eax,bwRng3D + imul eax,1664525 + add eax,1013904223 + mov bwRng3D,eax + shr eax,16 + and eax,2047 + mov bpClock3D,eax + ret +BowlingPlayStartAim3D ENDP + +LeaveBowlingPlay3D PROC uses esi + cmp bpActive3D,0 + je bp_leave_done + mov esi,bpLane3D + mov bwBusy3D[esi*4],0 + mov bwRack3D[esi*4],1023 + mov bwHit3D[esi*4],0 + mov bwState3D[esi*4],0 + mov bpActive3D,0 + mov bpScoreOpen3D,0 + mov eax,bpHomeX3D + mov playerX,eax + mov eax,bpHomeY3D + mov playerY,eax + mov eax,bpHomeAngle3D + mov playerAngle,eax + mov engineMode,MODE_WALK + call BowlingEye3D + call UpdateCamera3D + call ClearKeys3D + mov mouseClick3D,0 + mov bwMonitorClock3D,-1 +bp_leave_done: + ret +LeaveBowlingPlay3D ENDP + +BowlingPlayRack3D PROC uses esi + xor esi,esi +bp_rack_pin: + mov eax,bwPinX3D[esi*4] + mov bpPinX3D[esi*4],eax + mov eax,bwPinY3D[esi*4] + mov bpPinY3D[esi*4],eax + mov bpPinVX3D[esi*4],0 + mov bpPinVY3D[esi*4],0 + inc esi + cmp esi,10 + jb bp_rack_pin + mov bpHitMask3D,0 + mov bpGutter3D,0 + mov bpAccumulator3D,0 + mov bpBallX3D,0 + mov bpBallY3D,FP_1*225/10 + ret +BowlingPlayRack3D ENDP + +BowlingPlayLaunch3D PROC + mov bpState3D,BP_RELEASE + mov bpTime3D,0 + mov bpAccumulator3D,0 + mov eax,bpLockedPower3D + imul eax,6*FP_1 + cdq + mov ecx,100 + idiv ecx + add eax,3*FP_1 + imul eax,16 + cdq + mov ecx,1000 + idiv ecx + mov ecx,eax + neg eax + mov bpBallVY3D,eax + mov eax,bpLockedAim3D + imul ecx + mov ecx,18*FP_1 + idiv ecx + mov bpBallVX3D,eax + ret +BowlingPlayLaunch3D ENDP + +BowlingPlayHit3D PROC uses esi pin:DWORD,vx:DWORD,vy:DWORD + mov esi,pin + mov ecx,esi + mov eax,1 + shl eax,cl + test bpHitMask3D,eax + jnz bp_hit_done + or bpHitMask3D,eax + mov eax,vx + mov bpPinVX3D[esi*4],eax + mov eax,vy + mov bpPinVY3D[esi*4],eax +bp_hit_done: + ret +BowlingPlayHit3D ENDP + +; 16-ms fixed steps, lane-relative 16.16 discs. No random hit selection. +BowlingPlayStep3D PROC uses ebx esi edi + local deltaX:DWORD,deltaY:DWORD,impulseX:DWORD,rackMask:DWORD + mov esi,bpLane3D + mov eax,bwRack3D[esi*4] + mov rackMask,eax + mov eax,bpBallVX3D + add bpBallX3D,eax + mov eax,bpBallVY3D + add bpBallY3D,eax + mov eax,bpBallX3D + cdq + xor eax,edx + sub eax,edx + cmp eax,FP_1*74/100 + jbe bp_step_pins + mov bpGutter3D,1 + mov eax,FP_1*87/100 + cmp bpBallX3D,0 + jge @F + neg eax +@@: + mov bpBallX3D,eax + mov bpBallVX3D,0 +bp_step_pins: + xor esi,esi +bp_step_pin: + mov ecx,esi + mov ebx,1 + shl ebx,cl + test rackMask,ebx + jz bp_step_next_pin + test bpHitMask3D,ebx + jnz bp_step_move_pin + cmp bpGutter3D,0 + jne bp_step_next_pin + mov eax,bpPinX3D[esi*4] + sub eax,bpBallX3D + mov deltaX,eax + sar eax,8 + imul eax,eax + mov edi,eax + mov eax,bpPinY3D[esi*4] + sub eax,bpBallY3D + sar eax,8 + imul eax,eax + add eax,edi + cmp eax,56*56 + ja bp_step_next_pin + mov eax,deltaX + sar eax,3 + add eax,bpBallVX3D + mov impulseX,eax + mov eax,bpBallVY3D + imul eax,3 + sar eax,2 + invoke BowlingPlayHit3D,esi,impulseX,eax +bp_step_move_pin: + mov eax,bpPinVX3D[esi*4] + add bpPinX3D[esi*4],eax + imul eax,245 + sar eax,8 + mov bpPinVX3D[esi*4],eax + mov eax,bpPinVY3D[esi*4] + add bpPinY3D[esi*4],eax + imul eax,245 + sar eax,8 + mov bpPinVY3D[esi*4],eax + xor edi,edi +bp_step_chain: + mov ecx,edi + mov ebx,1 + shl ebx,cl + test rackMask,ebx + jz bp_step_chain_next + test bpHitMask3D,ebx + jnz bp_step_chain_next + mov eax,bpPinX3D[edi*4] + sub eax,bpPinX3D[esi*4] + mov deltaX,eax + sar eax,8 + imul eax,eax + mov ebx,eax + mov eax,bpPinY3D[edi*4] + sub eax,bpPinY3D[esi*4] + mov deltaY,eax + sar eax,8 + imul eax,eax + add eax,ebx + cmp eax,49*49 + ja bp_step_chain_next + mov eax,deltaX + sar eax,8 + imul bpPinVX3D[esi*4] + mov ebx,eax + mov eax,deltaY + sar eax,8 + imul bpPinVY3D[esi*4] + add eax,ebx + jle bp_step_chain_next + mov eax,bpPinVX3D[esi*4] + sar eax,1 + mov ebx,deltaX + sar ebx,3 + add eax,ebx + mov impulseX,eax + mov eax,bpPinVY3D[esi*4] + sar eax,1 + mov ebx,deltaY + sar ebx,3 + add eax,ebx + invoke BowlingPlayHit3D,edi,impulseX,eax +bp_step_chain_next: + inc edi + cmp edi,10 + jb bp_step_chain +bp_step_next_pin: + inc esi + cmp esi,10 + jb bp_step_pin + ret +BowlingPlayStep3D ENDP + +BowlingPlayFinishRoll3D PROC uses esi + mov eax,bpHitMask3D + xor ecx,ecx +bp_finish_count: + test eax,eax + jz bp_finish_register + lea edx,[eax-1] + and eax,edx + inc ecx + jmp bp_finish_count +bp_finish_register: + mov bpLast3D,ecx + mov bpLastKind3D,0 + mov esi,bpLane3D + cmp bwBall3D[esi*4],1 + jne bp_finish_strike + mov eax,bwFirstHit3D[esi*4] + cmp eax,10 + je bp_finish_strike + add eax,ecx + cmp eax,10 + jne bp_finish_register_score + mov bpLastKind3D,2 + jmp bp_finish_register_score +bp_finish_strike: + cmp ecx,10 + jne bp_finish_register_score + mov bpLastKind3D,1 +bp_finish_register_score: + invoke BowlingRegisterRoll3D,bpLane3D,ecx + mov bpState3D,BP_SETTLE + mov bpTime3D,0 + mov bwMonitorClock3D,-1 + ret +BowlingPlayFinishRoll3D ENDP + +BowlingPlayNext3D PROC uses ebx esi + mov esi,bpLane3D + mov eax,bpHitMask3D + not eax + and bwRack3D[esi*4],eax + cmp bwNewRack3D[esi*4],0 + je @F + mov bwRack3D[esi*4],1023 +@@: + cmp bwFrameDone3D[esi*4],0 + je bp_next_ready + xor bwTurn3D[esi*4],1 + lea ebx,[esi*2] + add ebx,bwTurn3D[esi*4] + cmp bwFrames3D[ebx*4],10 + jb bp_next_ready + xor bwTurn3D[esi*4],1 + lea ebx,[esi*2] + add ebx,bwTurn3D[esi*4] + cmp bwFrames3D[ebx*4],10 + jb bp_next_ready + mov bpState3D,BP_FINISHED + ret +bp_next_ready: + call BowlingPlayRack3D + call BowlingPlayStartAim3D + mov bpTime3D,0 + mov bpState3D,BP_DIRECTION + cmp bwTurn3D[esi*4],0 + je @F + mov bpState3D,BP_BOT +@@: + ret +BowlingPlayNext3D ENDP + +HandleBowlingPlay3D PROC uses ebx esi + local click:DWORD,elapsed:DWORD + invoke Pressed3D,9 + test eax,eax + jz @F + xor bpScoreOpen3D,1 +@@: + mov eax,mouseClick3D + mov click,eax + mov mouseClick3D,0 + cmp click,0 + je @F + cmp mouseY3D,224 + jb @F + cmp mouseX3D,265 + jb @F + call LeaveBowlingPlay3D + ret +@@: + invoke Pressed3D,20h + or click,eax + cmp bpScoreOpen3D,0 + jne bp_handle_done + cmp bpState3D,BP_FINISHED + jne bp_handle_clock + cmp click,0 + je bp_handle_done + call ResetBowlingPlay3D + ret +bp_handle_clock: + mov eax,deltaTime3D + cmp eax,250 + jbe @F + mov eax,250 +@@: + mov elapsed,eax + add bpClock3D,eax + add bpTime3D,eax + cmp bpState3D,BP_DIRECTION + jne bp_handle_power + mov eax,bpClock3D + xor edx,edx + mov ecx,4 + div ecx + and eax,511 + mov eax,tableSin[eax*4] + imul eax,6 + cdq + mov ecx,5 + idiv ecx + mov bpAim3D,eax + cmp click,0 + je bp_handle_done + mov bpLockedAim3D,eax + mov bpState3D,BP_POWER + mov bpClock3D,0 + mov bpTime3D,0 + ret +bp_handle_power: + cmp bpState3D,BP_POWER + jne bp_handle_release + mov eax,bpClock3D + xor edx,edx + mov ecx,1600 + div ecx + mov eax,edx + cmp eax,800 + jbe @F + neg eax + add eax,1600 +@@: + shr eax,3 + mov bpPower3D,eax + cmp click,0 + je bp_handle_done + mov bpLockedPower3D,eax + call BowlingPlayLaunch3D + ret +bp_handle_release: + cmp bpState3D,BP_RELEASE + jne bp_handle_roll + mov esi,bpLane3D + mov ecx,350 + cmp bwTurn3D[esi*4],0 + je @F + mov ecx,2400 +@@: + cmp bpTime3D,ecx + jb bp_handle_done + mov bpState3D,BP_ROLL + mov bpTime3D,0 + ret +bp_handle_roll: + cmp bpState3D,BP_ROLL + jne bp_handle_settle + mov eax,elapsed + add bpAccumulator3D,eax +bp_handle_fixed_step: + cmp bpAccumulator3D,16 + jb bp_handle_roll_end + sub bpAccumulator3D,16 + call BowlingPlayStep3D + cmp bpBallY3D,3*FP_1 + jl bp_handle_impact_done + jmp bp_handle_fixed_step +bp_handle_roll_end: + cmp bpBallY3D,3*FP_1 + jl bp_handle_impact_done + cmp bpTime3D,12000 + jb bp_handle_done +bp_handle_impact_done: + call BowlingPlayFinishRoll3D + ret +bp_handle_settle: + cmp bpState3D,BP_SETTLE + jne bp_handle_bot + cmp bpTime3D,1800 + jb bp_handle_done + call BowlingPlayNext3D + ret +bp_handle_bot: + cmp bpState3D,BP_BOT + jne bp_handle_done + cmp bpTime3D,1100 + jb bp_handle_done + mov eax,bwRng3D + imul eax,1664525 + add eax,1013904223 + mov bwRng3D,eax + shr eax,16 + xor edx,edx + mov ecx,101 + div ecx + sub edx,50 + imul eax,edx,FP_1 + cdq + mov ecx,100 + idiv ecx + mov bpLockedAim3D,eax + mov eax,bwRng3D + shr eax,24 + and eax,31 + add eax,60 + mov bpLockedPower3D,eax + call BowlingPlayLaunch3D +bp_handle_done: + ret +HandleBowlingPlay3D ENDP + +include src_graphics\bowling_play_render.inc diff --git a/src_graphics/bowling_play_data.inc b/src_graphics/bowling_play_data.inc new file mode 100644 index 0000000..b691499 --- /dev/null +++ b/src_graphics/bowling_play_data.inc @@ -0,0 +1,80 @@ +MODE_BOWLING equ 30 +INTERACT_BOWL_PLAY equ 56 +BP_DIRECTION equ 0 +BP_POWER equ 1 +BP_RELEASE equ 2 +BP_ROLL equ 3 +BP_SETTLE equ 4 +BP_BOT equ 5 +BP_FINISHED equ 6 +BeginBowlingPlay3D PROTO :DWORD +LeaveBowlingPlay3D PROTO +ResetBowlingPlay3D PROTO +BowlingPlayStartAim3D PROTO +HandleBowlingPlay3D PROTO +RenderBowlingPlay3D PROTO +BowlingPlayRack3D PROTO +BowlingPlayLaunch3D PROTO +BowlingPlayStep3D PROTO +BowlingPlayFinishRoll3D PROTO +BowlingPlayNext3D PROTO +BowlingPlayHit3D PROTO :DWORD,:DWORD,:DWORD +DrawBowlingPlayPin3D PROTO :DWORD,:DWORD +BowlingFrameTotal3D PROTO :DWORD,:DWORD +DrawBowlingPlayScores3D PROTO +InitBowlingBallSurface3D PROTO +DrawBowlingGaugeLine3D PROTO :DWORD,:DWORD,:DWORD,:DWORD +.data +bpActive3D dd 0 +bpLane3D dd 1 +bpTarget3D dd 1 +bpState3D dd BP_DIRECTION +bpClock3D dd 0 +bpTime3D dd 0 +bpAim3D dd 0 +bpPower3D dd 50 +bpLockedAim3D dd 0 +bpLockedPower3D dd 50 +bpLast3D dd 0 +bpLastKind3D dd 0 +bpGutter3D dd 0 +bpAccumulator3D dd 0 +bpScoreOpen3D dd 0 +bpBallX3D dd 0 +bpBallY3D dd 0 +bpBallVX3D dd 0 +bpBallVY3D dd 0 +bpHitMask3D dd 0 +bpPinX3D dd 10 dup(0) +bpPinY3D dd 10 dup(0) +bpPinVX3D dd 10 dup(0) +bpPinVY3D dd 10 dup(0) +bpCentre3D dd 0 +bpHomeX3D dd 0 +bpHomeY3D dd 0 +bpHomeAngle3D dd 0 +bpText3D db 24 dup(0) +bpMonitorTexts3D dd OFFSET bpDirectionText,OFFSET bpPowerText,OFFSET bpRollText,OFFSET bpRollText + dd OFFSET bpResultText,OFFSET bpBot,OFFSET bpScoreText +.data? +bpBallSurface3D dd 64*64 dup(?) +.data +bpPlayPrompt db 200,195,208,192,210,220,32,194,32,193,206,211,203,200,205,195,0 +bpTitle db 193,206,211,203,200,205,195,0 +bpYou db 194,219,0 +bpBot db 204,192,202,209,0 +bpDirectionText db 205,192,207,208,192,194,203,197,205,200,197,0 +bpPowerText db 209,200,203,192,0 +bpRollText db 193,208,206,209,206,202,0 +bpResultText db 209,193,200,210,206,0 +bpWinText db 194,219,32,207,206,193,197,196,200,203,200,0 +bpLoseText db 207,206,193,197,196,200,203,32,204,192,202,209,0 +bpDrawText db 205,200,215,220,223,0 +bpAgainText db 209,205,206,194,192,0 +bpExitText db 194,219,201,210,200,0 +bpFrameText db 212,208,197,201,204,0 +bpScoreText db 209,215,168,210,0 +bpGutterText db 198,197,203,206,193,0 +bpStrikeText db 209,210,208,192,201,202,0 +bpSpareText db 209,207,221,208,0 +.code diff --git a/src_graphics/bowling_play_render.inc b/src_graphics/bowling_play_render.inc new file mode 100644 index 0000000..812d85e --- /dev/null +++ b/src_graphics/bowling_play_render.inc @@ -0,0 +1,459 @@ +; A small native marble surface avoids shipping a second copy of a ball sprite. +InitBowlingBallSurface3D PROC uses ebx esi edi + local stripe:DWORD + xor esi,esi + lea edi,bpBallSurface3D +bp_ball_tex_row: + xor ebx,ebx +bp_ball_tex_pixel: + mov eax,esi + shl eax,3 + and eax,511 + mov eax,tableSin[eax*4] + sar eax,11 + lea eax,[eax+ebx*8] + and eax,511 + mov eax,tableSin[eax*4] + add eax,FP_1 + shr eax,12 + mov stripe,eax + lea edx,[eax+5] + shl edx,16 + lea eax,[eax*2+48] + shl eax,8 + or edx,eax + mov eax,stripe + shl eax,1 + add eax,155 + or eax,edx + or eax,0FF000000h + stosd + inc ebx + cmp ebx,64 + jb bp_ball_tex_pixel + inc esi + cmp esi,64 + jb bp_ball_tex_row + ret +InitBowlingBallSurface3D ENDP + +DrawBowlingPlayPin3D PROC uses ebx esi edi pin:DWORD,centre:DWORD + local x:DWORD,y:DWORD,z:DWORD,headY:DWORD,ry:DWORD,rz:DWORD + mov esi,pin + mov ecx,esi + mov ebx,1 + shl ebx,cl + mov edi,bpLane3D + test bwRack3D[edi*4],ebx + jz bp_pin_done + mov eax,bpPinX3D[esi*4] + add eax,centre + mov x,eax + mov eax,bpPinY3D[esi*4] + mov y,eax + mov headY,eax + mov z,FP_1*15/100 + mov ry,FP_1/10 + mov rz,FP_1*13/100 + test bpHitMask3D,ebx + jz bp_pin_draw + mov z,FP_1*7/100 + mov ry,FP_1*23/100 + mov rz,FP_1*6/100 + sub headY,FP_1/4 +bp_pin_draw: + invoke DrawRoofEllipsoid3D,x,y,z,FP_1/10,ry,rz,239 + mov eax,FP_1*34/100 + test bpHitMask3D,ebx + jz @F + mov eax,FP_1*7/100 +@@: + invoke DrawRoofEllipsoid3D,x,headY,eax,FP_1*65/1000,FP_1*65/1000,FP_1*6/100,239 + mov eax,z + add eax,rz + invoke DrawRoofEllipsoid3D,x,headY,eax,FP_1*45/1000,FP_1*45/1000,FP_1*25/1000,240 +bp_pin_done: + ret +DrawBowlingPlayPin3D ENDP + +; A frame's cumulative score stays unresolved until all its bonuses are known. +BowlingFrameTotal3D PROC uses ebx esi edi owner:DWORD,wanted:DWORD + local total:DWORD,index:DWORD,available:DWORD,current:DWORD + mov ebx,owner + mov eax,bwRollCount3D[ebx*4] + mov available,eax + imul ebx,96 + lea esi,bwRolls3D[ebx] + mov index,0 + mov current,0 + mov total,0 +bp_frame_loop: + mov edi,index + cmp edi,available + jae bp_frame_pending + mov eax,[esi+edi*4] + mov ebx,eax + inc edi + cmp edi,available + jae bp_frame_pending + add eax,[esi+edi*4] + inc edi + cmp ebx,10 + je bp_frame_bonus + cmp eax,10 + jne bp_frame_open +bp_frame_bonus: + cmp edi,available + jae bp_frame_pending + add eax,[esi+edi*4] +bp_frame_open: + add total,eax + cmp ebx,10 + jne @F + dec edi +@@: + mov index,edi + mov eax,current + cmp eax,wanted + je bp_frame_ready + inc current + jmp bp_frame_loop +bp_frame_ready: + mov eax,total + ret +bp_frame_pending: + mov eax,-1 + ret +BowlingFrameTotal3D ENDP + +DrawBowlingPlayScores3D PROC uses ebx esi edi + local x:DWORD,y:DWORD,owner:DWORD + invoke DrawBoxBorder,4,47,312,139,00151F26h,005BC9BEh,1 + invoke DrawString8x8,12,55,OFFSET bpScoreText,00E8D7ADh + xor esi,esi +bp_score_heading: + imul eax,esi,26 + add eax,44 + mov x,eax + lea eax,[esi+1] + invoke FormatNumber,eax,OFFSET bpText3D + invoke DrawString8x8,x,73,OFFSET bpText3D,00AABDB6h + inc esi + cmp esi,10 + jb bp_score_heading + xor edi,edi +bp_score_owner: + lea eax,[edi*2] + imul eax,18 + add eax,96 + mov y,eax + mov eax,OFFSET bpYou + test edi,edi + jz @F + mov eax,OFFSET bpBot +@@: + invoke DrawString8x8,9,y,eax,005BC9BEh + mov eax,bpLane3D + shl eax,1 + add eax,edi + mov owner,eax + xor esi,esi +bp_score_cell: + imul eax,esi,26 + add eax,44 + mov x,eax + invoke BowlingFrameTotal3D,owner,esi + cmp eax,-1 + je bp_score_empty + invoke FormatNumber,eax,OFFSET bpText3D + invoke DrawString8x8,x,y,OFFSET bpText3D,00FFFFFFh + jmp bp_score_next +bp_score_empty: + invoke DrawBox,x,y,8,1,005C727Bh +bp_score_next: + inc esi + cmp esi,10 + jb bp_score_cell + inc edi + cmp edi,2 + jb bp_score_owner + ret +DrawBowlingPlayScores3D ENDP + +DrawBowlingGaugeLine3D PROC uses ebx esi edi x0:DWORD,y0:DWORD,x1:DWORD,y1:DWORD + local px:DWORD,py:DWORD,stepX:DWORD,stepY:DWORD + mov eax,x1 + sub eax,x0 + cdq + xor eax,edx + sub eax,edx + mov ebx,eax + mov eax,y1 + sub eax,y0 + cdq + xor eax,edx + sub eax,edx + cmp eax,ebx + jbe @F + mov ebx,eax +@@: + test ebx,ebx + jnz @F + inc ebx +@@: + mov eax,x1 + sub eax,x0 + shl eax,16 + cdq + idiv ebx + mov stepX,eax + mov eax,y1 + sub eax,y0 + shl eax,16 + cdq + idiv ebx + mov stepY,eax + mov eax,x0 + shl eax,16 + mov px,eax + mov eax,y0 + shl eax,16 + mov py,eax + inc ebx +bp_gauge_line: + mov eax,px + sar eax,16 + mov edx,py + sar edx,16 + invoke DrawBox,eax,edx,1,1,00D8F5B7h + mov eax,stepX + add px,eax + mov eax,stepY + add py,eax + dec ebx + jnz bp_gauge_line + ret +DrawBowlingGaugeLine3D ENDP + +RenderBowlingPlay3D PROC uses ebx esi edi + local x:DWORD,y:DWORD,color:DWORD,owner:DWORD,startY:DWORD + mov eax,bpCentre3D + mov playerX,eax + mov startY,FP_1*252/10 + mov esi,bpLane3D + cmp bwTurn3D[esi*4],0 + je @F + mov startY,FP_1*264/10 +@@: + mov eax,startY + mov playerY,eax + mov playerAngle,384 + call BowlingEye3D + cmp bpState3D,BP_ROLL + je bp_camera_follow + cmp bpState3D,BP_SETTLE + jne bp_camera_ready +bp_camera_follow: + mov eax,bpBallY3D + add eax,FP_1*27/10 + cmp eax,7*FP_1 + jge @F + mov eax,7*FP_1 +@@: + mov playerY,eax + mov viewEye3D,FP_1*7/10 + cmp bpState3D,BP_ROLL + jne bp_camera_ready + mov ebx,bpTime3D + cmp ebx,500 + jae bp_camera_ready + sub eax,startY + imul eax,ebx + cdq + mov ecx,500 + idiv ecx + add eax,startY + mov playerY,eax + imul eax,ebx,FP_1*7/20 + cdq + idiv ecx + mov edx,HALL_STANDING_EYE + sub edx,eax + mov viewEye3D,edx +bp_camera_ready: + call UpdateCamera3D + call RenderBowling3D + mov esi,bpLane3D + cmp bwTurn3D[esi*4],1 + jne bp_render_ball + cmp bpState3D,BP_BOT + je bp_render_bot_ready + cmp bpState3D,BP_RELEASE + je bp_render_bot_throw + cmp bpState3D,BP_ROLL + jne bp_render_ball + invoke ConfigureCharacter3D,OFFSET bwActor3D,6,CAST_THROW,2300,0,-FP_1 + jmp bp_render_bot_draw +bp_render_bot_throw: + invoke ConfigureCharacter3D,OFFSET bwActor3D,6,CAST_THROW,bpTime3D,0,-FP_1 + jmp bp_render_bot_draw +bp_render_bot_ready: + invoke ConfigureCharacter3D,OFFSET bwActor3D,6,CAST_IDLE,bpTime3D,0,0 +bp_render_bot_draw: + mov eax,bpCentre3D + add eax,FP_1*38/100 + mov bwActor3D.posX,eax + mov bwActor3D.posY,FP_1*235/10 + invoke DrawCityObject3D,OFFSET bwActor3D,0 +bp_render_ball: + cmp bpState3D,BP_FINISHED + je bp_render_hud + mov eax,bpBallX3D + add eax,bpCentre3D + invoke DrawBowlingBall3D,eax,bpBallY3D,253 +bp_render_hud: + invoke DrawBox,0,0,320,17,00121B20h + invoke DrawString8x8,8,4,OFFSET bpTitle,00E8D7ADh + mov eax,bpLane3D + shl eax,1 + mov owner,eax + mov esi,eax + invoke DrawString8x8,100,4,OFFSET bpYou,005BC9BEh + invoke FormatNumber,bwScores3D[esi*4],OFFSET bpText3D + invoke DrawString8x8,126,4,OFFSET bpText3D,00FFFFFFh + invoke DrawString8x8,205,4,OFFSET bpBot,00E8D7ADh + invoke FormatNumber,bwScores3D[esi*4+4],OFFSET bpText3D + invoke DrawString8x8,248,4,OFFSET bpText3D,00FFFFFFh + invoke DrawBox,0,221,320,19,00121B20h + invoke DrawString8x8,268,227,OFFSET bpExitText,00E8D7ADh + mov esi,bpLane3D + mov eax,bwTurn3D[esi*4] + add owner,eax + mov eax,OFFSET bpYou + cmp bwTurn3D[esi*4],0 + je @F + mov eax,OFFSET bpBot +@@: + invoke DrawString8x8,8,227,eax,005BC9BEh + mov ebx,owner + mov eax,bwFrames3D[ebx*4] + cmp bpState3D,BP_SETTLE + jne @F + cmp bwFrameDone3D[esi*4],0 + je @F + dec eax +@@: + inc eax + cmp eax,10 + jbe @F + mov eax,10 +@@: + invoke FormatNumber,eax,OFFSET bpText3D + invoke DrawString8x8,60,227,OFFSET bpFrameText,00AABDB6h + invoke DrawString8x8,116,227,OFFSET bpText3D,00FFFFFFh + cmp bpState3D,BP_DIRECTION + je bp_render_direction + cmp bpState3D,BP_POWER + je bp_render_power + cmp bpState3D,BP_SETTLE + je bp_render_result + cmp bpState3D,BP_FINISHED + je bp_render_finished + invoke DrawString8x8,8,204,OFFSET bpRollText,00E8D7ADh + jmp bp_render_done +bp_render_direction: + invoke DrawString8x8,108,145,OFFSET bpDirectionText,00E8D7ADh + xor esi,esi +bp_render_arc: + lea eax,[esi-70] + imul eax,eax + xor edx,edx + mov ecx,380 + div ecx + add eax,169 + mov y,eax + mov color,00D67057h + cmp esi,27 + jb @F + cmp esi,113 + ja @F + mov color,00E7C455h + cmp esi,59 + jb @F + cmp esi,81 + ja @F + mov color,005BC98Ch +@@: + lea eax,[esi+90] + invoke DrawBox,eax,y,1,6,color + inc esi + cmp esi,141 + jb bp_render_arc + mov eax,bpAim3D + imul eax,52 + sar eax,16 + add eax,160 + mov x,eax + invoke DrawBowlingGaugeLine3D,160,205,eax,162 + mov eax,x + lea ebx,[eax-5] + invoke DrawBowlingGaugeLine3D,eax,162,ebx,168 + mov eax,x + lea ebx,[eax+5] + invoke DrawBowlingGaugeLine3D,eax,162,ebx,168 + jmp bp_render_done +bp_render_power: + invoke DrawString8x8,246,62,OFFSET bpPowerText,00E8D7ADh + invoke DrawBoxBorder,276,79,13,124,00121B20h,00AABDB6h,1 + invoke DrawBox,278,82,9,118,00D67057h + invoke DrawBox,278,128,9,38,00E7C455h + invoke DrawBox,278,82,9,46,005BC98Ch + mov eax,bpPower3D + imul eax,116 + xor edx,edx + mov ecx,100 + div ecx + mov edx,198 + sub edx,eax + mov y,edx + invoke DrawBox,270,y,25,2,00FFFFFFh + invoke FormatNumber,bpPower3D,OFFSET bpText3D + invoke DrawString8x8,246,180,OFFSET bpText3D,00FFFFFFh + jmp bp_render_done +bp_render_result: + mov eax,OFFSET bpResultText + cmp bpGutter3D,0 + je @F + mov eax,OFFSET bpGutterText +@@: + cmp bpLastKind3D,1 + jne @F + mov eax,OFFSET bpStrikeText +@@: + cmp bpLastKind3D,2 + jne @F + mov eax,OFFSET bpSpareText +@@: + invoke DrawString8x8,104,197,eax,00E8D7ADh + invoke FormatNumber,bpLast3D,OFFSET bpText3D + invoke DrawString8x8,192,197,OFFSET bpText3D,00FFFFFFh + jmp bp_render_done +bp_render_finished: + mov eax,bwScores3D[esi*8] + mov ebx,OFFSET bpDrawText + cmp eax,bwScores3D[esi*8+4] + je @F + mov ebx,OFFSET bpWinText + ja @F + mov ebx,OFFSET bpLoseText +@@: + invoke DrawBoxBorder,72,150,176,58,00121B20h,005BC9BEh,1 + invoke DrawString8x8,88,161,ebx,00E8D7ADh + invoke DrawString8x8,136,187,OFFSET bpAgainText,00FFFFFFh +bp_render_done: + cmp bpScoreOpen3D,0 + je @F + call DrawBowlingPlayScores3D +@@: + ret +RenderBowlingPlay3D ENDP diff --git a/src_graphics/bowling_render.inc b/src_graphics/bowling_render.inc index 532a854..0d5f196 100644 --- a/src_graphics/bowling_render.inc +++ b/src_graphics/bowling_render.inc @@ -158,7 +158,40 @@ bw_planes_done: RenderBowlingPlanes3D ENDP DrawBowlingBall3D PROC uses ebx esi x:DWORD,y:DWORD,mat:DWORD + local holeY:DWORD,holeZ:DWORD,phase:DWORD invoke DrawRoofEllipsoid3D,x,y,FP_1*135/1000,FP_1*12/100,FP_1*12/100,FP_1*12/100,mat + cmp mat,253 + jne bw_ball_done + mov eax,y + sar eax,8 + and eax,511 + mov phase,eax + mov eax,tableCos[eax*4] + test eax,eax + jle bw_ball_done + imul eax,11 + cdq + mov ecx,100 + idiv ecx + add eax,y + mov holeY,eax + mov eax,phase + mov eax,tableSin[eax*4] + imul eax,11 + cdq + idiv ecx + add eax,FP_1*135/1000 + mov holeZ,eax + mov eax,x + sub eax,FP_1*35/1000 + invoke DrawRoofEllipsoid3D,eax,holeY,holeZ,FP_1*17/1000,FP_1*17/1000,FP_1*17/1000,233 + mov eax,x + add eax,FP_1*35/1000 + invoke DrawRoofEllipsoid3D,eax,holeY,holeZ,FP_1*17/1000,FP_1*17/1000,FP_1*17/1000,233 + mov eax,holeZ + sub eax,FP_1*38/1000 + invoke DrawRoofEllipsoid3D,x,holeY,eax,FP_1*18/1000,FP_1*18/1000,FP_1*18/1000,233 +bw_ball_done: ret DrawBowlingBall3D ENDP @@ -166,6 +199,13 @@ DrawBowlingPin3D PROC uses ebx esi edi lane:DWORD,pin:DWORD,centre:DWORD local x:DWORD,y:DWORD,t:DWORD,z:DWORD,ry:DWORD,rz:DWORD,headY:DWORD,headZ:DWORD mov esi,lane mov edi,pin + cmp bpActive3D,0 + je @F + cmp esi,bpLane3D + jne @F + invoke DrawBowlingPlayPin3D,edi,centre + ret +@@: mov eax,bwPinX3D[edi*4] add eax,centre mov x,eax @@ -263,6 +303,8 @@ bw_render_pin: jb bw_render_pin cmp bwBusy3D[esi*4],0 je bw_render_lane_next + cmp bwBusy3D[esi*4],2 + je bw_render_lane_next mov time,-1 cmp bwState3D[esi*4],2 jne @F @@ -347,8 +389,8 @@ RenderBowlingVisitors3D PROC uses ebx esi edi local action:DWORD,clock:DWORD,vx:DWORD,vy:DWORD xor esi,esi bw_cast_lane: - cmp bwBusy3D[esi*4],0 - je bw_cast_lane_next + cmp bwBusy3D[esi*4],1 + jne bw_cast_lane_next imul eax,esi,BW_LANE_STEP add eax,3*FP_1 mov centre,eax @@ -483,6 +525,12 @@ bw_cast_lane_next: ; Four other visitors occupy the dining/bar area; ten visitors, no body doubles. xor esi,esi bw_cast_dining: + ; The match opponent leaves this restaurant seat instead of appearing twice. + cmp bpActive3D,0 + je @F + test esi,esi + jz bw_cast_dining_next +@@: mov eax,bwVisitorX3D[esi*4] mov bwSmallActor3D.posX,eax mov eax,bwVisitorY3D[esi*4] @@ -494,6 +542,7 @@ bw_cast_dining: add bwSmallActor3D.groundZ,FP_1/4 @@: invoke DrawCityObject3D,OFFSET bwSmallActor3D,0 +bw_cast_dining_next: inc esi cmp esi,4 jb bw_cast_dining diff --git a/src_graphics/bowling_simulation.inc b/src_graphics/bowling_simulation.inc index 96b15f6..e8f5c85 100644 --- a/src_graphics/bowling_simulation.inc +++ b/src_graphics/bowling_simulation.inc @@ -191,8 +191,8 @@ bw_update_stand: bw_update_lanes: xor esi,esi bw_update_lane: - cmp bwBusy3D[esi*4],0 - je bw_update_next + cmp bwBusy3D[esi*4],1 + jne bw_update_next mov eax,deltaTime3D add bwTime3D[esi*4],eax mov eax,bwState3D[esi*4] @@ -372,8 +372,15 @@ bw_monitor_lane: invoke BowlingMonitorText3D,target,36,30,OFFSET bwFreeText3D,0FF55D9CFh jmp bw_monitor_advance bw_monitor_scores: + cmp bwBusy3D[esi*4],2 + jne bw_monitor_visitors + invoke BowlingMonitorText3D,target,8,21,OFFSET bpYou,0FFFF9F89h + invoke BowlingMonitorText3D,target,70,21,OFFSET bpBot,0FF55D9CFh + jmp bw_monitor_values +bw_monitor_visitors: invoke BowlingMonitorText3D,target,8,21,OFFSET bwPlayerA3D,0FFFF9F89h invoke BowlingMonitorText3D,target,70,21,OFFSET bwPlayerB3D,0FF55D9CFh +bw_monitor_values: lea ebx,[esi*8] invoke FormatNumber,bwScores3D[ebx],ADDR number invoke BowlingMonitorText3D,target,8,33,ADDR number,0FFFFFFFFh @@ -381,6 +388,11 @@ bw_monitor_scores: invoke BowlingMonitorText3D,target,70,33,ADDR number,0FFFFFFFFh mov eax,bwState3D[esi*4] mov eax,bwPhaseTexts3D[eax*4] + cmp bwBusy3D[esi*4],2 + jne @F + mov eax,bpState3D + mov eax,bpMonitorTexts3D[eax*4] +@@: invoke BowlingMonitorText3D,target,8,51,eax,0FFFFBF70h bw_monitor_advance: inc esi diff --git a/src_graphics/character_cast.inc b/src_graphics/character_cast.inc index 641f7d5..f20f5fb 100644 --- a/src_graphics/character_cast.inc +++ b/src_graphics/character_cast.inc @@ -166,5 +166,23 @@ ConfigureRoofVisitor3D PROC uses ebx esi edi slot:DWORD mov vy,-FP_1 cast_roof_ready: invoke ConfigureCharacter3D,OFFSET roofActor3D,roofGuestIdentity3D[esi*4],phase,clock,vx,vy + ; The terrace uses a taller world scale than the hall; keep faces at eye level. + mov eax,CAST_HEIGHT + mov ecx,ROOF_STANDING_EYE + imul ecx + mov ecx,HALL_STANDING_EYE + idiv ecx + mov roofActor3D.worldH,eax + imul eax,192 + mov ecx,224 + cdq + idiv ecx + mov roofActor3D.worldW,eax + mov eax,roofActor3D.worldH + neg eax + cdq + mov ecx,56 + idiv ecx + mov roofActor3D.groundZ,eax ret ConfigureRoofVisitor3D ENDP diff --git a/src_graphics/city_lighting.inc b/src_graphics/city_lighting.inc index f578e86..2586c77 100644 --- a/src_graphics/city_lighting.inc +++ b/src_graphics/city_lighting.inc @@ -199,7 +199,7 @@ ShadeCityGround3D ENDP IsCityEmissive3D PROC uses ebx rgb:DWORD,material:DWORD cmp material,242 jb @F - cmp material,BW_LAST_MATERIAL + cmp material,252 ja @F mov eax,1 ret diff --git a/tools/tests/bowling_play_test.inc b/tools/tests/bowling_play_test.inc new file mode 100644 index 0000000..f389026 --- /dev/null +++ b/tools/tests/bowling_play_test.inc @@ -0,0 +1,266 @@ +.data +bpTestDirection db "build\hall-expansion\bowling-play-direction.bmp",0 +bpTestDirectionLeft db "build\hall-expansion\bowling-play-direction-left.bmp",0 +bpTestPower db "build\hall-expansion\bowling-play-power.bmp",0 +bpTestPowerLow db "build\hall-expansion\bowling-play-power-low.bmp",0 +bpTestOpponent db "build\hall-expansion\bowling-play-opponent.bmp",0 +bpTestRoll db "build\hall-expansion\bowling-play-roll.bmp",0 +bpTestScores db "build\hall-expansion\bowling-play-scores.bmp",0 +.code +BowlingPlayNativeTests PROC uses ebx esi edi + local bank:DWORD,savedRng:DWORD,savedHits:DWORD,remainingSteps:DWORD + mov expansionStage,150 + mov eax,bankroll + mov bank,eax + call InitBowling3D + mov hallLevel3D,HALL_BOWLING + mov engineMode,MODE_WALK + mov playerX,5*FP_1 + mov playerY,25*FP_1 + mov playerAngle,384 + invoke BeginBowlingPlay3D,0 + test eax,eax + jnz bp_test_fail + invoke BeginBowlingPlay3D,1 + cmp eax,1 + jne bp_test_fail + cmp engineMode,MODE_BOWLING + jne bp_test_fail + cmp bwBusy3D[4],2 + jne bp_test_fail + mov expansionStage,151 + mov deltaTime3D,0 + mov bpClock3D,0 + mov mouseX3D,160 + mov mouseY3D,120 + mov viewWidth3D,640 + mov viewHeight3D,480 + mov viewLeft3D,0 + mov viewTop3D,0 + invoke WindowEvents3D,0,201h,1,00F00140h + call HandleBowlingPlay3D + cmp bpState3D,BP_POWER + jne bp_test_fail + invoke WindowEvents3D,0,100h,20h,40000000h + call HandleBowlingPlay3D + cmp bpState3D,BP_POWER + jne bp_test_fail + cmp bpLockedAim3D,0 + jne bp_test_fail + call HandleBowlingPlay3D + cmp bpState3D,BP_POWER + jne bp_test_fail + mov bpClock3D,560 + invoke WindowEvents3D,0,100h,20h,0 + call HandleBowlingPlay3D + cmp bpState3D,BP_RELEASE + jne bp_test_fail + cmp bpLockedPower3D,70 + jne bp_test_fail + mov eax,bwRng3D + mov savedRng,eax + mov deltaTime3D,16 + mov remainingSteps,1100 +bp_test_roll: + call HandleBowlingPlay3D + cmp bpState3D,BP_SETTLE + je bp_test_roll_done + dec remainingSteps + jnz bp_test_roll + jmp bp_test_fail +bp_test_roll_done: + mov eax,bwRng3D + cmp eax,savedRng + jne bp_test_fail + cmp bpLast3D,1 + jb bp_test_fail + cmp bpLast3D,10 + ja bp_test_fail + mov eax,bpHitMask3D + mov savedHits,eax + ; The complete fixed-step shot is identical at 16 and 32-ms frame deltas. + call ResetBowlingPlay3D + mov bpLockedAim3D,0 + mov bpLockedPower3D,70 + call BowlingPlayLaunch3D + mov deltaTime3D,32 + mov remainingSteps,600 +bp_test_rate32: + call HandleBowlingPlay3D + cmp bpState3D,BP_SETTLE + je bp_test_rate32_done + dec remainingSteps + jnz bp_test_rate32 + jmp bp_test_fail +bp_test_rate32_done: + mov eax,bpHitMask3D + cmp eax,savedHits + jne bp_test_fail + mov expansionStage,152 + call ResetBowlingPlay3D + mov bpLockedAim3D,0 + mov bpLockedPower3D,70 + call BowlingPlayLaunch3D + mov esi,600 +bp_test_repeat: + call BowlingPlayStep3D + dec esi + jnz bp_test_repeat + mov eax,bpHitMask3D + cmp eax,savedHits + jne bp_test_fail + call ResetBowlingPlay3D + mov bpLockedAim3D,2*FP_1 + mov bpLockedPower3D,70 + call BowlingPlayLaunch3D + mov esi,600 +bp_test_gutter: + call BowlingPlayStep3D + dec esi + jnz bp_test_gutter + cmp bpGutter3D,1 + jne bp_test_fail + cmp bpHitMask3D,0 + jne bp_test_fail + ; A removed pin can never be credited again. + call ResetBowlingPlay3D + mov bwRack3D[4],0 + mov bpLockedAim3D,0 + mov bpLockedPower3D,70 + call BowlingPlayLaunch3D + mov esi,600 +bp_test_empty_rack: + call BowlingPlayStep3D + dec esi + jnz bp_test_empty_rack + cmp bpHitMask3D,0 + jne bp_test_fail + mov expansionStage,153 + call ResetBowlingPlay3D + mov bpHitMask3D,1 + call BowlingPlayFinishRoll3D + call BowlingPlayNext3D + cmp bwRack3D[4],1022 + jne bp_test_fail + cmp bwTurn3D[4],0 + jne bp_test_fail + mov bpHitMask3D,1022 + call BowlingPlayFinishRoll3D + call BowlingPlayNext3D + cmp bwRack3D[4],1023 + jne bp_test_fail + cmp bwTurn3D[4],1 + jne bp_test_fail + cmp bpState3D,BP_BOT + jne bp_test_fail + cmp bpLastKind3D,2 + jne bp_test_fail + mov deltaTime3D,250 + mov esi,5 +bp_test_bot: + call HandleBowlingPlay3D + dec esi + jnz bp_test_bot + cmp bpState3D,BP_RELEASE + jne bp_test_fail + mov expansionStage,154 + call ResetBowlingPlay3D + mov bpHitMask3D,1023 + call BowlingPlayFinishRoll3D + invoke BowlingFrameTotal3D,2,0 + cmp eax,-1 + jne bp_test_fail + call BowlingPlayNext3D + mov esi,23 +bp_test_perfect: + mov bpHitMask3D,1023 + call BowlingPlayFinishRoll3D + call BowlingPlayNext3D + dec esi + jnz bp_test_perfect + cmp bpState3D,BP_FINISHED + jne bp_test_fail + cmp bwScores3D[8],300 + jne bp_test_fail + cmp bwScores3D[12],300 + jne bp_test_fail + invoke BowlingFrameTotal3D,2,9 + cmp eax,300 + jne bp_test_fail + mov expansionStage,155 + call ResetBowlingPlay3D + mov deltaTime3D,0 + mov bpClock3D,150 + call HandleBowlingPlay3D + call RenderBowlingPlay3D + invoke ExpansionSnapshot,OFFSET bpTestDirection + mov bpClock3D,1550 + call HandleBowlingPlay3D + call RenderBowlingPlay3D + invoke ExpansionSnapshot,OFFSET bpTestDirectionLeft + mov bpState3D,BP_POWER + mov bpClock3D,560 + call HandleBowlingPlay3D + call RenderBowlingPlay3D + invoke ExpansionSnapshot,OFFSET bpTestPower + mov bpClock3D,200 + call HandleBowlingPlay3D + call RenderBowlingPlay3D + invoke ExpansionSnapshot,OFFSET bpTestPowerLow + mov bpScoreOpen3D,1 + call RenderBowlingPlay3D + invoke ExpansionSnapshot,OFFSET bpTestScores + mov bpScoreOpen3D,0 + mov bpLockedAim3D,0 + mov bpLockedPower3D,70 + call BowlingPlayLaunch3D + mov bpState3D,BP_ROLL + mov esi,160 +bp_test_flight: + call BowlingPlayStep3D + dec esi + jnz bp_test_flight + mov bpTime3D,2560 + call RenderBowlingPlay3D + invoke ExpansionSnapshot,OFFSET bpTestRoll + mov eax,bpBallY3D + mov savedHits,eax + mov pressedKeys3D[9],1 + mov deltaTime3D,16 + call HandleBowlingPlay3D + call HandleBowlingPlay3D + mov eax,bpBallY3D + cmp eax,savedHits + jne bp_test_fail + mov pressedKeys3D[9],1 + call HandleBowlingPlay3D + mov eax,bpBallY3D + cmp eax,savedHits + je bp_test_fail + mov bwTurn3D[4],1 + mov bpState3D,BP_RELEASE + mov bpTime3D,1700 + call RenderBowlingPlay3D + invoke ExpansionSnapshot,OFFSET bpTestOpponent + mov expansionStage,156 + call LeaveBowlingPlay3D + cmp engineMode,MODE_WALK + jne bp_test_fail + cmp bwBusy3D[4],0 + jne bp_test_fail + cmp bpActive3D,0 + jne bp_test_fail + cmp playerX,5*FP_1 + jne bp_test_fail + cmp playerY,25*FP_1 + jne bp_test_fail + cmp playerAngle,384 + jne bp_test_fail + mov eax,bankroll + cmp eax,bank + jne bp_test_fail + ret +bp_test_fail: + invoke ExitProcess,expansionStage + ret +BowlingPlayNativeTests ENDP diff --git a/tools/tests/bowling_play_ui.inc b/tools/tests/bowling_play_ui.inc new file mode 100644 index 0000000..52d6f3f --- /dev/null +++ b/tools/tests/bowling_play_ui.inc @@ -0,0 +1,73 @@ +; Interactive fixture: production MASM scene and events, no profile writes. +.code +BowlingUiStart PROC + call InitPaths3D + call Create3DWindow + test eax,eax + jz bp_ui_fail + invoke SetWindowLongA,h3DWnd,-4,OFFSET WindowEvents3D + call InitBowling3D + call LoadBowling3D + mov hallLevel3D,HALL_BOWLING + mov worldMap3D,OFFSET bwMap3D + mov playerX,FP_1*49/10 + mov playerY,25*FP_1 + mov playerAngle,384 + invoke BeginBowlingPlay3D,1 + call RenderBowlingPlay3D + invoke ShowWindow,h3DWnd,1 + call Present3D + invoke SetForegroundWindow,h3DWnd + invoke GetTickCount + mov previousTick3D,eax +bp_ui_loop: + invoke PeekMessageA,OFFSET msg3D,0,0,0,1 + test eax,eax + jz bp_ui_frame + cmp DWORD PTR msg3D[4],12h + je bp_ui_close + invoke TranslateMessage,OFFSET msg3D + invoke DispatchMessageA,OFFSET msg3D + jmp bp_ui_loop +bp_ui_frame: + invoke GetTickCount + mov frameStart3D,eax + sub eax,previousTick3D + cmp eax,100 + jbe @F + mov eax,100 +@@: + mov deltaTime3D,eax + mov eax,frameStart3D + mov previousTick3D,eax + invoke GetForegroundWindow + cmp eax,h3DWnd + jne bp_ui_idle + invoke Pressed3D,1Bh + test eax,eax + jnz bp_ui_close + mov eax,deltaTime3D + add animationClock3D,eax + call HandleBowlingPlay3D + cmp engineMode,MODE_BOWLING + jne bp_ui_mode_error + call RenderBowlingPlay3D + call Present3D +bp_ui_idle: + invoke Sleep,8 + jmp bp_ui_loop +bp_ui_close: + call LeaveBowlingPlay3D + call FreeBowling3D + call FreeCharacterCast3D + invoke ReleaseDC,h3DWnd,h3DDC + invoke DestroyWindow,h3DWnd + invoke ExitProcess,0 + ret +bp_ui_fail: + invoke ExitProcess,157 + ret +bp_ui_mode_error: + invoke ExitProcess,158 + ret +BowlingUiStart ENDP diff --git a/tools/tests/bowling_test.inc b/tools/tests/bowling_test.inc index a48d482..dccec0f 100644 --- a/tools/tests/bowling_test.inc +++ b/tools/tests/bowling_test.inc @@ -233,8 +233,10 @@ bw_test_bar_visits: mov engineMode,MODE_WALK call ClearKeys3D call CharacterCastNativeTests + call BowlingPlayNativeTests ret bw_test_fail: invoke ExitProcess,expansionStage ret BowlingNativeTests ENDP +include tools/tests/bowling_play_test.inc diff --git a/tools/tests/character_cast_test.inc b/tools/tests/character_cast_test.inc index 1a22a87..09a454a 100644 --- a/tools/tests/character_cast_test.inc +++ b/tools/tests/character_cast_test.inc @@ -260,8 +260,11 @@ cast_test_walking: mov phase,0 cmp esi,CAST_COUNT jb cast_test_identity + call RoofCharacterScaleNativeTests ret cast_test_fail: invoke ExitProcess,expansionStage ret CharacterCastNativeTests ENDP + +include tools/tests/roof_character_scale_test.inc diff --git a/tools/tests/check_character_cast.py b/tools/tests/check_character_cast.py index 42115f7..31aa1e5 100644 --- a/tools/tests/check_character_cast.py +++ b/tools/tests/check_character_cast.py @@ -56,6 +56,12 @@ def check(): walking[0].save(OUT/f'cast-walk-{identity}.gif',save_all=True, append_images=walking[1:],duration=110,loop=0) roster.save(OUT/'cast-native-roster.png') + for visitor in range(8): + native = Image.open(OUT/f'roof-cast-{visitor}.bmp').convert('RGB') + box = ImageChops.difference(native, Image.new('RGB', native.size, (28,34,40))).getbbox() + assert box and 175 <= box[3]-box[1] <= 270 and 195 <= box[1] <= 250,('roof visitor scale',visitor,box) + native.save(OUT/f'roof-cast-{visitor}.png') + print('PASS: terrace visitor proportions and grounded feet match the taller rooftop camera') print('PASS: ten identities, contact/passing gait, eye-level scale, 60 poses and 80 native walking frames') diff --git a/tools/tests/check_hall_expansion.py b/tools/tests/check_hall_expansion.py index 78c1b31..9728d75 100644 --- a/tools/tests/check_hall_expansion.py +++ b/tools/tests/check_hall_expansion.py @@ -246,6 +246,13 @@ def check_bowling_room(): throws = [Image.open(OUT/f"bowling-throw-{i}.bmp").tobytes() for i in range(4)] assert len(set(throws)) == 4,"whole-body throw must visibly progress" assert len(captures) >= 10 + for a,b,region in ( + ("direction","direction-left",(180,314,462,414)), + ("power","power-low",(536,154,596,408)), + ): + images = [Image.open(OUT/f"bowling-play-{name}.bmp").convert("RGB").crop(region) for name in (a,b)] + assert ImageChops.difference(*images).getbbox(), (a,b,"timing indicator must move") + print("PASS: playable bowling gates, collision solver, frame-rate invariance, remaining racks, bonus balls, opponent and score sheet") print(f"PASS: eight bowling lanes, {len(reached)} connected walk points, reachable bar/dining/seats, native throw and room captures") diff --git a/tools/tests/playtest_bowling.py b/tools/tests/playtest_bowling.py new file mode 100644 index 0000000..6c660b6 --- /dev/null +++ b/tools/tests/playtest_bowling.py @@ -0,0 +1,35 @@ +"""Open the production native bowling scene without accessing player profiles.""" +import hashlib +import subprocess +from pathlib import Path +from check_hall_expansion import ROOT, OUT, prepare +from check_artifacts import toolchain + + +def main(): + def profile(): + return {str(p): hashlib.sha256(p.read_bytes()).digest() + for p in (ROOT/'runtime').rglob('*') if p.is_file()} + prepare() + source = (OUT/'module.asm').read_text(encoding='cp1251') + source = source.replace('end ExpansionTestStart', + 'include tools/tests/bowling_play_ui.inc\nend BowlingUiStart') + target = OUT/'bowling-ui' + target.with_suffix('.asm').write_text(source, encoding='cp1251') + ml,link = toolchain() + sdk = sorted(Path('C:/Program Files (x86)/Windows Kits/10/Lib').glob('*/um/x86'))[-1] + subprocess.run([str(ml),'/c','/coff','/Cp','/nologo','/I.', + f'/Fo{target}.obj',f'{target}.asm'],cwd=ROOT,check=True) + subprocess.run([str(link),'/nologo','/SUBSYSTEM:WINDOWS','/ENTRY:BowlingUiStart@0', + f'/LIBPATH:{sdk}',f'/OUT:{target}.exe',f'{target}.obj', + str(OUT/'helpers.obj'),'kernel32.lib','user32.lib','gdi32.lib', + 'winmm.lib','ws2_32.lib'],cwd=ROOT,check=True) + before = profile() + result = subprocess.run([str(target.with_suffix('.exe'))],cwd=ROOT,timeout=900) + assert result.returncode == 0,result.returncode + assert profile() == before,'Interactive bowling changed the player profile' + print('PASS: interactive native bowling closed without changing the player profile') + + +if __name__ == '__main__': + main() diff --git a/tools/tests/release_smoke_check.ps1 b/tools/tests/release_smoke_check.ps1 index a118248..2f4df15 100644 --- a/tools/tests/release_smoke_check.ps1 +++ b/tools/tests/release_smoke_check.ps1 @@ -71,10 +71,10 @@ try { $asmText = $cp1251.GetString($asmBytes) foreach ($needle in @( 'SAVE_SIGN_SIZE equ 276', - 'SAVE_FILE_SIZE equ 280', + 'SAVE_FILE_SIZE equ 312', 'CalcSaveSignature PROC', 'mov DWORD PTR [saveBuffer+SAVE_SIGN_SIZE], eax', - 'cmp charsRead, SAVE_FILE_SIZE', + 'cmp charsRead,SAVE_FILE_SIZE', 'includelib ws2_32.lib' )) { if ($asmText.IndexOf($needle) -lt 0) { diff --git a/tools/tests/roof_character_scale_test.inc b/tools/tests/roof_character_scale_test.inc new file mode 100644 index 0000000..cf1a474 --- /dev/null +++ b/tools/tests/roof_character_scale_test.inc @@ -0,0 +1,78 @@ +.data +roofCastTest0 db "build\hall-expansion\roof-cast-0.bmp",0 +roofCastTest1 db "build\hall-expansion\roof-cast-1.bmp",0 +roofCastTest2 db "build\hall-expansion\roof-cast-2.bmp",0 +roofCastTest3 db "build\hall-expansion\roof-cast-3.bmp",0 +roofCastTest4 db "build\hall-expansion\roof-cast-4.bmp",0 +roofCastTest5 db "build\hall-expansion\roof-cast-5.bmp",0 +roofCastTest6 db "build\hall-expansion\roof-cast-6.bmp",0 +roofCastTest7 db "build\hall-expansion\roof-cast-7.bmp",0 +roofCastTestPaths dd OFFSET roofCastTest0,OFFSET roofCastTest1,OFFSET roofCastTest2,OFFSET roofCastTest3 + dd OFFSET roofCastTest4,OFFSET roofCastTest5,OFFSET roofCastTest6,OFFSET roofCastTest7 +.code +RoofCharacterScaleNativeTests PROC uses ebx esi edi + local moving:DWORD + mov expansionStage,160 + mov viewEye3D,ROOF_STANDING_EYE + mov viewHorizon3D,HALF_H + xor esi,esi +roof_cast_test_visitor: + mov eax,roofGuestMoving3D[esi*4] + mov moving,eax + xor ebx,ebx +roof_cast_test_pose: + mov roofGuestMoving3D[esi*4],ebx + invoke ConfigureRoofVisitor3D,esi + ; Height relative to standing eyes must match the hall, in either pose. + mov eax,roofActor3D.worldH + mov ecx,HALL_STANDING_EYE + imul ecx + mov ecx,ROOF_STANDING_EYE + idiv ecx + sub eax,CAST_HEIGHT + cmp eax,-1 + jl roof_cast_test_fail + cmp eax,1 + jg roof_cast_test_fail + mov eax,roofActor3D.worldH + imul eax,192 + cdq + mov ecx,224 + idiv ecx + cmp eax,roofActor3D.worldW + jne roof_cast_test_fail + mov eax,roofActor3D.worldH + neg eax + cdq + mov ecx,56 + idiv ecx + cmp eax,roofActor3D.groundZ + jne roof_cast_test_fail + cmp roofActor3D.asset,OFFSET characterCast3D + jne roof_cast_test_fail + inc ebx + cmp ebx,2 + jb roof_cast_test_pose + invoke DrawBox,0,0,SCREEN_W,SCREEN_H,001C2228h + lea edi,zBuffer + mov eax,7FFFFFFFh + mov ecx,SCREEN_W + rep stosd + lea edi,cityDepthBuffer3D + mov ecx,SCREEN_SIZE + rep stosd + mov roofActor3D.posX,16*FP_1 + mov roofActor3D.posY,16*FP_1 + invoke DrawCityObject3D,OFFSET roofActor3D,0 + invoke ExpansionSnapshot,roofCastTestPaths[esi*4] + mov eax,moving + mov roofGuestMoving3D[esi*4],eax + inc esi + cmp esi,ROOF_GUEST_COUNT + jb roof_cast_test_visitor + mov viewEye3D,HALL_STANDING_EYE + ret +roof_cast_test_fail: + invoke ExitProcess,expansionStage + ret +RoofCharacterScaleNativeTests ENDP