feat: enhance bowling physics, audio, ball weight selection, and visitor cast routines

- Bowling feel: impulse contact solver, string pinsetter cycle, in-world lengthwise return rack and visible ball return lift

- Ball weights: 4/5/6/7 kg selection with equal launch work physics, momentum balance, and projected picking

- Audio: dedicated MCI channels with 7 synthesized waveaudio cues (release, rolling, impacts, applause, pinsetter, return)

- First-person animations: 33-frame player underarm throw/follow-through sequence (player-underarm-v2)

- Visitors: 10 persistent shared cast identities with exclusive reservations, activity transitions, and chair sit/rise animations

- Tests & verification: comprehensive native regression test suites and updated documentation

- Synchronize binaries and update launcher hash to 536E23BED4055562BC63871826FF44FFED037923A9BE8E0F591131DB4FBD104B
This commit is contained in:
Artvasil09 2026-11-04 18:44:12 +03:00
parent 24d66ddf0e
commit 7e081beeca
51 changed files with 5270 additions and 411 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

View file

@ -0,0 +1,25 @@
{
"generator": "built-in image_gen",
"source": "player-underarm-v2.png",
"brief": "First-person right-handed underarm bowling: low pendulum swing beside thigh, backswing leaves view, hand behind/below blue ball, ankle-height release, palm-up follow-through; dark rolled sleeve; transparent 4 by 3 atlas. Replaces rejected forward-pushing draft.",
"selected_cells": [
0,
1,
2,
3,
4,
5,
6,
8,
9,
10,
11
],
"frames": 33,
"frame_size": [
256,
256
],
"release_frame": 21,
"audio": "Original seeded PCM synthesis, no sampled third-party recordings."
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 571 KiB

Binary file not shown.

View file

@ -8,7 +8,7 @@ namespace AssemblerCasinoLauncher
{
static class Program
{
const string ExpectedPokerSha256 = "3D0237E09CAED350548457BFAB24C4D67D6FAFC3D689C2D87C8BE360B64EC0AF";
const string ExpectedPokerSha256 = "536E23BED4055562BC63871826FF44FFED037923A9BE8E0F591131DB4FBD104B";
[STAThread]
static void Main()

Binary file not shown.

Binary file not shown.

View file

@ -35,19 +35,115 @@ 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.
ten-frame score sheet with every roll, X for a strike, / for a spare and - for a
miss; unresolved cumulative 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
The player game uses an arcade, not regulation-grade, collision approximation.
Each 16-ms fixed tick has four 4-ms collision substeps. Normal impulses include
relative velocity, restitution and the ball/pin mass ratio; overlap correction
does not apply a second impulse to opening contacts. Billiards-style contact
resolution is reused without changing the billiards module. Contact normals and
pin axes retain fractional precision and are renormalized after yaw: even tiny
negative impulses cannot turn an ellipsoid into a clipped rectangular slab. Fallen pins still
collide and transfer momentum. Kickbacks and the back cushion return pins to the
deck; friction damps sliding and spin, increasing after the initial collision
cascade. Bounds include the tilted pin's full silhouette. Gutters latch once entered.
The match uses a shopping-centre-style string pinsetter with cords intentionally
not drawn. Invisible slack tethers allow about 0.5 world units of travel from
each spot, damping outward motion only at the limit rather than pulling pins
back during the roll. Constraints run after every 4-ms contact substep.
A separate 5.2-second reset cycle closes a smooth metal pin shield, raises and
straightens the pins behind it, holds them aloft, lowers only the remaining pins
after the first ball (or all ten for a fresh rack), then opens the shield. A
first-ball miss leaves the standing rack alone, without running the motor.
Knocked-down pins remain physically represented aloft behind the hood, rather
than disappearing halfway up. The close camera eases back to frame the machine.
Rendering this cycle never changes scoring or advances the turn twice.
The sequence is based on Brunswick StringPin's documented standing-pin, strike,
second-ball and gutter cycles, with a front shield as on contemporary string machines:
https://brunswickbowling.com/uploads/document-library/Service-Manuals/Pinsetters/StringPin/BB_Service_Operations_Manual_StringPin_55-900004-000_En_Rev-06-16.pdf
Video reference: https://www.youtube.com/watch?v=EUwpfHz1Fp4
Impact excites a separate spring/gravity rocking angle: a small nudge can recover,
while a pin crossing the fall threshold is scored. Pins keep falling and hitting
one another after the ball passes; scoring waits for settlement with a bounded
timeout. Oriented ray/ellipsoid surfaces follow each pin's fall direction and
rolling yaw. This remains a floor-plane impulse solver with angular toppling,
not an unconstrained 3D rigid-body simulation. Other exhibition lanes retain
their existing automatic simulation; player and match opponent share the new
solver. Removed pins cannot be scored twice.
Private lane RNG chooses only the opponent's launch aim and power. The player
has a 33-frame first-person underarm sequence: the backswing leaves the view,
the camera bends toward the low release, and the actual ball begins moving at
the release event. The held-ball sprite is never drawn over the rolling sphere.
The rejected forward-push draft is not shipped. Direction/power controls remain
unchanged, with the power meter on the left, away from the player's arm.
The camera follows the ball and returns to the approach as the reset finishes.
The bot uses a full-body shared-cast throwing animation.
Each ball is a native shaded sphere with a procedural marble surface and
rotating finger-hole details, not a new neural or image runtime.
After every roll, including a first-ball miss and the last ball of the match,
the lane enters a 2.8-second ball-return phase. The ball disappears into the rear
pit, travels through the hidden return, then visibly emerges from the paired
return hood, rolls along the metal guides and stops in the tray. The camera
turns toward the correct left/right return unit and eases back to the approach;
the player's held-ball pose rises into view only after the tray ball is hidden.
The returning ball retains its committed colour; its storage slot stays empty.
Input during return cannot launch or lock the next throw. The existing scorer
advances exactly once before presentation; the final result overlay waits until
the ball has returned. Tab pauses this phase and its motor sound; Escape still
restores the saved room position and camera. The room's four return machines
have native hood/rail geometry and collision around their extended trays.
The return cycle does not change launch parameters or scores.
### Ball Mass
Before a match, the camera faces the actual paired return's ball storage rack.
Four balls (4 kg green, 5 kg red, 6 kg blue, 7 kg amber) rest lengthwise on two
metal rails beside the separate automatic return track. Click a sphere or its
projected weight plate to take it; Space takes the default/current ball. Keys
1-4 also select a ball. The weight command during aiming reopens this same rack.
Sphere picking and plates use the same world projection on either lane side.
The chosen ball's slot is empty while it is held, rolled or returned. A cached
single-frame palette transform colours only blue ball pixels in the hand atlas,
preserving skin, sleeve and alpha without duplicating the full animation pack.
Selecting a mass consumes its own event; it never also locks direction.
The default and opponent use 6 kg. The committed mass is frozen
for the whole throw; changing the next selection cannot alter an in-flight ball.
Each pin has mass 1600 grams, within the USBC standard pin range:
https://bowl.com/welcome/how-bowling-pins-are-made
The normal impulse solver uses Q16 inverse masses proportional to 1600/mass_g,
with signed 64-bit products to avoid overflow. It retains restitution 0.5 and
mass-weighted separation. A heavier ball loses a smaller fraction of its incoming
velocity at an otherwise identical contact. No random accuracy penalty, strike
bonus or mass-dependent floor-friction multiplier is added.
The gauge fixes the launch work W, calibrated to the original 6 kg release:
v_reference = 3 + 6 * power/100 m/s, W = 0.5 * 6 * v_reference^2 joules.
Actual speed is v_reference * sqrt(6 / mass_kg). The launch direction is normalized
so aiming away from the centre does not add extra speed. This is translational
launch work, not a complete model of arm biomechanics or rolling rotational energy.
Greater mass is therefore not an unconditional advantage: lighter balls launch
faster at equal work, while heavier balls resist collision deflection better.
The opponent and two nearby spectators use their existing persistent identities
and react to the result. Their restaurant duplicates are suppressed during the
match. Strikes, spares and strong throws trigger shared-cast cheering; misses
leave conversational poses rather than forcing a celebration.
Seven dedicated MCI waveaudio channels provide release, rolling, light/heavy pin
impacts, applause, the pinsetter motor and ball return. Impacts are driven by contact energy and rate-limited,
not triggered by rendering. The current sound-volume setting, including mute,
is applied before playback. Opening the score sheet stops the rolling channel;
leaving the match closes every alias. Effects are original seeded offline PCM
synthesis, not recordings taken from another game.
## Visitor Simulation
Each occupied lane owns a timer, rack mask, two players, frame counters and roll
@ -64,6 +160,16 @@ the other hall assets. No city mesh or rooftop OpenGL pass is used here.
## Sources And Rebuild
- `src_graphics/bowling*.inc`: native room and lane simulation.
- `bowling_physics.inc`, `bowling_deck.inc`, `bowling_pinsetter.inc`, `bowling_return.inc`,
`bowling_weight.inc`,
`bowling_pin_shape.inc`, `bowling_scorecard.inc`,
`bowling_performance.inc`, `bowling_audio.inc`: match physics and presentation.
- `tools/generators/pack_bowling_feel.py`: first-person atlas normalization,
offline in-betweens and original PCM Foley.
- `assets/sprites/bowling/player-underarm-v2.png` and `.json`: retained built-in
imagegen source and generation brief. The packed 33-frame ANM1 asset lives in
`assets/animations/bowling/player-underarm-v2.bin`.
- `assets/audio/bowling/`: seven distributable WAV effects.
- `tools/generators/pack_bowling.py`: offline atlas packing, frame normalization,
in-between frames and furniture/collision tables.
- `assets/sprites/bowling/`: retained image-generation sources.
@ -88,6 +194,26 @@ The additional `bowling_play_test.inc` covers LMB/Space input through native win
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.
`bowling_feel_test.inc` additionally checks recoverable rocking, strong toppling,
opening and repeated fallen-pin contacts, late settlement, tenth-frame glyphs,
63 aim/power launches, invisible travel limits, complete tilted-body bounds,
automatic guard/hoist/respots, first-miss idle, duplicate-score prevention,
tiny-impact axis/silhouette regressions, per-substep travel limits,
eight pinsetter captures,
33 native hand captures and real muted MCI playback/cleanup.
`bowling_return_test.inc` covers the paired return positions and camera turns,
visible rise/coast, held-ball handoff, blocked throw input, pause/resume, final-ball
return before results, unchanged scores/turns, tray collision and exit restoration.
Eight native return captures also produce `build/hall-expansion/bowling-native-return.gif`.
`bowling_weight_test.inc` checks world-projected sphere/plate selection on both
lane pairs, non-overlapping kg labels, storage-rail collision, colour and alpha
preservation in source/scratch hand pixels, native mouse/key input, immutable
shot mass, bot independence, equal launch work, momentum balance, restitution and
non-increasing translational contact energy. An additional 84 complete high-power
throws across all four masses verify settlement and deck bounds without replacing
the simulated outcomes.
The handoff and follow-through convention follows the USBC release guidance:
https://bowl.com/welcome/developing-your-release-f7b6dd1aff6c612e4aea7fce9dbb9194
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.

View file

@ -42,6 +42,58 @@ pick it up, throw, return, sit and alternate turns while keeping their identity.
The historical twelve-slot NPC journal is preserved for save compatibility;
its appearance mapping resolves to the ten shared visual identities.
## Main Hall Routines
The main hall now has ten simultaneous visitors with fixed identities, rather
than six slots changing people at the entrance. The first six legacy poker-mind
records retain their size and personality indices; four extra world actors use
the same bounded entity accessor. The twelve-entry contract journal is retained
without changing existing profile saves. Ten of its identities are currently
represented in the hall; old entries are not deleted or reassigned.
Each visitor has a preferred game, walking speed, session length, private
ambient wallet and private RNG. Weighted destinations replace the single shared
route. Short abstract game sessions produce a win/loss reaction, after which
guests continue playing or take a break. These are ambient outcomes, not full
hands played by the actual card-game engines. Neither the player's bankroll,
casino RNG, achievements nor contract progress is touched.
There are twelve reservable places: five game approaches, two bar positions,
two existing lounge chairs, two audience positions and the entrance. Guests
order, drink, rest, converse with a nearby stationary guest and watch the stage.
Only short nearby remarks are displayed. Stale remarks clear on arrival.
Rest/drinks ease negative mood. Out-of-funds guests leave through the entrance,
wait offstage and return as the same person with a new ambient budget.
BFS and body clearance still use the real room geometry. A visitor reserves a
place before walking and releases it after a successful change of destination.
Blocked guests pause with staggered retries; persistent obstruction releases
the reservation and permits replanning. Gait only advances on actual movement.
Seated poses align with the two existing chairs and pause navigation during the
stand-up transition. Visitor canvas dimensions remain unchanged.
Pathfinding checks quarter points along each tile edge, not just free tile
centres, so approaches cannot cut through the expanded chair collision. Seating
has its own bounded 800 ms clock: starting a conversation resets the activity
clock but cannot replay the sit-down animation. The visual seat anchor blends
smoothly in both directions; walking frames require actual movement velocity.
Routines currently belong to the main hall. Other floors keep their existing
ambient controllers and shared appearance assets; this is not yet a persistent
cross-floor population simulation. Hall routines pause outside walking mode or
outside the main hall, including while a modal conversation is open.
`check_hall_expansion.py --npc-only` compiles production MASM and checks eight
simulated minutes: fixed identities, exclusive reservations, reachable positions,
activity transitions, walking progress, player-bank/RNG isolation, modal/floor
pause and bounded recovery from a player blocking the path. It also captures
native order, drinking, seated, win and loss poses. Full hall checks include the
same cases; no neural model or new runtime dependency is needed.
The chair regression also walks a visitor to a real chair at 16 ms ticks, checks
the complete sit/rise frame sequence and monotonic anchor motion, and starts a
seated conversation without restarting the transition. Native captures and GIFs
are written as `build/hall-expansion/npc-chair-*`.
## Assets
- `assets/sprites/characters/person-00-source-v1.png` through `person-09-source-v1.png`:

BIN
poker.exe

Binary file not shown.

BIN
poker.obj

Binary file not shown.

View file

@ -1,7 +1,10 @@
; Native room, automatic lane simulation and full-body visitors; no AI service.
InitBowling3D PROC uses esi edi
mov bpActive3D,0
mov bpWeightKg3D,6
mov bpBotWeightKg3D,6
call InitBowlingBallSurface3D
call InitBowlingSetterSurface3D
mov bwClock3D,-1
mov bwMonitorClock3D,-1
mov bwSeat3D,0
@ -48,12 +51,18 @@ LoadBowling3D PROC
invoke LoadAnimPack3D,OFFSET bwArt3D,OFFSET bwArtPath3D
invoke ValidateCityPack3D,OFFSET bwArt3D,256,256,14
call LoadCharacterCast3D
invoke LoadAnimPack3D,OFFSET bpHandArt3D,OFFSET bpHandPath3D
invoke ValidateCityPack3D,OFFSET bpHandArt3D,256,256,33
mov bpRackHandFrame3D,-1
mov bpRackHandKg3D,-1
call RefreshBowlingMonitors3D
bw_load_done:
ret
LoadBowling3D ENDP
FreeBowling3D PROC
call BowlingAudioClose3D
invoke FreeAnimPack3D,OFFSET bpHandArt3D
invoke FreeAnimPack3D,OFFSET bwArt3D
invoke FreeAnimPack3D,OFFSET bwMotionArt3D
invoke FreeAnimPack3D,OFFSET bwSeatedArt3D
@ -62,6 +71,24 @@ FreeBowling3D ENDP
ResolveBowlingSurface3D PROC material:DWORD
mov eax,material
cmp eax,255
jb bw_material_base
cmp eax,257
ja bw_material_missing
sub eax,254
shl eax,14
add eax,OFFSET bpBallSurface3D
mov ecx,64
mov edx,64
ret
bw_material_base:
cmp eax,254
jne @F
mov eax,OFFSET bpSetterSurface3D
mov ecx,64
mov edx,64
ret
@@:
cmp eax,253
jne @F
mov eax,OFFSET bpBallSurface3D
@ -173,6 +200,36 @@ BowlingCanWalk3D PROC uses esi edi px:DWORD,py:DWORD
cmp py,FP_1*223/10
jb bw_walk_block
bw_walk_furniture:
cmp py,FP_1*252/10
jb bw_walk_existing_props
cmp py,FP_1*266/10
ja bw_walk_existing_props
xor esi,esi
bw_walk_return_tray:
imul eax,esi,FP_1*38/10
add eax,FP_1*395/100
sub eax,px
mov edi,eax
cdq
xor eax,edx
sub eax,edx
cmp py,26*FP_1
jb @F
cmp eax,FP_1*4/10
jbe bw_walk_block
@@:
; The adjacent storage rails keep the automatic return path clear.
mov eax,edi
neg eax
cmp eax,FP_1*25/100
jl @F
cmp eax,FP_1*85/100
jle bw_walk_block
@@:
inc esi
cmp esi,4
jb bw_walk_return_tray
bw_walk_existing_props:
lea esi,bwCollision3D
mov edi,BOWL_COLLISION_COUNT
bw_walk_prop:

View file

@ -0,0 +1,119 @@
BowlingAudioOpen3D PROC uses esi
call BowlingAudioClose3D
; Offscreen mechanics probes have no playback device or foreground window.
cmp h3DWnd,0
je bp_audio_open_done
xor esi,esi
bp_audio_open:
invoke mciSendStringA,bpAudioOpens[esi*4],0,0,0
inc esi
cmp esi,7
jb bp_audio_open
mov bpAudioReady3D,1
mov bpAudioVolume3D,-1
bp_audio_open_done:
ret
BowlingAudioOpen3D ENDP
BowlingAudioClose3D PROC uses esi
cmp bpAudioReady3D,0
je bp_audio_close_done
xor esi,esi
bp_audio_close:
invoke mciSendStringA,bpAudioCloses[esi*4],0,0,0
inc esi
cmp esi,7
jb bp_audio_close
bp_audio_close_done:
mov bpAudioReady3D,0
mov bpAudioRolling3D,0
mov bpAudioReturning3D,0
ret
BowlingAudioClose3D ENDP
BowlingAudioUpdate3D PROC uses ebx esi
cmp bpAudioReady3D,0
je bp_audio_done
mov eax,settingsVolume
cmp eax,bpAudioVolume3D
je bp_audio_state
mov bpAudioVolume3D,eax
xor esi,esi
bp_audio_gain:
mov eax,settingsVolume
imul eax,70
test esi,esi
jnz @F
imul eax,settingsVolume,35
@@:
invoke FormatNumber,eax,OFFSET bpText3D
invoke CopyCString,OFFSET bpAudioCommand,bpAudioPrefixes[esi*4]
invoke AppendCString,OFFSET bpAudioCommand,OFFSET bpText3D
invoke mciSendStringA,OFFSET bpAudioCommand,0,0,0
inc esi
cmp esi,7
jb bp_audio_gain
bp_audio_state:
xor ebx,ebx
cmp bpState3D,BP_ROLL
jne @F
cmp bpScoreOpen3D,0
jne @F
cmp bpEndTicks3D,0
jne @F
mov ebx,1
@@:
cmp ebx,bpAudioRolling3D
je bp_audio_return
mov bpAudioRolling3D,ebx
test ebx,ebx
jz bp_audio_stop_roll
cmp bpAudioReleased3D,0
jne @F
mov bpAudioReleased3D,1
invoke mciSendStringA,OFFSET bpReleasePlay,0,0,0
@@:
mov eax,OFFSET bpRollPlay
jmp bp_audio_send_roll
bp_audio_stop_roll:
mov eax,OFFSET bpRollStop
bp_audio_send_roll:
invoke mciSendStringA,eax,0,0,0
bp_audio_return:
xor ebx,ebx
cmp bpState3D,BP_RETURN
jne @F
cmp bpScoreOpen3D,0
jne @F
cmp bpTime3D,2000
jae @F
mov ebx,1
@@:
cmp ebx,bpAudioReturning3D
je bp_audio_impact
mov bpAudioReturning3D,ebx
mov eax,OFFSET bpReturnStop
test ebx,ebx
jz @F
invoke FormatNumber,bpTime3D,OFFSET bpText3D
invoke CopyCString,OFFSET bpAudioCommand,OFFSET bpReturnPlay
invoke AppendCString,OFFSET bpAudioCommand,OFFSET bpText3D
mov eax,OFFSET bpAudioCommand
@@:
invoke mciSendStringA,eax,0,0,0
bp_audio_impact:
cmp bpImpactCooldown3D,0
jne bp_audio_done
cmp bpImpactEnergy3D,768
jb bp_audio_done
mov eax,OFFSET bpLightPlay
cmp bpImpactEnergy3D,4000
jb @F
mov eax,OFFSET bpImpactPlay
@@:
invoke mciSendStringA,eax,0,0,0
mov bpImpactCooldown3D,140
mov bpImpactEnergy3D,0
bp_audio_done:
ret
BowlingAudioUpdate3D ENDP

View file

@ -0,0 +1,35 @@
.data
bpRollOpen db 'open "assets/audio/bowling/rolling.wav" type waveaudio alias bowlroll',0
bpImpactOpen db 'open "assets/audio/bowling/pins-heavy.wav" type waveaudio alias bowlpins',0
bpReleaseOpen db 'open "assets/audio/bowling/release.wav" type waveaudio alias bowlrelease',0
bpClapOpen db 'open "assets/audio/bowling/applause.wav" type waveaudio alias bowlclap',0
bpLightOpen db 'open "assets/audio/bowling/pins-light.wav" type waveaudio alias bowllight',0
bpResetOpen db 'open "assets/audio/bowling/pinsetter.wav" type waveaudio alias bowlreset',0
bpReturnOpen db 'open "assets/audio/bowling/return.wav" type waveaudio alias bowlreturn',0
bpReturnPlay db 'play bowlreturn from ',0
bpReturnStop db 'stop bowlreturn',0
bpRollPlay db 'play bowlroll from 0',0
bpRollStop db 'stop bowlroll',0
bpImpactPlay db 'play bowlpins from 0',0
bpReleasePlay db 'play bowlrelease from 0',0
bpClapPlay db 'play bowlclap from 0',0
bpLightPlay db 'play bowllight from 0',0
bpResetPlay db 'play bowlreset from 0',0
bpRollClose db 'close bowlroll',0
bpImpactClose db 'close bowlpins',0
bpReleaseClose db 'close bowlrelease',0
bpClapClose db 'close bowlclap',0
bpLightClose db 'close bowllight',0
bpResetClose db 'close bowlreset',0
bpReturnClose db 'close bowlreturn',0
bpAudioOpens dd OFFSET bpRollOpen,OFFSET bpImpactOpen,OFFSET bpReleaseOpen,OFFSET bpClapOpen,OFFSET bpLightOpen,OFFSET bpResetOpen,OFFSET bpReturnOpen
bpAudioCloses dd OFFSET bpRollClose,OFFSET bpImpactClose,OFFSET bpReleaseClose,OFFSET bpClapClose,OFFSET bpLightClose,OFFSET bpResetClose,OFFSET bpReturnClose
bpVolumeRoll db 'setaudio bowlroll volume to ',0
bpVolumePins db 'setaudio bowlpins volume to ',0
bpVolumeRelease db 'setaudio bowlrelease volume to ',0
bpVolumeClap db 'setaudio bowlclap volume to ',0
bpVolumeLight db 'setaudio bowllight volume to ',0
bpVolumeReset db 'setaudio bowlreset volume to ',0
bpVolumeReturn db 'setaudio bowlreturn volume to ',0
bpAudioPrefixes dd OFFSET bpVolumeRoll,OFFSET bpVolumePins,OFFSET bpVolumeRelease,OFFSET bpVolumeClap,OFFSET bpVolumeLight,OFFSET bpVolumeReset,OFFSET bpVolumeReturn
bpAudioCommand db 80 dup(0)

View file

@ -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 253
BW_LAST_MATERIAL equ 257
BW_LANES equ 8
BW_LANE_STEP equ FP_1*19/10
InitBowling3D PROTO
@ -22,6 +22,7 @@ RenderBowlingLanes3D PROTO
RenderBowlingVisitors3D PROTO
DrawBowlingPin3D PROTO :DWORD,:DWORD,:DWORD
DrawBowlingBall3D PROTO :DWORD,:DWORD,:DWORD
DrawBowlingBallAt3D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
BowlingImpact3D PROTO :DWORD
BowlingRegisterRoll3D PROTO :DWORD,:DWORD
BowlingScore3D PROTO :DWORD

View file

@ -0,0 +1,149 @@
; Invisible slack constraints engage only at the end of a pin's free travel.
BowlingPlayTether3D PROC uses ebx esi pin:DWORD
local deltaX:DWORD,deltaY:DWORD,length8:DWORD,nx:DWORD,ny:DWORD,outward:DWORD
mov esi,pin
mov eax,bpPinX3D[esi*4]
sub eax,bwPinX3D[esi*4]
mov deltaX,eax
sar eax,8
imul eax,eax
mov ebx,eax
mov eax,bpPinY3D[esi*4]
sub eax,bwPinY3D[esi*4]
mov deltaY,eax
sar eax,8
imul eax,eax
add eax,ebx
cmp eax,128*128
jbe bp_tether_done
invoke PoolSqrt3D,eax
mov length8,eax
mov eax,deltaX
imul eax,256
cdq
idiv length8
mov nx,eax
mov eax,deltaY
imul eax,256
cdq
idiv length8
mov ny,eax
mov eax,nx
imul eax,128
sar eax,8
add eax,bwPinX3D[esi*4]
mov bpPinX3D[esi*4],eax
mov eax,ny
imul eax,128
sar eax,8
add eax,bwPinY3D[esi*4]
mov bpPinY3D[esi*4],eax
mov eax,bpPinVX3D[esi*4]
imul nx
shrd eax,edx,16
mov ebx,eax
mov eax,bpPinVY3D[esi*4]
imul ny
shrd eax,edx,16
add eax,ebx
test eax,eax
jle bp_tether_done
imul eax,4
cdq
mov ecx,5
idiv ecx
mov outward,eax
imul nx
shrd eax,edx,16
sub bpPinVX3D[esi*4],eax
mov eax,outward
imul ny
shrd eax,edx,16
sub bpPinVY3D[esi*4],eax
bp_tether_done:
ret
BowlingPlayTether3D ENDP
; Bound the complete pin silhouette, including its horizontal neck/head.
BowlingPlayDeckBounds3D PROC uses ebx esi pin:DWORD
local extension:DWORD,lo:DWORD,hi:DWORD
mov esi,pin
mov ecx,bpPinTilt3D[esi*4]
shr ecx,8
mov eax,tableSin[ecx*4]
imul eax,35
cdq
mov ecx,100
idiv ecx
mov extension,eax
imul bpPinAxisX3D[esi*4]
shrd eax,edx,16
mov lo,-FP_1*82/100
mov hi,FP_1*82/100
test eax,eax
js bp_deck_left_extent
sub hi,eax
jmp bp_deck_x
bp_deck_left_extent:
sub lo,eax
bp_deck_x:
mov eax,bpPinX3D[esi*4]
cmp eax,lo
jge bp_deck_right
mov eax,lo
mov bpPinX3D[esi*4],eax
cmp bpPinVX3D[esi*4],0
jge bp_deck_y_extent
jmp bp_deck_bounce_x
bp_deck_right:
cmp eax,hi
jle bp_deck_y_extent
mov eax,hi
mov bpPinX3D[esi*4],eax
cmp bpPinVX3D[esi*4],0
jle bp_deck_y_extent
bp_deck_bounce_x:
mov eax,bpPinVX3D[esi*4]
neg eax
cdq
mov ecx,5
idiv ecx
mov bpPinVX3D[esi*4],eax
bp_deck_y_extent:
mov eax,extension
imul bpPinAxisY3D[esi*4]
shrd eax,edx,16
mov lo,FP_1*308/100
mov hi,FP_1*547/100
test eax,eax
js bp_deck_back_extent
sub hi,eax
jmp bp_deck_y
bp_deck_back_extent:
sub lo,eax
bp_deck_y:
mov eax,bpPinY3D[esi*4]
cmp eax,lo
jge bp_deck_front
mov eax,lo
mov bpPinY3D[esi*4],eax
cmp bpPinVY3D[esi*4],0
jge bp_deck_done
jmp bp_deck_bounce_y
bp_deck_front:
cmp eax,hi
jle bp_deck_done
mov eax,hi
mov bpPinY3D[esi*4],eax
cmp bpPinVY3D[esi*4],0
jle bp_deck_done
bp_deck_bounce_y:
mov eax,bpPinVY3D[esi*4]
neg eax
cdq
mov ecx,5
idiv ecx
mov bpPinVY3D[esi*4],eax
bp_deck_done:
ret
BowlingPlayDeckBounds3D ENDP

View file

@ -0,0 +1,120 @@
DrawBowlingPlayerHand3D PROC uses ebx esi
local offsetY:DWORD
mov offsetY,120
cmp bpHandArt3D.pixels,0
je bp_hand_done
cmp bpScoreOpen3D,0
jne bp_hand_done
mov esi,bpLane3D
cmp bwTurn3D[esi*4],0
jne bp_hand_done
xor ebx,ebx
cmp bpState3D,BP_RETURN
jne @F
cmp bpAfterReturn3D,BP_FINISHED
je bp_hand_done
mov eax,bpTime3D
sub eax,2100
jle bp_hand_done
invoke BowlingPinsetterEase3D,eax,700
neg eax
add eax,FP_1
imul eax,300
sar eax,16
add eax,120
mov offsetY,eax
jmp bp_hand_draw
@@:
cmp bpState3D,BP_DIRECTION
je bp_hand_draw
cmp bpState3D,BP_POWER
je bp_hand_draw
cmp bpState3D,BP_RELEASE
jne bp_hand_follow
mov eax,bpTime3D
xor edx,edx
mov ecx,33
div ecx
cmp eax,20
jbe @F
mov eax,20
@@:
mov ebx,eax
jmp bp_hand_draw
bp_hand_follow:
cmp bpState3D,BP_ROLL
jne bp_hand_done
cmp bpTime3D,396
jae bp_hand_done
mov eax,bpTime3D
xor edx,edx
mov ecx,33
div ecx
add eax,21
cmp eax,32
jbe @F
mov eax,32
@@:
mov ebx,eax
bp_hand_draw:
mov eax,bpShotWeightKg3D
cmp bpState3D,BP_DIRECTION
je bp_hand_selected
cmp bpState3D,BP_POWER
jne @F
bp_hand_selected:
mov eax,bpWeightKg3D
@@:
invoke BowlingTintHand3D,ebx,eax
invoke DrawAnimSprite3D,OFFSET bpTintedHand3D,0,320,offsetY,320,320
bp_hand_done:
ret
DrawBowlingPlayerHand3D ENDP
RenderBowlingReactions3D PROC uses ebx esi edi
local phase:DWORD
mov phase,CAST_TALK
cmp bpReactionTime3D,0
je @F
mov eax,bpReaction3D
mov phase,eax
@@:
xor esi,esi
bp_crowd_person:
lea ebx,[esi+6]
test esi,esi
jnz bp_crowd_draw
mov edi,bpLane3D
cmp bwTurn3D[edi*4],1
jne bp_crowd_draw
cmp bpState3D,BP_SETTLE
je bp_crowd_draw
cmp bpState3D,BP_FINISHED
jne bp_crowd_next
bp_crowd_draw:
invoke ConfigureCharacter3D,OFFSET bwSmallActor3D,ebx,phase,animationClock3D,0,0
mov eax,bpCentre3D
mov ecx,FP_1*16/10
test esi,esi
jnz @F
mov ecx,FP_1*9/10
@@:
cmp esi,1
jne @F
neg ecx
@@:
add eax,ecx
mov bwSmallActor3D.posX,eax
mov eax,FP_1*218/10
test esi,esi
jnz @F
mov eax,23*FP_1
@@:
mov bwSmallActor3D.posY,eax
invoke DrawCityObject3D,OFFSET bwSmallActor3D,0
bp_crowd_next:
inc esi
cmp esi,3
jb bp_crowd_person
ret
RenderBowlingReactions3D ENDP

View file

@ -0,0 +1,482 @@
; Symmetric normal impulses and overlap correction follow the existing billiards
; solver. Four 4-ms substeps prevent tunnelling; angular rocking is separate.
include src_graphics\bowling_deck.inc
NormalizeBowlingPinAxis3D PROC uses esi pin:DWORD
local axisLength:REAL8
mov esi,pin
mov eax,bpPinAxisX3D[esi*4]
or eax,bpPinAxisY3D[esi*4]
jnz bp_axis_length
mov bpPinAxisY3D[esi*4],-FP_1
ret
bp_axis_length:
fild bpPinAxisX3D[esi*4]
fmul st,st
fild bpPinAxisY3D[esi*4]
fmul st,st
faddp st(1),st
fsqrt
fstp axisLength
fild bpPinAxisX3D[esi*4]
fdiv axisLength
fmul roofFloatFP3D
fistp bpPinAxisX3D[esi*4]
fild bpPinAxisY3D[esi*4]
fdiv axisLength
fmul roofFloatFP3D
fistp bpPinAxisY3D[esi*4]
ret
NormalizeBowlingPinAxis3D ENDP
BowlingPlayHit3D PROC pin:DWORD,vx:DWORD,vy:DWORD
invoke BowlingPlayKick3D,pin,vx,vy
ret
BowlingPlayHit3D ENDP
BowlingPlayKick3D PROC uses ebx esi edi pin:DWORD,vx:DWORD,vy:DWORD
local energy:DWORD
mov esi,pin
cmp esi,10
jae bp_kick_done
; Preserve fractional impulses: signed pre-rounding can collapse the basis.
fild vx
fmul st,st
fild vy
fmul st,st
faddp st(1),st
fsqrt
fistp energy
mov eax,energy
cmp eax,128
jb bp_kick_done
cmp eax,bpImpactEnergy3D
jbe @F
mov bpImpactEnergy3D,eax
@@:
cmp eax,128
jb bp_kick_done
mov edx,bpPhysicsTicks3D
mov bpPinContactTick3D[esi*4],edx
mov ecx,2
cdq
idiv ecx
cmp eax,2000
jbe @F
mov eax,2000
@@:
add bpPinTiltV3D[esi*4],eax
mov eax,bpPinTiltV3D[esi*4]
cmp eax,2400
jle @F
mov bpPinTiltV3D[esi*4],2400
@@:
cmp bpPinTilt3D[esi*4],64*256
jae bp_kick_done
mov eax,vx
mov ecx,FP_1
imul ecx
idiv energy
mov bpPinAxisX3D[esi*4],eax
mov eax,vy
imul ecx
idiv energy
mov bpPinAxisY3D[esi*4],eax
invoke NormalizeBowlingPinAxis3D,esi
mov eax,vx
sub eax,vy
sar eax,10
mov bpPinSpin3D[esi*4],eax
bp_kick_done:
ret
BowlingPlayKick3D ENDP
BowlingPlayContact3D PROC uses ebx esi edi a:DWORD,b:DWORD
local deltaX:DWORD,deltaY:DWORD,radius:DWORD,distance:DWORD,nx:DWORD,ny:DWORD
local impulse:DWORD,changeA:DWORD,changeB:DWORD,ix:DWORD,iy:DWORD,massSum:DWORD
local contactLength:REAL8
mov esi,a
mov edi,b
mov eax,bpRadius3D[esi*4]
add eax,bpRadius3D[edi*4]
mov radius,eax
shl eax,8
mov ebx,eax
mov eax,bpPinX3D[edi*4]
sub eax,bpPinX3D[esi*4]
cmp eax,ebx
jg bp_contact_done
neg ebx
cmp eax,ebx
jl bp_contact_done
mov deltaX,eax
mov eax,bpPinY3D[edi*4]
sub eax,bpPinY3D[esi*4]
cmp eax,ebx
jl bp_contact_done
neg ebx
cmp eax,ebx
jg bp_contact_done
mov deltaY,eax
sar eax,8
imul eax,eax
mov ebx,deltaX
sar ebx,8
imul ebx,ebx
add eax,ebx
mov ebx,radius
imul ebx,ebx
cmp eax,ebx
jae bp_contact_done
fild deltaX
fmul st,st
fild deltaY
fmul st,st
faddp st(1),st
fsqrt
fst contactLength
fistp distance
cmp distance,0
jne @F
mov deltaX,1
mov deltaY,0
mov distance,1
fild distance
fstp contactLength
@@:
mov eax,radius
shl eax,8
cmp distance,eax
jae bp_contact_done
fild deltaX
fdiv contactLength
fmul roofFloatFP3D
fistp nx
fild deltaY
fdiv contactLength
fmul roofFloatFP3D
fistp ny
mov eax,bpInvMass3D[esi*4]
add eax,bpInvMass3D[edi*4]
mov massSum,eax
mov eax,bpPinVX3D[edi*4]
sub eax,bpPinVX3D[esi*4]
imul nx
shrd eax,edx,16
mov ebx,eax
mov eax,bpPinVY3D[edi*4]
sub eax,bpPinVY3D[esi*4]
imul ny
shrd eax,edx,16
add eax,ebx
test eax,eax
jns bp_contact_separate
neg eax
mov ecx,FP_1*3/2
imul ecx
idiv massSum
mov impulse,eax
imul bpInvMass3D[esi*4]
shrd eax,edx,16
mov changeA,eax
mov eax,impulse
imul bpInvMass3D[edi*4]
shrd eax,edx,16
mov changeB,eax
mov eax,changeA
imul nx
shrd eax,edx,16
sub bpPinVX3D[esi*4],eax
neg eax
mov ix,eax
mov eax,changeA
imul ny
shrd eax,edx,16
sub bpPinVY3D[esi*4],eax
neg eax
invoke BowlingPlayKick3D,esi,ix,eax
mov eax,changeB
imul nx
shrd eax,edx,16
add bpPinVX3D[edi*4],eax
mov ix,eax
mov eax,changeB
imul ny
shrd eax,edx,16
add bpPinVY3D[edi*4],eax
invoke BowlingPlayKick3D,edi,ix,eax
bp_contact_separate:
mov eax,radius
shl eax,8
sub eax,distance
jle bp_contact_done
imul eax,3
sar eax,2
mov impulse,eax
imul bpInvMass3D[esi*4]
idiv massSum
mov changeA,eax
mov eax,impulse
imul bpInvMass3D[edi*4]
idiv massSum
mov changeB,eax
mov eax,changeA
imul nx
shrd eax,edx,16
sub bpPinX3D[esi*4],eax
mov eax,changeA
imul ny
shrd eax,edx,16
sub bpPinY3D[esi*4],eax
mov eax,changeB
imul nx
shrd eax,edx,16
add bpPinX3D[edi*4],eax
mov eax,changeB
imul ny
shrd eax,edx,16
add bpPinY3D[edi*4],eax
bp_contact_done:
ret
BowlingPlayContact3D ENDP
BowlingPlayStep3D PROC uses ebx esi edi
local substep:DWORD,rack:DWORD
mov esi,bpLane3D
mov eax,bwRack3D[esi*4]
or eax,1024
mov rack,eax
mov eax,bpBallX3D
mov bpPinX3D[40],eax
mov eax,bpBallY3D
mov bpPinY3D[40],eax
mov eax,bpBallVX3D
mov bpPinVX3D[40],eax
mov eax,bpBallVY3D
mov bpPinVY3D[40],eax
mov substep,0
bp_physics_substep:
xor esi,esi
bp_physics_integrate:
bt rack,esi
jnc bp_physics_integrate_next
mov eax,bpPinVX3D[esi*4]
sar eax,2
add bpPinX3D[esi*4],eax
mov eax,bpPinVY3D[esi*4]
sar eax,2
add bpPinY3D[esi*4],eax
cmp esi,10
jne bp_physics_integrate_next
cmp bpPinY3D[40],FP_1*28/10
jge @F
mov bpPinVX3D[40],0
mov bpPinVY3D[40],0
@@:
mov eax,bpPinX3D[40]
cdq
xor eax,edx
sub eax,edx
cmp eax,FP_1*74/100
jbe bp_physics_integrate_next
mov bpGutter3D,1
mov eax,FP_1*87/100
cmp bpPinX3D[40],0
jge @F
neg eax
@@:
mov bpPinX3D[40],eax
mov bpPinVX3D[40],0
bp_physics_integrate_next:
inc esi
cmp esi,BP_BODY_COUNT
jb bp_physics_integrate
xor esi,esi
bp_physics_pair_a:
bt rack,esi
jnc bp_physics_pair_next_a
lea edi,[esi+1]
bp_physics_pair_b:
bt rack,edi
jnc bp_physics_pair_next_b
cmp edi,10
jne @F
cmp bpGutter3D,0
jne bp_physics_pair_next_b
@@:
invoke BowlingPlayContact3D,esi,edi
bp_physics_pair_next_b:
inc edi
cmp edi,BP_BODY_COUNT
jb bp_physics_pair_b
bp_physics_pair_next_a:
inc esi
cmp esi,10
jb bp_physics_pair_a
xor esi,esi
bp_physics_substep_limits:
bt rack,esi
jnc @F
invoke BowlingPlayTether3D,esi
invoke BowlingPlayDeckBounds3D,esi
@@:
inc esi
cmp esi,10
jb bp_physics_substep_limits
inc substep
cmp substep,4
jb bp_physics_substep
xor esi,esi
bp_physics_pin:
bt rack,esi
jnc bp_physics_pin_next
mov ecx,246
mov eax,bpPhysicsTicks3D
sub eax,bpPinContactTick3D[esi*4]
cmp eax,10
jb @F
bt bpHitMask3D,esi
jnc @F
mov ecx,224
@@:
mov eax,bpPinVX3D[esi*4]
imul eax,ecx
sar eax,8
mov bpPinVX3D[esi*4],eax
mov eax,bpPinVY3D[esi*4]
imul eax,ecx
sar eax,8
mov bpPinVY3D[esi*4],eax
mov eax,bpPinTilt3D[esi*4]
cmp eax,24*256
jae bp_physics_topple
mov edx,eax
sar edx,6
sub bpPinTiltV3D[esi*4],edx
jmp bp_physics_tilt
bp_physics_topple:
add bpPinTiltV3D[esi*4],160
bp_physics_tilt:
mov edx,bpPinTiltV3D[esi*4]
imul edx,245
sar edx,8
mov bpPinTiltV3D[esi*4],edx
add eax,edx
test eax,eax
jns @F
xor eax,eax
mov bpPinTiltV3D[esi*4],0
@@:
cmp eax,128*256
jbe @F
mov eax,128*256
mov edx,bpPinTiltV3D[esi*4]
neg edx
sar edx,2
cmp edx,-80
jl bp_physics_bounce
xor edx,edx
bp_physics_bounce:
mov bpPinTiltV3D[esi*4],edx
@@:
mov bpPinTilt3D[esi*4],eax
cmp eax,64*256
jb bp_physics_pin_next
bts bpHitMask3D,esi
mov bpRadius3D[esi*4],48
; Slow rolling yaw, independent of the fall's vertical angle.
mov ecx,bpPinSpin3D[esi*4]
and ecx,511
mov eax,bpPinAxisX3D[esi*4]
imul tableCos[ecx*4]
shrd eax,edx,16
mov ebx,eax
mov eax,bpPinAxisY3D[esi*4]
imul tableSin[ecx*4]
shrd eax,edx,16
sub ebx,eax
mov eax,bpPinAxisX3D[esi*4]
imul tableSin[ecx*4]
shrd eax,edx,16
mov edi,eax
mov eax,bpPinAxisY3D[esi*4]
imul tableCos[ecx*4]
shrd eax,edx,16
add eax,edi
mov bpPinAxisY3D[esi*4],eax
mov bpPinAxisX3D[esi*4],ebx
invoke NormalizeBowlingPinAxis3D,esi
mov eax,bpPinSpin3D[esi*4]
imul eax,230
test eax,eax
jns @F
add eax,255
@@:
sar eax,8
mov bpPinSpin3D[esi*4],eax
bp_physics_pin_next:
bt rack,esi
jnc @F
invoke BowlingPlayTether3D,esi
invoke BowlingPlayDeckBounds3D,esi
@@:
inc esi
cmp esi,10
jb bp_physics_pin
mov eax,bpPinX3D[40]
mov bpBallX3D,eax
mov eax,bpPinY3D[40]
mov bpBallY3D,eax
mov eax,bpPinVX3D[40]
mov bpBallVX3D,eax
mov eax,bpPinVY3D[40]
mov bpBallVY3D,eax
inc bpPhysicsTicks3D
cmp bpBallY3D,3*FP_1
jge @F
inc bpEndTicks3D
@@:
ret
BowlingPlayStep3D ENDP
BowlingPlayReady3D PROC uses esi
cmp bpPhysicsTicks3D,900
jae bp_physics_ready
cmp bpEndTicks3D,100
jb bp_physics_not_ready
xor esi,esi
bp_physics_quiet:
mov eax,bpPinVX3D[esi*4]
cdq
xor eax,edx
sub eax,edx
cmp eax,96
ja bp_physics_not_ready
mov eax,bpPinTilt3D[esi*4]
cmp eax,128*256
jae @F
cmp eax,24*256
jae bp_physics_not_ready
mov eax,bpPinTiltV3D[esi*4]
cdq
xor eax,edx
sub eax,edx
cmp eax,32
ja bp_physics_not_ready
@@:
mov eax,bpPinVY3D[esi*4]
cdq
xor eax,edx
sub eax,edx
cmp eax,96
ja bp_physics_not_ready
inc esi
cmp esi,10
jb bp_physics_quiet
bp_physics_ready:
mov eax,1
ret
bp_physics_not_ready:
xor eax,eax
ret
BowlingPlayReady3D ENDP

View file

@ -0,0 +1,296 @@
; The existing ray/ellipsoid renderer, evaluated in a pin-local orthonormal basis.
DrawBowlingEllipsoid3D PROC uses ebx esi edi centreX:DWORD,centreY:DWORD,centreZ:DWORD,radiusX:DWORD,radiusY:DWORD,radiusZ:DWORD,material:DWORD
local leftPx:DWORD,rightPx:DWORD,topPx:DWORD,bottomPx:DWORD,column:DWORD,row:DWORD
local centrePx:DWORD,centreDepth:DWORD,pixelDepth:DWORD,temp:DWORD,texU:DWORD,texV:DWORD
local texture:DWORD,texWidth:DWORD,texHeight:DWORD,shade:DWORD,color:DWORD
local worldOX:DWORD,worldOY:DWORD,worldOZ:DWORD,worldRZ:REAL4,boundRadius:DWORD
local ox:REAL4,oy:REAL4,oz:REAL4,rayX:REAL4,rayY:REAL4,rayZ:REAL4
local quadraticC:REAL4,quadraticA:REAL4,quadraticB:REAL4,discriminant:REAL4,hitTime:REAL4
invoke ResolveCitySurface3D,material
test eax,eax
jz bp_shape_done
mov texture,eax
mov texWidth,ecx
mov texHeight,edx
invoke ProjectCityPoint3D,centreX,centreY
cmp edx,FP_1/8
jle bp_shape_done
mov centrePx,eax
mov centreDepth,edx
mov eax,radiusX
cmp eax,radiusZ
jge @F
mov eax,radiusZ
@@:
mov boundRadius,eax
shl eax,1
imul eax,SCREEN_H
cdq
idiv centreDepth
mov ecx,centrePx
sub ecx,eax
mov leftPx,ecx
add eax,centrePx
mov rightPx,eax
mov eax,viewEye3D
sub eax,centreZ
sub eax,boundRadius
imul eax,SCREEN_H
cdq
idiv centreDepth
add eax,viewHorizon3D
sub eax,4
mov topPx,eax
mov eax,viewEye3D
sub eax,centreZ
add eax,boundRadius
imul eax,SCREEN_H
cdq
idiv centreDepth
add eax,viewHorizon3D
add eax,4
mov bottomPx,eax
cmp leftPx,0
jge @F
mov leftPx,0
@@:
cmp rightPx,SCREEN_W-1
jle @F
mov rightPx,SCREEN_W-1
@@:
cmp topPx,0
jge @F
mov topPx,0
@@:
cmp bottomPx,SCREEN_H-1
jle @F
mov bottomPx,SCREEN_H-1
@@:
mov eax,leftPx
cmp eax,rightPx
jge bp_shape_done
mov eax,topPx
cmp eax,bottomPx
jge bp_shape_done
mov eax,playerX
sub eax,centreX
mov worldOX,eax
mov eax,playerY
sub eax,centreY
mov worldOY,eax
mov eax,viewEye3D
sub eax,centreZ
mov worldOZ,eax
fild worldOX
fimul bpRenderUX3D
fild worldOY
fimul bpRenderUY3D
faddp st(1),st
fdiv roofFloatFP3D
fidiv radiusX
fstp ox
fild worldOX
fimul bpRenderVX3D
fild worldOY
fimul bpRenderVY3D
faddp st(1),st
fild worldOZ
fimul bpRenderVZ3D
faddp st(1),st
fdiv roofFloatFP3D
fidiv radiusY
fstp oy
fild worldOX
fimul bpRenderNX3D
fild worldOY
fimul bpRenderNY3D
faddp st(1),st
fild worldOZ
fimul bpRenderNZ3D
faddp st(1),st
fdiv roofFloatFP3D
fidiv radiusZ
fstp oz
fld ox
fmul st,st
fld oy
fmul st,st
faddp st(1),st
fld oz
fmul st,st
faddp st(1),st
fld1
fsubp st(1),st
fstp quadraticC
mov eax,topPx
mov row,eax
bp_shape_row:
mov eax,viewHorizon3D
sub eax,row
mov temp,eax
fild temp
fmul roofFloatFP3D
fdiv roofFloat4803D
fstp worldRZ
mov eax,row
imul eax,SCREEN_W*4
mov ebx,leftPx
lea edi,[eax+ebx*4]
mov column,ebx
bp_shape_pixel:
mov ebx,column
fild cityRayX3D[ebx*4]
fimul bpRenderUX3D
fild cityRayY3D[ebx*4]
fimul bpRenderUY3D
faddp st(1),st
fdiv roofFloatFP3D
fidiv radiusX
fstp rayX
fild cityRayX3D[ebx*4]
fimul bpRenderVX3D
fild cityRayY3D[ebx*4]
fimul bpRenderVY3D
faddp st(1),st
fld worldRZ
fimul bpRenderVZ3D
faddp st(1),st
fdiv roofFloatFP3D
fidiv radiusY
fstp rayY
fild cityRayX3D[ebx*4]
fimul bpRenderNX3D
fild cityRayY3D[ebx*4]
fimul bpRenderNY3D
faddp st(1),st
fld worldRZ
fimul bpRenderNZ3D
faddp st(1),st
fdiv roofFloatFP3D
fidiv radiusZ
fstp rayZ
fld rayX
fmul st,st
fld rayY
fmul st,st
faddp st(1),st
fld rayZ
fmul st,st
faddp st(1),st
fstp quadraticA
fld rayX
fmul ox
fld rayY
fmul oy
faddp st(1),st
fld rayZ
fmul oz
faddp st(1),st
fstp quadraticB
fld quadraticB
fmul st,st
fld quadraticA
fmul quadraticC
fsubp st(1),st
fst discriminant
ftst
fnstsw ax
sahf
fstp st(0)
jb bp_shape_next
fld discriminant
fsqrt
fadd quadraticB
fchs
fdiv quadraticA
fst hitTime
fmul roofFloatFP3D
fistp pixelDepth
mov eax,pixelDepth
cmp eax,4096
jle bp_shape_next
cmp eax,cityDepthBuffer3D[edi]
jae bp_shape_next
fld hitTime
fmul rayX
fadd ox
fld1
faddp st(1),st
fmul roofFloatHalfTex3D
fistp texU
fld hitTime
fmul rayZ
fadd oz
fchs
fld1
faddp st(1),st
fmul roofFloatHalfTex3D
fistp texV
fld hitTime
fmul rayZ
fadd oz
fmul roofFloatShade3D
fadd roofFloatAmbient3D
fistp shade
mov eax,texV
and eax,255
mov ecx,texHeight
imul eax,ecx
shr eax,8
imul eax,texWidth
mov edx,texU
and edx,255
imul edx,texWidth
shr edx,8
add eax,edx
mov esi,texture
mov eax,[esi+eax*4]
mov color,eax
movzx eax,al
imul eax,shade
shr eax,8
mov ebx,eax
mov eax,color
shr eax,8
and eax,255
imul eax,shade
and eax,0FF00h
or ebx,eax
mov eax,color
shr eax,16
and eax,255
imul eax,shade
and eax,0FF00h
shl eax,8
or eax,ebx
cmp roofCityPass3D,0
je bp_shape_color_ready
invoke ShadeRoofCityPixel3D,eax,pixelDepth,256,material
mov ecx,edi
shr ecx,2
mov edx,roofPixelGlow3D
mov roofGlowMask3D[ecx],dl
bp_shape_color_ready:
mov screenBuffer[edi],eax
cmp hallLevel3D,HALL_ROOF
jne @F
mov ecx,edi
shr ecx,2
mov edx,roofCityPass3D
mov roofCityMask3D[ecx],dl
@@:
mov eax,pixelDepth
mov cityDepthBuffer3D[edi],eax
bp_shape_next:
add edi,4
inc column
mov eax,column
cmp eax,rightPx
jbe bp_shape_pixel
inc row
mov eax,row
cmp eax,bottomPx
jbe bp_shape_row
bp_shape_done:
ret
DrawBowlingEllipsoid3D ENDP

View file

@ -0,0 +1,204 @@
InitBowlingSetterSurface3D PROC uses ebx esi edi
lea edi,bpSetterSurface3D
xor esi,esi
bp_setter_surface_row:
xor ebx,ebx
bp_setter_surface_pixel:
mov eax,esi
and eax,15
mov edx,002C363Dh
test eax,eax
jnz @F
mov edx,00141B20h
@@:
cmp eax,1
jne @F
mov edx,00414C54h
@@:
mov eax,ebx
and eax,3
imul eax,00010101h
add eax,edx
or eax,0FF000000h
stosd
inc ebx
cmp ebx,64
jb bp_setter_surface_pixel
inc esi
cmp esi,64
jb bp_setter_surface_row
ret
InitBowlingSetterSurface3D ENDP
; Smooth motor travel, clamped at both mechanical stops.
BowlingPinsetterEase3D PROC ticks:DWORD,duration:DWORD
local phase:DWORD
mov eax,ticks
test eax,eax
jle bp_ease_zero
cmp eax,duration
jae bp_ease_full
imul eax,FP_1
cdq
idiv duration
mov phase,eax
imul eax
shrd eax,edx,16
mov ecx,3*FP_1
sub ecx,phase
sub ecx,phase
imul ecx
shrd eax,edx,16
ret
bp_ease_zero:
xor eax,eax
ret
bp_ease_full:
mov eax,FP_1
ret
BowlingPinsetterEase3D ENDP
; Strings stay invisible; the guard closes before the hoist starts.
BowlingPinsetterGate3D PROC
cmp bpState3D,BP_RESET
jne bp_gate_open
mov eax,bpTime3D
cmp eax,4400
jae bp_gate_open
cmp eax,400
jb bp_gate_closing
sub eax,4000
invoke BowlingPinsetterEase3D,eax,400
mov ecx,FP_1*87/100
imul ecx
shrd eax,edx,16
add eax,FP_1*13/100
ret
bp_gate_closing:
invoke BowlingPinsetterEase3D,eax,400
mov ecx,FP_1*87/100
imul ecx
shrd eax,edx,16
neg eax
add eax,FP_1
ret
bp_gate_open:
mov eax,FP_1
ret
BowlingPinsetterGate3D ENDP
; All physical pins remain present aloft, including the knocked-down ones.
BowlingPinsetterPose3D PROC uses ebx esi edi pin:DWORD
local progress:DWORD
mov esi,pin
mov edi,bpLane3D
mov ebx,bwRack3D[edi*4]
mov eax,bpPinX3D[esi*4]
mov bpVisualPinX3D,eax
mov eax,bpPinY3D[esi*4]
mov bpVisualPinY3D,eax
mov eax,bpPinTilt3D[esi*4]
mov bpVisualPinTilt3D,eax
mov bpVisualPinLift3D,0
cmp bpState3D,BP_RESET
jne bp_setter_mask
cmp bwNewRack3D[edi*4],0
je @F
mov ebx,1023
@@:
mov eax,bpTime3D
cmp eax,2200
jae bp_setter_lower
sub eax,400
invoke BowlingPinsetterEase3D,eax,1300
mov progress,eax
mov ecx,FP_1*11/10
imul ecx
shrd eax,edx,16
mov bpVisualPinLift3D,eax
mov eax,bwPinX3D[esi*4]
sub eax,bpVisualPinX3D
imul progress
shrd eax,edx,16
add bpVisualPinX3D,eax
mov eax,bwPinY3D[esi*4]
sub eax,bpVisualPinY3D
imul progress
shrd eax,edx,16
add bpVisualPinY3D,eax
mov eax,FP_1
sub eax,progress
imul bpVisualPinTilt3D
shrd eax,edx,16
mov bpVisualPinTilt3D,eax
jmp bp_setter_mask
bp_setter_lower:
cmp bwNewRack3D[edi*4],0
jne bp_setter_new_pose
bt bpHitMask3D,esi
jnc bp_setter_new_pose
mov bpVisualPinLift3D,FP_1*11/10
mov bpVisualPinTilt3D,0
mov eax,bwPinX3D[esi*4]
mov bpVisualPinX3D,eax
mov eax,bwPinY3D[esi*4]
mov bpVisualPinY3D,eax
jmp bp_setter_mask
bp_setter_new_pose:
mov eax,bwPinX3D[esi*4]
mov bpVisualPinX3D,eax
mov eax,bwPinY3D[esi*4]
mov bpVisualPinY3D,eax
mov bpVisualPinTilt3D,0
mov eax,bpTime3D
sub eax,2200
invoke BowlingPinsetterEase3D,eax,1500
neg eax
add eax,FP_1
mov ecx,FP_1*11/10
imul ecx
shrd eax,edx,16
mov bpVisualPinLift3D,eax
bp_setter_mask:
bt ebx,esi
setc al
movzx eax,al
ret
BowlingPinsetterPose3D ENDP
RenderBowlingPinsetter3D PROC uses ebx esi
local lo:DWORD,hi:DWORD,light:DWORD,gate:DWORD,top:DWORD
mov eax,bpCentre3D
sub eax,FP_1*82/100
mov lo,eax
add eax,FP_1*164/100
mov hi,eax
invoke DrawRoofSolid3D,lo,hi,FP_1*28/10,FP_1*52/10,FP_1,FP_1*16/10,254
mov eax,lo
add eax,FP_1*6/100
invoke DrawRoofSolid3D,lo,eax,FP_1*52/10,FP_1*53/10,0,FP_1*16/10,238
mov eax,hi
sub eax,FP_1*6/100
invoke DrawRoofSolid3D,eax,hi,FP_1*52/10,FP_1*53/10,0,FP_1*16/10,238
call BowlingPinsetterGate3D
mov gate,eax
add eax,FP_1*9/10
mov top,eax
invoke DrawRoofSolid3D,lo,hi,FP_1*53/10,FP_1*534/100,gate,top,254
mov eax,gate
add eax,FP_1*4/100
invoke DrawCityVertical3D,1,FP_1*535/100,lo,hi,gate,eax,238
mov light,242
cmp bpState3D,BP_RESET
jne @F
test bpTime3D,100h
jz @F
mov light,243
@@:
mov eax,bpCentre3D
sub eax,FP_1/10
mov ebx,bpCentre3D
add ebx,FP_1/10
invoke DrawCityVertical3D,1,FP_1*536/100,eax,ebx,FP_1*12/10,FP_1*124/100,light
ret
RenderBowlingPinsetter3D ENDP

View file

@ -23,6 +23,8 @@ BeginBowlingPlay3D PROC uses esi lane:DWORD
mov paused3D,0
mov bwSeat3D,0
call ResetBowlingPlay3D
mov bpState3D,BP_SELECT_BALL
call BowlingAudioOpen3D
mov eax,1
ret
bp_begin_no:
@ -56,6 +58,8 @@ ResetBowlingPlay3D PROC uses ebx esi edi
mov bpScoreOpen3D,0
mov bpLast3D,0
mov bpLastKind3D,0
mov bpReactionTime3D,0
mov bpAfterReturn3D,BP_DIRECTION
call BowlingPlayRack3D
call ClearKeys3D
mov mouseClick3D,0
@ -77,6 +81,7 @@ BowlingPlayStartAim3D ENDP
LeaveBowlingPlay3D PROC uses esi
cmp bpActive3D,0
je bp_leave_done
call BowlingAudioClose3D
mov esi,bpLane3D
mov bwBusy3D[esi*4],0
mov bwRack3D[esi*4],1023
@ -109,180 +114,78 @@ bp_rack_pin:
mov bpPinY3D[esi*4],eax
mov bpPinVX3D[esi*4],0
mov bpPinVY3D[esi*4],0
mov bpPinTilt3D[esi*4],0
mov bpPinTiltV3D[esi*4],0
mov bpPinAxisX3D[esi*4],0
mov bpPinAxisY3D[esi*4],-FP_1
mov bpPinSpin3D[esi*4],0
mov bpPinContactTick3D[esi*4],0
mov bpRadius3D[esi*4],24
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
mov bpPhysicsTicks3D,0
mov bpEndTicks3D,0
mov bpImpactEnergy3D,0
mov bpImpactCooldown3D,0
mov bpAudioReleased3D,0
mov bpBallX3D,FP_1/2
mov bpBallY3D,24*FP_1
ret
BowlingPlayRack3D ENDP
BowlingPlayLaunch3D PROC
local speed:DWORD,aimX:DWORD,aimLength:REAL8
mov bpState3D,BP_RELEASE
mov bpTime3D,0
mov bpAccumulator3D,0
call BowlingApplyBallMass3D
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 speed,eax
; The gauge fixes launch work, calibrated to the existing six-kilogram ball.
; v = v_reference * sqrt(m_reference / m); no mass-dependent aim noise.
fild bpReferenceKg3D
fidiv bpShotWeightKg3D
fsqrt
fimul speed
fist bpLaunchSpeed3D
fimul bpTickMs3D
fidiv bpSecondMs3D
fistp speed
mov eax,bpLockedAim3D
imul ecx
mov ecx,18*FP_1
idiv ecx
mov bpBallVX3D,eax
sub eax,bpBallX3D
mov aimX,eax
fild aimX
fmul st,st
fild bpLaneDistance3D
fmul st,st
faddp st(1),st
fsqrt
fstp aimLength
fild aimX
fdiv aimLength
fimul speed
fistp bpBallVX3D
fild bpLaneDistance3D
fdiv aimLength
fimul speed
fchs
fistp bpBallVY3D
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
include src_graphics\bowling_physics.inc
include src_graphics\bowling_audio.inc
include src_graphics\bowling_return.inc
include src_graphics\bowling_weight.inc
BowlingPlayFinishRoll3D PROC uses esi
mov eax,bpHitMask3D
@ -296,6 +199,12 @@ bp_finish_count:
jmp bp_finish_count
bp_finish_register:
mov bpLast3D,ecx
mov bpReaction3D,CAST_TALK
cmp ecx,7
jb @F
mov bpReaction3D,CAST_CHEER
@@:
mov bpReactionTime3D,6500
mov bpLastKind3D,0
mov esi,bpLane3D
cmp bwBall3D[esi*4],1
@ -313,7 +222,17 @@ bp_finish_strike:
jne bp_finish_register_score
mov bpLastKind3D,1
bp_finish_register_score:
invoke BowlingRegisterRoll3D,bpLane3D,ecx
cmp bpLastKind3D,0
je @F
mov bpReaction3D,CAST_CHEER
@@:
cmp bpReaction3D,CAST_CHEER
jne @F
cmp bpAudioReady3D,0
je @F
invoke mciSendStringA,OFFSET bpClapPlay,0,0,0
@@:
invoke BowlingRegisterRoll3D,bpLane3D,bpLast3D
mov bpState3D,BP_SETTLE
mov bpTime3D,0
mov bwMonitorClock3D,-1
@ -356,7 +275,16 @@ bp_next_ready:
BowlingPlayNext3D ENDP
HandleBowlingPlay3D PROC uses ebx esi
local click:DWORD,elapsed:DWORD
local click:DWORD,elapsed:DWORD,mousePress:DWORD
mov eax,deltaTime3D
sub bpImpactCooldown3D,eax
jns @F
mov bpImpactCooldown3D,0
@@:
sub bpReactionTime3D,eax
jns @F
mov bpReactionTime3D,0
@@:
invoke Pressed3D,9
test eax,eax
jz @F
@ -364,6 +292,7 @@ HandleBowlingPlay3D PROC uses ebx esi
@@:
mov eax,mouseClick3D
mov click,eax
mov mousePress,eax
mov mouseClick3D,0
cmp click,0
je @F
@ -383,6 +312,7 @@ HandleBowlingPlay3D PROC uses ebx esi
cmp click,0
je bp_handle_done
call ResetBowlingPlay3D
mov bpState3D,BP_SELECT_BALL
ret
bp_handle_clock:
mov eax,deltaTime3D
@ -393,8 +323,23 @@ bp_handle_clock:
mov elapsed,eax
add bpClock3D,eax
add bpTime3D,eax
cmp bpState3D,BP_SELECT_BALL
jne @F
cmp mousePress,0
jne bp_handle_rack_mouse
cmp click,0
je bp_handle_rack_mouse
mov bpState3D,BP_DIRECTION
ret
bp_handle_rack_mouse:
invoke HandleBowlingWeight3D,mousePress
jmp bp_handle_done
@@:
cmp bpState3D,BP_DIRECTION
jne bp_handle_power
invoke HandleBowlingWeight3D,mousePress
test eax,eax
jnz bp_handle_done
mov eax,bpClock3D
xor edx,edx
mov ecx,4
@ -437,7 +382,7 @@ bp_handle_release:
cmp bpState3D,BP_RELEASE
jne bp_handle_roll
mov esi,bpLane3D
mov ecx,350
mov ecx,BP_RELEASE_MS
cmp bwTurn3D[esi*4],0
je @F
mov ecx,2400
@ -457,25 +402,52 @@ bp_handle_fixed_step:
jb bp_handle_roll_end
sub bpAccumulator3D,16
call BowlingPlayStep3D
cmp bpBallY3D,3*FP_1
jl bp_handle_impact_done
call BowlingPlayReady3D
test eax,eax
jnz 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
jmp bp_handle_done
bp_handle_impact_done:
call BowlingPlayFinishRoll3D
ret
bp_handle_settle:
cmp bpState3D,BP_SETTLE
jne bp_handle_bot
cmp bpTime3D,1800
jne bp_handle_reset
cmp bpTime3D,1600
jb bp_handle_done
call BowlingPlayNext3D
cmp bpHitMask3D,0
jne @F
mov esi,bpLane3D
cmp bwNewRack3D[esi*4],0
jne @F
call CompleteBowlingRoll3D
ret
@@:
mov bpState3D,BP_RESET
mov bpTime3D,0
cmp bpAudioReady3D,0
je bp_handle_done
invoke mciSendStringA,OFFSET bpResetPlay,0,0,0
jmp bp_handle_done
bp_handle_reset:
cmp bpState3D,BP_RESET
jne bp_handle_bot
cmp bpTime3D,BP_RESET_MS
jb bp_handle_done
call CompleteBowlingRoll3D
ret
bp_handle_bot:
cmp bpState3D,BP_RETURN
jne bp_handle_bot_ready
cmp bpTime3D,BP_RETURN_MS
jb bp_handle_done
mov eax,bpAfterReturn3D
mov bpState3D,eax
mov bpTime3D,0
call ClearKeys3D
jmp bp_handle_done
bp_handle_bot_ready:
cmp bpState3D,BP_BOT
jne bp_handle_done
cmp bpTime3D,1100
@ -501,6 +473,7 @@ bp_handle_bot:
mov bpLockedPower3D,eax
call BowlingPlayLaunch3D
bp_handle_done:
call BowlingAudioUpdate3D
ret
HandleBowlingPlay3D ENDP

View file

@ -7,6 +7,37 @@ BP_ROLL equ 3
BP_SETTLE equ 4
BP_BOT equ 5
BP_FINISHED equ 6
BP_RESET equ 7
BP_RETURN equ 8
BP_SELECT_BALL equ 9
BP_RELEASE_MS equ 700
BP_RESET_MS equ 5200
BP_RETURN_MS equ 2800
BP_BODY_COUNT equ 11
BP_BALL_BODY equ 10
BP_PIN_GRAMS equ 1600
PoolSqrt3D PROTO :DWORD
BowlingPlayContact3D PROTO :DWORD,:DWORD
BowlingPlayKick3D PROTO :DWORD,:DWORD,:DWORD
NormalizeBowlingPinAxis3D PROTO :DWORD
BowlingPlayReady3D PROTO
BowlingPlayTether3D PROTO :DWORD
BowlingPlayDeckBounds3D PROTO :DWORD
BowlingRollGlyph3D PROTO :DWORD,:DWORD,:DWORD
BowlingAudioOpen3D PROTO
BowlingAudioClose3D PROTO
BowlingAudioUpdate3D PROTO
DrawBowlingPlayerHand3D PROTO
RenderBowlingReactions3D PROTO
RenderBowlingPinsetter3D PROTO
BowlingPinsetterPose3D PROTO :DWORD
BowlingPinsetterEase3D PROTO :DWORD,:DWORD
BowlingPinsetterGate3D PROTO
CompleteBowlingRoll3D PROTO
BowlingReturnCamera3D PROTO :DWORD
BowlingReturnBallPose3D PROTO
RenderBowlingReturnMachine3D PROTO :DWORD
RenderBowlingReturnedBall3D PROTO
BeginBowlingPlay3D PROTO :DWORD
LeaveBowlingPlay3D PROTO
ResetBowlingPlay3D PROTO
@ -23,7 +54,17 @@ DrawBowlingPlayPin3D PROTO :DWORD,:DWORD
BowlingFrameTotal3D PROTO :DWORD,:DWORD
DrawBowlingPlayScores3D PROTO
InitBowlingBallSurface3D PROTO
InitBowlingSetterSurface3D PROTO
DrawBowlingGaugeLine3D PROTO :DWORD,:DWORD,:DWORD,:DWORD
BowlingApplyBallMass3D PROTO
HandleBowlingWeight3D PROTO :DWORD
DrawBowlingWeight3D PROTO
BowlingBallMaterial3D PROTO :DWORD
BowlingRackCamera3D PROTO
RenderBowlingBallRack3D PROTO :DWORD,:DWORD
BowlingRackScreen3D PROTO :DWORD
BowlingTintBallPixel3D PROTO :DWORD,:DWORD
BowlingTintHand3D PROTO :DWORD,:DWORD
.data
bpActive3D dd 0
bpLane3D dd 1
@ -35,6 +76,23 @@ bpAim3D dd 0
bpPower3D dd 50
bpLockedAim3D dd 0
bpLockedPower3D dd 50
bpWeightKg3D dd 6
bpBotWeightKg3D dd 6
bpShotWeightKg3D dd 6
bpBallGrams3D dd 6000
bpLaunchSpeed3D dd 0
bpReferenceKg3D dd 6
bpLaneDistance3D dd FP_1*193/10
bpTickMs3D dd 16
bpSecondMs3D dd 1000
bpRackHitX3D dd 4 dup(0)
bpRackHitY3D dd 4 dup(0)
bpRackHitRadius3D dd 4 dup(0)
bpRackHandFrame3D dd -1
bpRackHandKg3D dd -1
bpTintedHand3D AnimPack3D <>
bpBallMaterials3D dd 255,256,253,257
bpBallColors3D dd 0057C78Ch,00D26B65h,005B8DE8h,00D8AB55h
bpLast3D dd 0
bpLastKind3D dd 0
bpGutter3D dd 0
@ -45,19 +103,66 @@ 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)
; Inverse masses are Q16, normalized to a 1600-gram pin. Last body is the ball.
bpPinX3D dd BP_BODY_COUNT dup(0)
bpPinY3D dd BP_BODY_COUNT dup(0)
bpPinVX3D dd BP_BODY_COUNT dup(0)
bpPinVY3D dd BP_BODY_COUNT dup(0)
bpInvMass3D dd 10 dup(FP_1),BP_PIN_GRAMS*FP_1/6000
bpRadius3D dd 10 dup(24),31
bpPinTilt3D dd 10 dup(0)
bpPinTiltV3D dd 10 dup(0)
bpPinAxisX3D dd 10 dup(0)
bpPinAxisY3D dd 10 dup(-FP_1)
bpPinSpin3D dd 10 dup(0)
bpPinContactTick3D dd 10 dup(0)
bpPhysicsTicks3D dd 0
bpEndTicks3D dd 0
bpImpactEnergy3D dd 0
bpImpactCooldown3D dd 0
bpReaction3D dd 0
bpReactionTime3D dd 0
bpAudioReady3D dd 0
bpAudioRolling3D dd 0
bpAudioReturning3D dd 0
bpAudioReleased3D dd 0
bpAudioVolume3D dd -1
bpHandArt3D AnimPack3D <>
bpHandPath3D db "assets\animations\bowling\player-underarm-v2.bin",0
bpGlyph3D db 0,0
bpPinSectionHeight3D dd FP_1*14/100,FP_1*27/100,FP_1*285/1000,FP_1*35/100
bpPinSectionRadius3D dd FP_1*90/1000,FP_1*37/1000,FP_1*39/1000,FP_1*55/1000
bpPinSectionLength3D dd FP_1*145/1000,FP_1*65/1000,FP_1*16/1000,FP_1*65/1000
bpPinSectionMaterial3D dd 239,239,240,239
bpRenderNX3D dd 0
bpRenderNY3D dd 0
bpRenderNZ3D dd FP_1
bpRenderUX3D dd FP_1
bpRenderUY3D dd 0
bpRenderVX3D dd 0
bpRenderVY3D dd FP_1
bpRenderVZ3D dd 0
bpVisualPinX3D dd 0
bpVisualPinY3D dd 0
bpVisualPinTilt3D dd 0
bpVisualPinLift3D dd 0
bpAfterReturn3D dd BP_DIRECTION
bpReturnedX3D dd 0
bpReturnedY3D dd 0
bpReturnedZ3D dd 0
bpReturnedPhase3D dd 0
include src_graphics\bowling_audio_data.inc
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
dd OFFSET bpResultText,OFFSET bpBot,OFFSET bpScoreText,OFFSET bpResetText,OFFSET bpReturnText,OFFSET bpSelectBallText
.data?
bpBallSurface3D dd 64*64 dup(?)
bpBallSurface3D dd 4*64*64 dup(?)
bpTintedHandPixels3D dd 256*256 dup(?)
bpSetterSurface3D 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
@ -77,4 +182,15 @@ 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
bpResetText db 211,209,210,192,205,206,194,202,192,0
bpReturnText db 194,206,199,194,208,192,210,32,216,192,208,192,0
bpKilogramsText db ' ',202,195,0
bpKg4Text db '4 ',202,195,0
bpKg5Text db '5 ',202,195,0
bpKg6Text db '6 ',202,195,0
bpKg7Text db '7 ',202,195,0
bpWeightLabels3D dd OFFSET bpKg4Text,OFFSET bpKg5Text,OFFSET bpKg6Text,OFFSET bpKg7Text
bpSelectBallText db 194,219,193,206,208,32,216,192,208,192,0
bpTakeBallText db 194,199,223,210,220,0
bpChangeBallText db 216,192,208,0
.code

View file

@ -34,44 +34,97 @@ bp_ball_tex_pixel:
inc esi
cmp esi,64
jb bp_ball_tex_row
xor esi,esi
bp_ball_tex_variants:
mov eax,bpBallSurface3D[esi*4]
invoke BowlingTintBallPixel3D,eax,4
mov bpBallSurface3D[esi*4+64*64*4],eax
mov eax,bpBallSurface3D[esi*4]
invoke BowlingTintBallPixel3D,eax,5
mov bpBallSurface3D[esi*4+2*64*64*4],eax
mov eax,bpBallSurface3D[esi*4]
invoke BowlingTintBallPixel3D,eax,7
mov bpBallSurface3D[esi*4+3*64*64*4],eax
inc esi
cmp esi,64*64
jb bp_ball_tex_variants
ret
InitBowlingBallSurface3D ENDP
include src_graphics\bowling_pin_shape.inc
include src_graphics\bowling_pinsetter.inc
DrawBowlingPlayPin3D PROC uses ebx esi edi pin:DWORD,centre:DWORD
local x:DWORD,y:DWORD,z:DWORD,headY:DWORD,ry:DWORD,rz:DWORD
local baseX:DWORD,baseY:DWORD,axisX:DWORD,axisY:DWORD,axisZ:DWORD
local x:DWORD,y:DWORD,z:DWORD,radius:DWORD
mov esi,pin
mov ecx,esi
mov ebx,1
shl ebx,cl
mov edi,bpLane3D
test bwRack3D[edi*4],ebx
invoke BowlingPinsetterPose3D,esi
test eax,eax
jz bp_pin_done
mov eax,bpPinX3D[esi*4]
mov eax,bpVisualPinX3D
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
mov baseX,eax
mov eax,bpVisualPinY3D
mov baseY,eax
mov eax,bpVisualPinTilt3D
shr eax,8
cmp eax,128
jbe @F
mov eax,128
@@:
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
mov edi,tableSin[eax*4]
mov ecx,edi
neg ecx
mov bpRenderVZ3D,ecx
mov eax,tableCos[eax*4]
mov axisZ,eax
mov bpRenderNZ3D,eax
imul bpPinAxisX3D[esi*4]
shrd eax,edx,16
mov bpRenderVX3D,eax
mov eax,axisZ
imul bpPinAxisY3D[esi*4]
shrd eax,edx,16
mov bpRenderVY3D,eax
mov eax,bpPinAxisY3D[esi*4]
mov bpRenderUX3D,eax
mov eax,bpPinAxisX3D[esi*4]
neg eax
mov bpRenderUY3D,eax
mov eax,bpPinAxisX3D[esi*4]
imul edi
shrd eax,edx,16
mov axisX,eax
mov bpRenderNX3D,eax
mov eax,bpPinAxisY3D[esi*4]
imul edi
shrd eax,edx,16
mov axisY,eax
mov bpRenderNY3D,eax
xor ebx,ebx
bp_pin_section:
mov eax,bpPinSectionHeight3D[ebx*4]
imul axisX
shrd eax,edx,16
add eax,baseX
mov x,eax
mov eax,bpPinSectionHeight3D[ebx*4]
imul axisY
shrd eax,edx,16
add eax,baseY
mov y,eax
mov eax,bpPinSectionHeight3D[ebx*4]
imul axisZ
shrd eax,edx,16
add eax,FP_1*8/100
add eax,bpVisualPinLift3D
mov z,eax
mov eax,bpPinSectionRadius3D[ebx*4]
mov radius,eax
invoke DrawBowlingEllipsoid3D,x,y,z,radius,radius,bpPinSectionLength3D[ebx*4],bpPinSectionMaterial3D[ebx*4]
inc ebx
cmp ebx,4
jb bp_pin_section
bp_pin_done:
ret
DrawBowlingPlayPin3D ENDP
@ -126,59 +179,7 @@ bp_frame_pending:
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
include src_graphics\bowling_scorecard.inc
DrawBowlingGaugeLine3D PROC uses ebx esi edi x0:DWORD,y0:DWORD,x1:DWORD,y1:DWORD
local px:DWORD,py:DWORD,stepX:DWORD,stepY:DWORD
@ -235,6 +236,8 @@ bp_gauge_line:
ret
DrawBowlingGaugeLine3D ENDP
include src_graphics\bowling_performance.inc
RenderBowlingPlay3D PROC uses ebx esi edi
local x:DWORD,y:DWORD,color:DWORD,owner:DWORD,startY:DWORD
mov eax,bpCentre3D
@ -252,6 +255,8 @@ RenderBowlingPlay3D PROC uses ebx esi edi
cmp bpState3D,BP_ROLL
je bp_camera_follow
cmp bpState3D,BP_SETTLE
je bp_camera_follow
cmp bpState3D,BP_RESET
jne bp_camera_ready
bp_camera_follow:
mov eax,bpBallY3D
@ -262,6 +267,40 @@ bp_camera_follow:
@@:
mov playerY,eax
mov viewEye3D,FP_1*7/10
cmp bpState3D,BP_RESET
jne bp_camera_roll_blend
mov ebx,bpTime3D
cmp ebx,400
jbe @F
mov ebx,400
@@:
imul eax,ebx,2*FP_1
cdq
mov ecx,400
idiv ecx
add playerY,eax
mov eax,playerY
mov ebx,bpTime3D
sub ebx,BP_RESET_MS-800
jle bp_camera_ready
cmp ebx,800
jbe @F
mov ebx,800
@@:
mov edx,startY
sub edx,eax
mov eax,edx
imul eax,ebx
cdq
mov ecx,800
idiv ecx
add playerY,eax
imul eax,ebx,HALL_STANDING_EYE-FP_1*7/10
cdq
idiv ecx
add viewEye3D,eax
jmp bp_camera_ready
bp_camera_roll_blend:
cmp bpState3D,BP_ROLL
jne bp_camera_ready
mov ebx,bpTime3D
@ -274,15 +313,48 @@ bp_camera_follow:
idiv ecx
add eax,startY
mov playerY,eax
imul eax,ebx,FP_1*7/20
mov edx,HALL_STANDING_EYE
cmp bwTurn3D[esi*4],0
jne @F
mov edx,FP_1*55/100
@@:
mov x,edx
mov eax,FP_1*7/10
sub eax,edx
imul eax,ebx
cdq
idiv ecx
add eax,x
mov viewEye3D,eax
bp_camera_ready:
cmp bpState3D,BP_SELECT_BALL
jne @F
call BowlingRackCamera3D
@@:
cmp bpState3D,BP_RETURN
jne @F
invoke BowlingReturnCamera3D,startY
@@:
cmp bpState3D,BP_RELEASE
jne @F
cmp bwTurn3D[esi*4],0
jne @F
mov eax,bpTime3D
cmp eax,BP_RELEASE_MS
jbe bp_camera_release_time
mov eax,BP_RELEASE_MS
bp_camera_release_time:
imul eax,FP_1/2
cdq
mov ecx,BP_RELEASE_MS
idiv ecx
mov edx,HALL_STANDING_EYE
sub edx,eax
mov viewEye3D,edx
bp_camera_ready:
@@:
call UpdateCamera3D
call RenderBowling3D
call RenderBowlingPinsetter3D
mov esi,bpLane3D
cmp bwTurn3D[esi*4],1
jne bp_render_ball
@ -306,12 +378,31 @@ bp_render_bot_draw:
mov bwActor3D.posY,FP_1*235/10
invoke DrawCityObject3D,OFFSET bwActor3D,0
bp_render_ball:
call RenderBowlingReactions3D
cmp bpState3D,BP_SELECT_BALL
je bp_render_hud
cmp bpState3D,BP_FINISHED
je bp_render_hud
cmp bpState3D,BP_ROLL
je bp_render_real_ball
cmp bpState3D,BP_SETTLE
je bp_render_real_ball
cmp bpState3D,BP_RESET
je bp_render_hud
cmp bpState3D,BP_RETURN
je bp_render_hud
cmp bpHandArt3D.pixels,0
jne bp_render_hud
bp_render_real_ball:
cmp bpEndTicks3D,25
jae bp_render_hud
mov eax,bpBallX3D
add eax,bpCentre3D
invoke DrawBowlingBall3D,eax,bpBallY3D,253
mov x,eax
invoke BowlingBallMaterial3D,bpShotWeightKg3D
invoke DrawBowlingBall3D,x,bpBallY3D,eax
bp_render_hud:
call DrawBowlingPlayerHand3D
invoke DrawBox,0,0,320,17,00121B20h
invoke DrawString8x8,8,4,OFFSET bpTitle,00E8D7ADh
mov eax,bpLane3D
@ -338,7 +429,10 @@ bp_render_hud:
mov ebx,owner
mov eax,bwFrames3D[ebx*4]
cmp bpState3D,BP_SETTLE
je bp_frame_result
cmp bpState3D,BP_RESET
jne @F
bp_frame_result:
cmp bwFrameDone3D[esi*4],0
je @F
dec eax
@ -353,12 +447,24 @@ bp_render_hud:
invoke DrawString8x8,116,227,OFFSET bpText3D,00FFFFFFh
cmp bpState3D,BP_DIRECTION
je bp_render_direction
cmp bpState3D,BP_SELECT_BALL
je bp_render_done
cmp bpState3D,BP_POWER
je bp_render_power
cmp bpState3D,BP_SETTLE
je bp_render_result
cmp bpState3D,BP_FINISHED
je bp_render_finished
cmp bpState3D,BP_RESET
jne @F
invoke DrawString8x8,104,197,OFFSET bpResetText,00E8D7ADh
jmp bp_render_done
@@:
cmp bpState3D,BP_RETURN
jne @F
invoke DrawString8x8,104,197,OFFSET bpReturnText,00E8D7ADh
jmp bp_render_done
@@:
invoke DrawString8x8,8,204,OFFSET bpRollText,00E8D7ADh
jmp bp_render_done
bp_render_direction:
@ -403,11 +509,11 @@ bp_render_arc:
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
invoke DrawString8x8,16,62,OFFSET bpPowerText,00E8D7ADh
invoke DrawBoxBorder,30,79,13,124,00121B20h,00AABDB6h,1
invoke DrawBox,32,82,9,118,00D67057h
invoke DrawBox,32,128,9,38,00E7C455h
invoke DrawBox,32,82,9,46,005BC98Ch
mov eax,bpPower3D
imul eax,116
xor edx,edx
@ -416,9 +522,9 @@ bp_render_power:
mov edx,198
sub edx,eax
mov y,edx
invoke DrawBox,270,y,25,2,00FFFFFFh
invoke DrawBox,24,y,25,2,00FFFFFFh
invoke FormatNumber,bpPower3D,OFFSET bpText3D
invoke DrawString8x8,246,180,OFFSET bpText3D,00FFFFFFh
invoke DrawString8x8,50,180,OFFSET bpText3D,00FFFFFFh
jmp bp_render_done
bp_render_result:
mov eax,OFFSET bpResultText
@ -451,6 +557,7 @@ bp_render_finished:
invoke DrawString8x8,88,161,ebx,00E8D7ADh
invoke DrawString8x8,136,187,OFFSET bpAgainText,00FFFFFFh
bp_render_done:
call DrawBowlingWeight3D
cmp bpScoreOpen3D,0
je @F
call DrawBowlingPlayScores3D

View file

@ -158,14 +158,25 @@ 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
invoke DrawBowlingBallAt3D,x,y,FP_1*135/1000,mat,eax
ret
DrawBowlingBall3D ENDP
DrawBowlingBallAt3D PROC uses ebx esi x:DWORD,y:DWORD,z:DWORD,mat:DWORD,phase:DWORD
local holeY:DWORD,holeZ:DWORD
invoke DrawRoofEllipsoid3D,x,y,z,FP_1*12/100,FP_1*12/100,FP_1*12/100,mat
cmp mat,253
je bw_ball_holes
cmp mat,255
jb bw_ball_done
cmp mat,257
ja bw_ball_done
bw_ball_holes:
mov eax,phase
and eax,511
mov eax,tableCos[eax*4]
test eax,eax
jle bw_ball_done
@ -180,7 +191,7 @@ DrawBowlingBall3D PROC uses ebx esi x:DWORD,y:DWORD,mat:DWORD
imul eax,11
cdq
idiv ecx
add eax,FP_1*135/1000
add eax,z
mov holeZ,eax
mov eax,x
sub eax,FP_1*35/1000
@ -193,7 +204,7 @@ DrawBowlingBall3D PROC uses ebx esi x:DWORD,y:DWORD,mat:DWORD
invoke DrawRoofEllipsoid3D,x,holeY,eax,FP_1*18/1000,FP_1*18/1000,FP_1*18/1000,233
bw_ball_done:
ret
DrawBowlingBall3D ENDP
DrawBowlingBallAt3D ENDP
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
@ -528,8 +539,8 @@ 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
cmp esi,3
jb bw_cast_dining_next
@@:
mov eax,bwVisitorX3D[esi*4]
mov bwSmallActor3D.posX,eax
@ -602,32 +613,8 @@ bw_detail_return:
add eax,FP_1*64/100
mov x1,eax
invoke DrawCityVertical3D,1,26*FP_1+512,x0,x1,FP_1/20,FP_1*62/100,241
xor edi,edi
bw_detail_rack_ball:
mov eax,centerX
imul edx,edi,FP_1*23/100
add eax,edx
sub eax,FP_1*23/100
mov ebx,242
test edi,1
jz @F
mov ebx,243
@@:
invoke DrawRoofEllipsoid3D,eax,FP_1*254/10,FP_1*84/100,FP_1*11/100,FP_1*11/100,FP_1*11/100,ebx
mov eax,centerX
imul edx,edi,FP_1*23/100
add eax,edx
sub eax,FP_1*23/100
sub eax,FP_1*35/1000
invoke DrawRoofEllipsoid3D,eax,FP_1*25494/1000,FP_1*905/1000,FP_1*14/1000,FP_1*14/1000,FP_1*14/1000,233
mov eax,centerX
imul edx,edi,FP_1*23/100
add eax,edx
sub eax,FP_1*195/1000
invoke DrawRoofEllipsoid3D,eax,FP_1*25494/1000,FP_1*905/1000,FP_1*14/1000,FP_1*14/1000,FP_1*14/1000,233
inc edi
cmp edi,3
jb bw_detail_rack_ball
invoke RenderBowlingReturnMachine3D,centerX
invoke RenderBowlingBallRack3D,centerX,esi
mov eax,centerX
sub eax,FP_1*27/100
invoke DrawRoofEllipsoid3D,eax,27*FP_1,FP_1*49/100,FP_1*16/100,FP_1*16/100,FP_1*2/100,239
@ -637,6 +624,7 @@ bw_detail_rack_ball:
inc esi
cmp esi,4
jb bw_detail_return
call RenderBowlingReturnedBall3D
xor esi,esi
bw_detail_dining:
mov eax,esi

View file

@ -0,0 +1,145 @@
; Score/turn advancement stays independent of the visible ball-return cycle.
CompleteBowlingRoll3D PROC
call BowlingPlayNext3D
mov eax,bpState3D
mov bpAfterReturn3D,eax
mov bpState3D,BP_RETURN
mov bpTime3D,0
ret
CompleteBowlingRoll3D ENDP
BowlingReturnCamera3D PROC uses ebx esi startY:DWORD
local progress:DWORD
mov eax,bpTime3D
cmp eax,2000
jb bp_return_camera_turn
mov eax,BP_RETURN_MS
sub eax,bpTime3D
invoke BowlingPinsetterEase3D,eax,800
jmp bp_return_camera_pose
bp_return_camera_turn:
invoke BowlingPinsetterEase3D,eax,400
bp_return_camera_pose:
mov progress,eax
mov ebx,FP_1*268/10
sub ebx,startY
imul ebx
shrd eax,edx,16
add eax,startY
mov playerY,eax
mov eax,progress
imul eax,64
sar eax,16
mov esi,bpLane3D
test esi,1
jz @F
neg eax
@@:
add eax,384
mov playerAngle,eax
mov viewEye3D,HALL_STANDING_EYE
ret
BowlingReturnCamera3D ENDP
; Position belongs to the return tray, not to the pin collision solver.
BowlingReturnBallPose3D PROC uses esi
cmp bpState3D,BP_RETURN
jne bp_return_ball_hidden
mov eax,bpTime3D
cmp eax,400
jb bp_return_ball_hidden
cmp eax,2000
jae bp_return_ball_hidden
mov esi,bpLane3D
shr esi,1
imul eax,esi,FP_1*38/10
add eax,FP_1*395/100
mov bpReturnedX3D,eax
mov eax,bpTime3D
sub eax,400
cmp eax,900
jae bp_return_ball_coast
invoke BowlingPinsetterEase3D,eax,900
mov esi,eax
mov ecx,FP_1/2
imul ecx
shrd eax,edx,16
add eax,FP_1*251/10
mov bpReturnedY3D,eax
mov eax,esi
mov ecx,FP_1*22/100
imul ecx
shrd eax,edx,16
add eax,FP_1*62/100
mov bpReturnedZ3D,eax
jmp bp_return_ball_phase
bp_return_ball_coast:
sub eax,900
invoke BowlingPinsetterEase3D,eax,400
mov ecx,FP_1*55/100
imul ecx
shrd eax,edx,16
add eax,FP_1*256/10
mov bpReturnedY3D,eax
mov bpReturnedZ3D,FP_1*84/100
bp_return_ball_phase:
mov eax,bpReturnedY3D
sub eax,FP_1*251/10
imul eax,680
sar eax,16
and eax,511
mov bpReturnedPhase3D,eax
mov eax,1
ret
bp_return_ball_hidden:
xor eax,eax
ret
BowlingReturnBallPose3D ENDP
RenderBowlingReturnMachine3D PROC uses ebx esi centre:DWORD
local lo:DWORD,hi:DWORD
mov eax,centre
sub eax,FP_1*33/100
mov lo,eax
add eax,FP_1*66/100
mov hi,eax
invoke DrawRoofSolid3D,lo,hi,FP_1*248/10,FP_1*253/10,FP_1*68/100,FP_1,254
invoke DrawRoofEllipsoid3D,centre,FP_1*2495/100,FP_1*94/100,FP_1*33/100,FP_1*32/100,FP_1*18/100,254
mov eax,centre
sub eax,FP_1*14/100
mov ebx,centre
add ebx,FP_1*14/100
invoke DrawCityVertical3D,1,FP_1*2531/100,eax,ebx,FP_1*70/100,FP_1*95/100,233
mov esi,-1
bp_return_rail:
imul eax,esi,FP_1/10
add eax,centre
mov ebx,eax
sub eax,FP_1*2/100
add ebx,FP_1*2/100
invoke DrawRoofSolid3D,eax,ebx,FP_1*253/10,FP_1*2635/100,FP_1*70/100,FP_1*75/100,238
add esi,2
cmp esi,1
jle bp_return_rail
mov eax,centre
sub eax,FP_1*21/100
mov ebx,centre
add ebx,FP_1*21/100
mov lo,eax
mov hi,ebx
invoke DrawRoofSolid3D,lo,hi,FP_1*2635/100,FP_1*264/10,FP_1*70/100,FP_1*79/100,254
invoke DrawRoofSolid3D,lo,hi,FP_1*262/10,FP_1*2628/100,FP_1*6/100,FP_1*70/100,254
ret
RenderBowlingReturnMachine3D ENDP
RenderBowlingReturnedBall3D PROC
cmp bpActive3D,0
je bp_return_draw_done
call BowlingReturnBallPose3D
test eax,eax
jz bp_return_draw_done
invoke BowlingBallMaterial3D,bpShotWeightKg3D
invoke DrawBowlingBallAt3D,bpReturnedX3D,bpReturnedY3D,bpReturnedZ3D,eax,bpReturnedPhase3D
bp_return_draw_done:
ret
RenderBowlingReturnedBall3D ENDP

View file

@ -0,0 +1,164 @@
; Return the actual roll offset of a frame, including incomplete frames.
BowlingFrameIndex3D PROC uses ebx esi edi owner:DWORD,wanted:DWORD
mov ebx,owner
imul eax,ebx,96
lea esi,bwRolls3D[eax]
xor edi,edi
xor ecx,ecx
bp_index_frame:
cmp ecx,wanted
je bp_index_ready
cmp edi,bwRollCount3D[ebx*4]
jae bp_index_missing
cmp DWORD PTR [esi+edi*4],10
je @F
inc edi
@@:
inc edi
inc ecx
jmp bp_index_frame
bp_index_ready:
mov eax,edi
ret
bp_index_missing:
mov eax,-1
ret
BowlingFrameIndex3D ENDP
BowlingRollGlyph3D PROC uses ebx esi edi owner:DWORD,frame:DWORD,slot:DWORD
invoke BowlingFrameIndex3D,owner,frame
cmp eax,-1
je bp_glyph_blank
mov edi,eax
mov ebx,owner
imul eax,ebx,96
lea esi,bwRolls3D[eax]
mov ecx,slot
cmp frame,9
je bp_glyph_tenth
cmp ecx,2
jae bp_glyph_blank
cmp DWORD PTR [esi+edi*4],10
jne bp_glyph_available
test ecx,ecx
jnz bp_glyph_blank
jmp bp_glyph_available
bp_glyph_tenth:
cmp ecx,3
jae bp_glyph_blank
cmp ecx,2
jne bp_glyph_available
mov eax,[esi+edi*4]
cmp eax,10
je bp_glyph_available
add eax,[esi+edi*4+4]
cmp eax,10
jne bp_glyph_blank
bp_glyph_available:
add edi,ecx
cmp edi,bwRollCount3D[ebx*4]
jae bp_glyph_blank
mov eax,[esi+edi*4]
test ecx,ecx
jz bp_glyph_digit
cmp ecx,1
jne bp_glyph_bonus_spare
cmp DWORD PTR [esi+edi*4-4],10
je bp_glyph_digit
jmp bp_glyph_spare
bp_glyph_bonus_spare:
cmp DWORD PTR [esi+edi*4-8],10
jne bp_glyph_digit
cmp DWORD PTR [esi+edi*4-4],10
je bp_glyph_digit
bp_glyph_spare:
mov edx,eax
add edx,[esi+edi*4-4]
cmp edx,10
jne bp_glyph_digit
mov eax,'/'
ret
bp_glyph_digit:
cmp eax,10
je bp_glyph_strike
test eax,eax
jz bp_glyph_miss
add eax,'0'
ret
bp_glyph_miss:
mov eax,'-'
ret
bp_glyph_strike:
mov eax,'X'
ret
bp_glyph_blank:
mov eax,' '
ret
BowlingRollGlyph3D ENDP
DrawBowlingPlayScores3D PROC uses ebx esi edi
local x:DWORD,y:DWORD,owner:DWORD,slot:DWORD
invoke DrawBoxBorder,4,47,312,160,00151F26h,005BC9BEh,1
invoke DrawString8x8,12,55,OFFSET bpScoreText,00E8D7ADh
xor esi,esi
bp_card_header:
imul eax,esi,26
add eax,44
mov x,eax
invoke DrawBox,x,88,1,108,00425D64h
lea eax,[esi+1]
invoke FormatNumber,eax,OFFSET bpText3D
invoke DrawString8x8,x,73,OFFSET bpText3D,00AABDB6h
inc esi
cmp esi,10
jb bp_card_header
invoke DrawBox,6,137,308,1,005BC9BEh
xor edi,edi
bp_card_player:
imul eax,edi,54
add eax,95
mov y,eax
mov eax,OFFSET bpYou
test edi,edi
jz @F
mov eax,OFFSET bpBot
@@:
invoke DrawString8x8,8,y,eax,005BC9BEh
mov eax,bpLane3D
shl eax,1
add eax,edi
mov owner,eax
xor esi,esi
bp_card_frame:
mov slot,0
bp_card_roll:
invoke BowlingRollGlyph3D,owner,esi,slot
mov bpGlyph3D,al
imul eax,esi,26
add eax,45
mov ecx,slot
lea eax,[eax+ecx*8]
mov x,eax
invoke DrawString8x8,x,y,OFFSET bpGlyph3D,00E8D7ADh
inc slot
cmp slot,3
jb bp_card_roll
imul eax,esi,26
add eax,45
mov x,eax
invoke BowlingFrameTotal3D,owner,esi
cmp eax,-1
je bp_card_next
invoke FormatNumber,eax,OFFSET bpText3D
mov eax,y
add eax,22
invoke DrawString8x8,x,eax,OFFSET bpText3D,00FFFFFFh
bp_card_next:
inc esi
cmp esi,10
jb bp_card_frame
inc edi
cmp edi,2
jb bp_card_player
ret
DrawBowlingPlayScores3D ENDP

View file

@ -0,0 +1,388 @@
BowlingApplyBallMass3D PROC uses esi
mov esi,bpLane3D
mov eax,bpWeightKg3D
cmp bwTurn3D[esi*4],0
je @F
mov eax,bpBotWeightKg3D
@@:
cmp eax,4
jae @F
mov eax,4
@@:
cmp eax,7
jbe @F
mov eax,7
@@:
mov bpShotWeightKg3D,eax
imul eax,1000
mov bpBallGrams3D,eax
mov ecx,eax
mov eax,BP_PIN_GRAMS*FP_1
xor edx,edx
div ecx
mov bpInvMass3D[BP_BALL_BODY*4],eax
ret
BowlingApplyBallMass3D ENDP
; Selection consumes its own event, never the direction-lock/throw event.
HandleBowlingWeight3D PROC uses ebx esi mousePress:DWORD
xor esi,esi
bp_weight_key:
lea eax,[esi+31h]
invoke Pressed3D,eax
test eax,eax
jnz bp_weight_choose
inc esi
cmp esi,4
jb bp_weight_key
cmp mousePress,0
je bp_weight_no
cmp bpState3D,BP_SELECT_BALL
je bp_weight_rack_hit
cmp mouseY3D,26
jb bp_weight_no
cmp mouseY3D,44
jae bp_weight_no
cmp mouseX3D,8
jb bp_weight_no
cmp mouseX3D,96
jae bp_weight_no
mov bpState3D,BP_SELECT_BALL
mov eax,1
ret
bp_weight_rack_hit:
call BowlingRackCamera3D
call UpdateCamera3D
xor esi,esi
bp_weight_hit:
invoke BowlingRackScreen3D,esi
mov eax,mouseX3D
sub eax,bpRackHitX3D[esi*4]
imul eax,eax
mov ebx,eax
mov eax,mouseY3D
sub eax,bpRackHitY3D[esi*4]
imul eax,eax
add ebx,eax
mov eax,bpRackHitRadius3D[esi*4]
add eax,2
imul eax,eax
cmp ebx,eax
jbe bp_weight_choose
mov eax,bpRackHitX3D[esi*4]
sub eax,18
cmp mouseX3D,eax
jb bp_weight_hit_next
add eax,36
cmp mouseX3D,eax
jae bp_weight_hit_next
mov eax,bpRackHitY3D[esi*4]
add eax,bpRackHitRadius3D[esi*4]
add eax,2
cmp mouseY3D,eax
jb bp_weight_hit_next
add eax,14
cmp mouseY3D,eax
jb bp_weight_choose
bp_weight_hit_next:
inc esi
cmp esi,4
jb bp_weight_hit
; Ignore clicks on the floor instead of accidentally locking the direction.
cmp mouseY3D,193
jb bp_weight_rack_consumed
cmp mouseY3D,213
jae bp_weight_rack_consumed
cmp mouseX3D,128
jb bp_weight_rack_consumed
cmp mouseX3D,192
jae bp_weight_rack_consumed
mov bpState3D,BP_DIRECTION
bp_weight_rack_consumed:
mov eax,1
ret
bp_weight_no:
xor eax,eax
ret
bp_weight_choose:
lea eax,[esi+4]
mov bpWeightKg3D,eax
mov bpState3D,BP_DIRECTION
mov eax,1
ret
HandleBowlingWeight3D ENDP
DrawBowlingWeight3D PROC uses ebx esi edi
local x:DWORD,y:DWORD,edge:DWORD
cmp bpScoreOpen3D,0
jne bp_weight_draw_done
cmp bpState3D,BP_SELECT_BALL
jne bp_weight_locked_label
invoke DrawString8x8,120,32,OFFSET bpSelectBallText,00E8D7ADh
xor esi,esi
bp_weight_rack_label:
invoke BowlingRackScreen3D,esi
mov eax,bpRackHitX3D[esi*4]
sub eax,18
mov x,eax
mov eax,bpRackHitY3D[esi*4]
add eax,bpRackHitRadius3D[esi*4]
add eax,2
mov y,eax
mov edge,003C4D53h
lea eax,[esi+4]
cmp eax,bpWeightKg3D
jne @F
mov eax,bpBallColors3D[esi*4]
mov edge,eax
@@:
invoke DrawBoxBorder,x,y,36,14,00121B20h,edge,1
mov eax,x
add eax,2
mov ebx,y
add ebx,3
invoke DrawString8x8,eax,ebx,bpWeightLabels3D[esi*4],00FFFFFFh
inc esi
cmp esi,4
jb bp_weight_rack_label
invoke DrawBoxBorder,128,193,64,20,00121B20h,005BC9BEh,1
invoke DrawString8x8,136,199,OFFSET bpTakeBallText,00FFFFFFh
ret
bp_weight_locked_label:
mov eax,bpShotWeightKg3D
cmp bpState3D,BP_DIRECTION
je bp_weight_selected_label
cmp bpState3D,BP_POWER
jne @F
bp_weight_selected_label:
mov eax,bpWeightKg3D
@@:
cmp bpState3D,BP_BOT
jne @F
mov eax,bpBotWeightKg3D
@@:
mov esi,eax
invoke FormatNumber,eax,OFFSET bpText3D
invoke AppendCString,OFFSET bpText3D,OFFSET bpKilogramsText
sub esi,4
cmp esi,3
ja bp_weight_draw_done
mov eax,bpBallColors3D[esi*4]
mov edge,eax
cmp bpState3D,BP_DIRECTION
jne bp_weight_static_label
invoke DrawBoxBorder,8,26,88,18,00121B20h,edge,1
invoke DrawString8x8,14,31,OFFSET bpChangeBallText,00E8D7ADh
invoke DrawString8x8,50,31,OFFSET bpText3D,00FFFFFFh
ret
bp_weight_static_label:
invoke DrawBox,8,27,5,7,edge
invoke DrawString8x8,18,26,OFFSET bpText3D,00E8D7ADh
bp_weight_draw_done:
ret
DrawBowlingWeight3D ENDP
BowlingBallMaterial3D PROC kg:DWORD
mov eax,kg
sub eax,4
cmp eax,3
jbe @F
mov eax,2
@@:
mov eax,bpBallMaterials3D[eax*4]
ret
BowlingBallMaterial3D ENDP
BowlingRackCamera3D PROC
mov eax,bpLane3D
shr eax,1
imul eax,FP_1*38/10
add eax,FP_1*585/100
mov playerX,eax
mov playerY,FP_1*2583/100
mov playerAngle,256
mov viewEye3D,FP_1*102/100
ret
BowlingRackCamera3D ENDP
; Labels and picking share the actual world projection, not guessed UI slots.
BowlingRackScreen3D PROC uses ebx esi index:DWORD
mov esi,index
imul ebx,esi,-FP_1*245/1000
add ebx,FP_1*262/10
mov eax,playerX
sub eax,FP_1*135/100
invoke ProjectCityPoint3D,eax,ebx
sar eax,1
mov bpRackHitX3D[esi*4],eax
mov ebx,edx
mov eax,viewEye3D
sub eax,FP_1*84/100
imul eax,SCREEN_H
cdq
idiv ebx
add eax,viewHorizon3D
sar eax,1
mov bpRackHitY3D[esi*4],eax
mov eax,FP_1*12/100*SCREEN_H
cdq
idiv ebx
shr eax,1
mov bpRackHitRadius3D[esi*4],eax
ret
BowlingRackScreen3D ENDP
RenderBowlingBallRack3D PROC uses ebx esi edi centre:DWORD,pairIndex:DWORD
local lo:DWORD,hi:DWORD,x:DWORD,y:DWORD
; Storage runs lengthwise, alongside (not across) the clear return track.
mov eax,centre
add eax,FP_1*43/100
mov lo,eax
add eax,FP_1*4/100
mov hi,eax
invoke DrawRoofSolid3D,lo,hi,FP_1*253/10,FP_1*264/10,FP_1*70/100,FP_1*75/100,238
add lo,FP_1*20/100
add hi,FP_1*20/100
invoke DrawRoofSolid3D,lo,hi,FP_1*253/10,FP_1*264/10,FP_1*70/100,FP_1*75/100,238
mov eax,centre
add eax,FP_1*35/100
mov lo,eax
add eax,FP_1*40/100
mov hi,eax
invoke DrawRoofSolid3D,lo,hi,FP_1*253/10,FP_1*2538/100,FP_1*6/100,FP_1*73/100,254
invoke DrawRoofSolid3D,lo,hi,FP_1*2632/100,FP_1*264/10,FP_1*6/100,FP_1*73/100,254
xor esi,esi
bp_rack_stock:
cmp bpActive3D,0
je bp_rack_stock_draw
mov eax,bpLane3D
shr eax,1
cmp eax,pairIndex
jne bp_rack_stock_draw
cmp bpState3D,BP_SELECT_BALL
je bp_rack_stock_draw
mov eax,bpWeightKg3D
cmp bpState3D,BP_DIRECTION
je @F
cmp bpState3D,BP_POWER
je @F
mov eax,bpShotWeightKg3D
@@:
sub eax,4
cmp eax,esi
je bp_rack_stock_next
bp_rack_stock_draw:
mov eax,centre
add eax,FP_1*55/100
mov x,eax
imul eax,esi,-FP_1*245/1000
add eax,FP_1*262/10
mov y,eax
invoke DrawBowlingBallAt3D,x,y,FP_1*84/100,bpBallMaterials3D[esi*4],45
bp_rack_stock_next:
inc esi
cmp esi,4
jb bp_rack_stock
ret
RenderBowlingBallRack3D ENDP
; Reuse the blue marble's luminance; only its hue changes. Preserve alpha.
BowlingTintBallPixel3D PROC uses ebx ecx pixel:DWORD,kg:DWORD
mov eax,pixel
cmp kg,6
je bp_tint_done
mov ebx,eax
and ebx,255
mov ecx,eax
shr ecx,16
and ecx,255
and eax,0FF00FF00h
cmp kg,4
jne @F
mov edx,pixel
shr edx,8
and edx,255
shl ebx,8
and eax,0FFFF00FFh
or eax,ebx
or eax,edx
shl ecx,16
or eax,ecx
ret
@@:
or eax,ecx
cmp kg,5
jne bp_tint_gold
mov edx,eax
and edx,0000FF00h
shr edx,1
and edx,0000FF00h
and eax,0FFFF00FFh
or eax,edx
bp_tint_gold:
cmp kg,7
jne @F
mov edx,pixel
shr edx,8
and edx,255
add edx,ebx
shr edx,1
shl edx,8
and eax,0FFFF00FFh
or eax,edx
@@:
shl ebx,16
or eax,ebx
bp_tint_done:
ret
BowlingTintBallPixel3D ENDP
; A cached single-frame scratch pack keeps skin, sleeve and source assets intact.
BowlingTintHand3D PROC uses ebx esi edi frame:DWORD,kg:DWORD
mov eax,frame
cmp eax,bpRackHandFrame3D
jne bp_tint_hand_build
mov eax,kg
cmp eax,bpRackHandKg3D
je bp_tint_hand_done
bp_tint_hand_build:
mov eax,frame
mov bpRackHandFrame3D,eax
shl eax,18
add eax,bpHandArt3D.pixels
mov esi,eax
mov eax,kg
mov bpRackHandKg3D,eax
lea edi,bpTintedHandPixels3D
mov ebx,256*256
bp_tint_hand_pixel:
lodsd
cmp kg,6
je bp_tint_hand_store
mov edx,eax
and edx,255
mov ecx,eax
shr ecx,8
and ecx,255
add ecx,24
cmp edx,ecx
jbe bp_tint_hand_store
mov ecx,eax
shr ecx,16
and ecx,255
add ecx,24
cmp edx,ecx
jbe bp_tint_hand_store
invoke BowlingTintBallPixel3D,eax,kg
bp_tint_hand_store:
stosd
dec ebx
jnz bp_tint_hand_pixel
mov bpTintedHand3D.imageWidth,256
mov bpTintedHand3D.height,256
mov bpTintedHand3D.frames,1
mov bpTintedHand3D.bytes,256*256*4
mov bpTintedHand3D.pixels,OFFSET bpTintedHandPixels3D
bp_tint_hand_done:
ret
BowlingTintHand3D ENDP

View file

@ -5,13 +5,14 @@ InitNpcLife3D PROC uses ebx esi edi
mov npcSelected3D,-1
npc_life_init:
imul ebx,esi,SIZEOF NpcLife3D
mov npcLife3D[ebx].identity,esi
mov eax,npcInitialIdentity3D[esi*4]
mov npcLife3D[ebx].identity,eax
mov npcLife3D[ebx].state,NPC_STAND
mov eax,esi
imul eax,900
add eax,1800
mov npcLife3D[ebx].remaining,eax
mov npcLife3D[ebx].routeStep,esi
mov npcLife3D[ebx].routeStep,0
mov npcLife3D[ebx].pathCount,0
mov npcLife3D[ebx].remainder,0
mov npcLife3D[ebx].poseClock,0
@ -19,30 +20,29 @@ npc_life_init:
mov npcWalkVelocityX3D[esi*4],0
mov npcWalkVelocityY3D[esi*4],0
mov npcRise3D[esi*4],0
mov npcSitTime3D[esi*4],0
mov eax,npcStartDest3D[esi*4]
mov npcLife3D[ebx].destination,eax
imul edi,esi,SIZEOF NpcMindEntity
push eax
invoke NpcEntity3D,esi
mov edi,eax
pop eax
mov ecx,npcDestX3D[eax*4]
mov casinoNpcs3D[edi].posX,ecx
mov [edi].NpcMindEntity.posX,ecx
mov ecx,npcDestY3D[eax*4]
mov casinoNpcs3D[edi].posY,ecx
cmp eax,7
je @F
cmp eax,8
jne npc_life_init_next
@@:
mov npcLife3D[ebx].state,NPC_SIT
mov npcLife3D[ebx].poseClock,0
mov [edi].NpcMindEntity.posY,ecx
npc_life_init_next:
inc esi
cmp esi,NPC_LIFE_COUNT
jb npc_life_init
call InitNpcVisits3D
mov npcLifeReady3D,1
ret
InitNpcLife3D ENDP
; BFS visits each tile at most once. Body clearance includes furniture and the stage.
NpcTryCell3D PROC uses ebx esi cell:DWORD,parent:DWORD
NpcTryCell3D PROC uses ebx esi edi cell:DWORD,parent:DWORD
local startX:DWORD,startY:DWORD,endX:DWORD,endY:DWORD,probeX:DWORD
mov esi,cell
cmp esi,NPC_PATH_CAP
jae npc_cell_done
@ -56,9 +56,41 @@ NpcTryCell3D PROC uses ebx esi cell:DWORD,parent:DWORD
shr edx,5
shl edx,16
add edx,FP_HALF
mov endX,eax
mov endY,edx
invoke CanWalk3D,eax,edx
test eax,eax
jz npc_cell_done
; Free tile centres do not imply a free edge beside a chair or thin divider.
mov eax,parent
mov edx,eax
and eax,31
shl eax,16
add eax,FP_HALF
mov startX,eax
shr edx,5
shl edx,16
add edx,FP_HALF
mov startY,edx
mov edi,1
npc_cell_edge:
mov eax,endX
sub eax,startX
imul eax,edi
sar eax,2
add eax,startX
mov probeX,eax
mov eax,endY
sub eax,startY
imul eax,edi
sar eax,2
add eax,startY
invoke CanWalk3D,probeX,eax
test eax,eax
jz npc_cell_done
inc edi
cmp edi,4
jb npc_cell_edge
mov eax,parent
mov npcParents3D[esi*4],eax
mov ebx,npcSearchTail3D
@ -78,11 +110,12 @@ NpcPlanPath3D PROC uses ebx esi edi slot:DWORD,destination:DWORD
jae npc_plan_fail
imul ebx,slot,SIZEOF NpcLife3D
mov npcLife3D[ebx].pathCount,0
imul esi,slot,SIZEOF NpcMindEntity
mov eax,casinoNpcs3D[esi].posY
invoke NpcEntity3D,slot
mov esi,eax
mov eax,[esi].NpcMindEntity.posY
sar eax,16
shl eax,5
mov ecx,casinoNpcs3D[esi].posX
mov ecx,[esi].NpcMindEntity.posX
sar ecx,16
add eax,ecx
cmp eax,NPC_PATH_CAP
@ -190,16 +223,17 @@ npc_space_person:
imul ebx,esi,SIZEOF NpcLife3D
cmp npcLife3D[ebx].state,NPC_AWAY
je npc_space_next
imul ebx,esi,SIZEOF NpcMindEntity
invoke NpcEntity3D,esi
mov ebx,eax
mov eax,px
sub eax,casinoNpcs3D[ebx].posX
sub eax,[ebx].NpcMindEntity.posX
jns @F
neg eax
@@:
cmp eax,FP_1*2/5
jae npc_space_next
mov eax,py
sub eax,casinoNpcs3D[ebx].posY
sub eax,[ebx].NpcMindEntity.posY
jns @F
neg eax
@@:
@ -222,8 +256,8 @@ NpcMove3D PROC uses ebx esi edi slot:DWORD,milliseconds:DWORD
local budget:DWORD,wx:DWORD,wy:DWORD,mind:DWORD,pathBase:DWORD
local oldX:DWORD,oldY:DWORD
imul ebx,slot,SIZEOF NpcLife3D
imul eax,slot,SIZEOF NpcMindEntity
lea esi,casinoNpcs3D[eax]
invoke NpcEntity3D,slot
mov esi,eax
mov mind,esi
mov eax,[esi].NpcMindEntity.posX
mov oldX,eax
@ -234,7 +268,8 @@ NpcMove3D PROC uses ebx esi edi slot:DWORD,milliseconds:DWORD
add eax,OFFSET npcPaths3D
mov pathBase,eax
mov eax,milliseconds
mov ecx,55000
mov ecx,slot
mov ecx,npcVisitSpeed3D[ecx*4]
mul ecx
add eax,npcLife3D[ebx].remainder
adc edx,0
@ -338,28 +373,26 @@ npc_move_y:
cmp npcLife3D[ebx].pathCount,0
jne npc_move_done
npc_move_arrived:
mov eax,npcLife3D[ebx].destination
mov npcLife3D[ebx].state,NPC_STAND
mov npcLife3D[ebx].remaining,3500
cmp eax,0
jne @F
mov npcLife3D[ebx].state,NPC_AWAY
mov npcLife3D[ebx].remaining,4500
jmp npc_move_done
@@:
cmp eax,7
je @F
cmp eax,8
jne npc_move_done
@@:
mov npcLife3D[ebx].state,NPC_SIT
mov npcLife3D[ebx].remaining,12000
mov npcLife3D[ebx].poseClock,0
invoke NpcArrive3D,slot
jmp npc_move_done
npc_move_blocked:
mov eax,slot
add npcVisitBlocked3D[eax*4],250
cmp npcVisitBlocked3D[eax*4],2000
jae npc_move_abandon
mov npcLife3D[ebx].state,NPC_STAND
mov npcVisitActivity3D[eax*4],NV_YIELD
imul eax,23
add eax,200
mov npcLife3D[ebx].remaining,eax
jmp npc_move_done
npc_move_abandon:
invoke NpcReleaseStation3D,slot
mov npcLife3D[ebx].state,NPC_STAND
mov npcLife3D[ebx].remaining,1000
mov npcLife3D[ebx].pathCount,0
mov eax,slot
mov npcVisitActivity3D[eax*4],NV_IDLE
npc_move_done:
; Advance gait by successful displacement only, including partial blocked steps.
mov esi,mind
@ -372,6 +405,7 @@ npc_move_done:
mov edi,eax
or eax,ecx
jz npc_move_no_stride
mov npcVisitBlocked3D[ebx*4],0
mov npcWalkVelocityX3D[ebx*4],ecx
mov npcWalkVelocityY3D[ebx*4],edi
mov eax,ecx
@ -389,16 +423,25 @@ npc_move_done:
mov ecx,FP_1*3/4
div ecx
mov npcWalkDistance3D[ebx*4],edx
imul eax,ebx,SIZEOF NpcLife3D
cmp npcLife3D[eax].state,NPC_WALK
jne npc_move_no_stride
ret
npc_move_no_stride:
mov eax,slot
mov npcWalkVelocityX3D[eax*4],0
mov npcWalkVelocityY3D[eax*4],0
ret
NpcMove3D ENDP
UpdateNpcLife3D PROC uses ebx esi edi
local milliseconds:DWORD,destination:DWORD
local milliseconds:DWORD
cmp npcLifeReady3D,1
jne npc_update_done
cmp hallLevel3D,0
jne npc_update_done
cmp engineMode,MODE_WALK
jne npc_update_done
mov eax,deltaTime3D
cmp eax,250
jbe @F
@ -411,6 +454,20 @@ npc_update_person:
imul ebx,esi,SIZEOF NpcLife3D
mov eax,milliseconds
add npcLife3D[ebx].poseClock,eax
sub npcRemarkTime3D[esi*4],eax
jns @F
mov npcRemarkTime3D[esi*4],0
@@:
cmp npcLife3D[ebx].state,NPC_SIT
jne npc_update_seat_ready
mov ecx,npcSitTime3D[esi*4]
add ecx,eax
cmp ecx,800
jbe @F
mov ecx,800
@@:
mov npcSitTime3D[esi*4],ecx
npc_update_seat_ready:
cmp npcLife3D[ebx].state,NPC_WALK
jne npc_update_wait
cmp npcRise3D[esi*4],0
@ -425,51 +482,23 @@ npc_update_move:
npc_update_wait:
sub npcLife3D[ebx].remaining,eax
ja npc_update_sync
cmp npcLife3D[ebx].state,NPC_AWAY
jne npc_update_choose
mov eax,npcLife3D[ebx].identity
add eax,6
cmp eax,NPC_IDENTITY_COUNT
jb @F
sub eax,NPC_IDENTITY_COUNT
@@:
mov npcLife3D[ebx].identity,eax
mov npcLife3D[ebx].state,NPC_STAND
npc_update_choose:
mov eax,npcLife3D[ebx].routeStep
xor edx,edx
mov ecx,NPC_DEST_COUNT
div ecx
mov eax,npcVisitRoute3D[edx*4]
mov destination,eax
inc npcLife3D[ebx].routeStep
mov npcRise3D[esi*4],0
cmp npcLife3D[ebx].state,NPC_SIT
jne @F
mov npcRise3D[esi*4],800
@@:
invoke NpcPlanPath3D,esi,destination
test eax,eax
jz npc_update_retry
mov npcLife3D[ebx].state,NPC_WALK
mov npcLife3D[ebx].poseClock,0
jmp npc_update_sync
npc_update_retry:
mov npcRise3D[esi*4],0
mov npcLife3D[ebx].state,NPC_STAND
mov npcLife3D[ebx].remaining,1000
invoke NpcAdvanceVisit3D,esi
npc_update_sync:
imul edi,esi,SIZEOF NpcMindEntity
invoke NpcEntity3D,esi
mov edi,eax
mov eax,npcLife3D[ebx].state
mov casinoNpcs3D[edi].state,eax
mov [edi].NpcMindEntity.state,eax
mov eax,npcLife3D[ebx].remaining
mov casinoNpcs3D[edi].stateTimer,eax
mov [edi].NpcMindEntity.stateTimer,eax
; Keep the old public position array coherent for existing world checks.
cmp esi,6
jae npc_update_sync_next
imul ecx,esi,SIZEOF CityProp3D
mov eax,casinoNpcs3D[edi].posX
mov eax,[edi].NpcMindEntity.posX
mov hallGuests3D[ecx].posX,eax
mov eax,casinoNpcs3D[edi].posY
mov eax,[edi].NpcMindEntity.posY
mov hallGuests3D[ecx].posY,eax
npc_update_sync_next:
inc esi
cmp esi,NPC_LIFE_COUNT
jb npc_update_person
@ -483,8 +512,9 @@ RenderNpcLife3D PROC uses ebx esi edi
jne npc_draw_done
xor esi,esi
npc_draw_depth:
imul ebx,esi,SIZEOF NpcMindEntity
invoke ProjectCityPoint3D,casinoNpcs3D[ebx].posX,casinoNpcs3D[ebx].posY
invoke NpcEntity3D,esi
mov ebx,eax
invoke ProjectCityPoint3D,[ebx].NpcMindEntity.posX,[ebx].NpcMindEntity.posY
mov npcDrawDepth3D[esi*4],edx
mov npcDrawOrder3D[esi*4],esi
inc esi
@ -514,24 +544,29 @@ npc_draw_person:
imul ebx,eax,SIZEOF NpcLife3D
cmp npcLife3D[ebx].state,NPC_AWAY
je npc_draw_next
imul edi,eax,SIZEOF NpcMindEntity
mov eax,casinoNpcs3D[edi].posX
invoke NpcEntity3D,eax
mov edi,eax
mov eax,[edi].NpcMindEntity.posX
mov npcSprite3D.posX,eax
mov eax,casinoNpcs3D[edi].posY
mov eax,[edi].NpcMindEntity.posY
mov npcSprite3D.posY,eax
mov eax,npcLife3D[ebx].identity
mov eax,npcGuestAppearance3D[eax*4]
mov castId,eax
mov castAction,CAST_TALK
mov castAction,CAST_IDLE
mov eax,npcLife3D[ebx].poseClock
mov castClock,eax
mov eax,npcDrawOrder3D[esi*4]
mov slot,eax
invoke NpcResolveVisitPose3D,eax
mov castAction,eax
cmp npcLife3D[ebx].state,NPC_SIT
jne npc_draw_walk
mov castAction,CAST_SEAT_TALK
cmp castClock,800
mov eax,slot
mov eax,npcSitTime3D[eax*4]
cmp eax,800
jae npc_draw_sprite
mov castClock,eax
mov castAction,CAST_SIT_DOWN
jmp npc_draw_sprite
npc_draw_walk:
@ -546,6 +581,9 @@ npc_draw_walk:
mov castClock,eax
jmp npc_draw_sprite
npc_draw_moving:
mov eax,npcWalkVelocityX3D[ebx*4]
or eax,npcWalkVelocityY3D[ebx*4]
jz npc_draw_sprite
mov castAction,CAST_WALK
mov eax,npcWalkDistance3D[ebx*4]
mov castClock,eax
@ -553,6 +591,7 @@ npc_draw_sprite:
mov ebx,slot
invoke ConfigureCharacter3D,OFFSET npcSprite3D,castId,castAction,castClock,npcWalkVelocityX3D[ebx*4],npcWalkVelocityY3D[ebx*4]
invoke DrawCityObject3D,OFFSET npcSprite3D,0
invoke NpcDrawRemark3D,slot
npc_draw_next:
inc esi
cmp esi,NPC_LIFE_COUNT
@ -576,14 +615,15 @@ npc_interact_person:
imul ebx,esi,SIZEOF NpcLife3D
cmp npcLife3D[ebx].state,NPC_AWAY
je npc_interact_next
imul edi,esi,SIZEOF NpcMindEntity
mov eax,casinoNpcs3D[edi].posX
invoke NpcEntity3D,esi
mov edi,eax
mov eax,[edi].NpcMindEntity.posX
sub eax,playerX
sar eax,8
mov deltaX,eax
imul eax,eax
mov distance,eax
mov eax,casinoNpcs3D[edi].posY
mov eax,[edi].NpcMindEntity.posY
sub eax,playerY
sar eax,8
mov deltaY,eax
@ -640,3 +680,5 @@ npc_interact_next:
npc_interact_done:
ret
CheckNpcInteractions3D ENDP
include src_graphics\npc_visits.inc

View file

@ -3,9 +3,9 @@ IFDEF CASINO_ACTION_TEST
DeleteFileA PROTO :DWORD
ENDIF
INTERACT_NPC equ 40
NPC_LIFE_COUNT equ 6
NPC_LIFE_COUNT equ 10
NPC_IDENTITY_COUNT equ 12
NPC_DEST_COUNT equ 10
NPC_DEST_COUNT equ 12
NPC_STAND equ 0
NPC_WALK equ 1
NPC_SIT equ 2
@ -23,6 +23,16 @@ NpcPlanPath3D PROTO :DWORD,:DWORD
NpcTryCell3D PROTO :DWORD,:DWORD
NpcMove3D PROTO :DWORD,:DWORD
NpcCanStep3D PROTO :DWORD,:DWORD,:DWORD
NpcEntity3D PROTO :DWORD
InitNpcVisits3D PROTO
NpcArrive3D PROTO :DWORD
NpcChooseVisit3D PROTO :DWORD
NpcAdvanceVisit3D PROTO :DWORD
NpcResolveVisitPose3D PROTO :DWORD
NpcSeatOffset3D PROTO :DWORD
NpcVisitRandom3D PROTO :DWORD,:DWORD
NpcReleaseStation3D PROTO :DWORD
NpcDrawRemark3D PROTO :DWORD
NpcRecordRound3D PROTO :DWORD,:DWORD
NpcEnsureJournal3D PROTO
NpcSaveJournal3D PROTO
@ -43,6 +53,9 @@ NpcLife3D STRUCT
NpcLife3D ENDS
.data
npcLife3D NpcLife3D NPC_LIFE_COUNT dup(<>)
; The original six poker-mind records retain their layout and profile range.
npcExtraActors3D NpcMindEntity 4 dup(<>)
npcInitialIdentity3D dd 0,1,2,3,4,5,6,7,9,10
npcLifeReady3D dd 0
npcGuestArt3D AnimPack3D <>
npcGuestPath3D db "assets\animations\hall\guest-cast-v1.bin",0
@ -55,6 +68,7 @@ npcWalkVelocityX3D dd NPC_LIFE_COUNT dup(0)
npcWalkVelocityY3D dd NPC_LIFE_COUNT dup(0)
; Keep the twelve-slot historical journal, but resolve every appearance to the shared ten.
npcRise3D dd NPC_LIFE_COUNT dup(0)
npcSitTime3D dd NPC_LIFE_COUNT dup(0)
npcGuestAppearance3D dd 0,2,4,1,3,5,6,8,0,7,9,2
npcGuestNames3D dd OFFSET npcName0,OFFSET npcName1,OFFSET npcName2,OFFSET npcGuestAnna3D
dd OFFSET npcGuestMarina3D,OFFSET npcGuestVera3D,OFFSET npcName6,OFFSET npcName7
@ -71,11 +85,12 @@ npcDialogError3D dd 0
npcNameBuffer3D db 64 dup(0)
npcJournalIoError3D dd 0
npcDestX3D dd 16*FP_1+FP_HALF,16*FP_1+FP_HALF,10*FP_1+FP_HALF,6*FP_1+FP_HALF,13*FP_1+FP_HALF
dd 21*FP_1+FP_HALF,7*FP_1+FP_HALF,6*FP_1+FP_HALF,16*FP_1+FP_HALF,16*FP_1+FP_HALF
dd 21*FP_1+FP_HALF,7*FP_1+FP_HALF,13*FP_1+FP_HALF,16*FP_1+FP_HALF,19*FP_1+FP_HALF
dd 6*FP_1+FP_HALF,15*FP_1+FP_HALF
npcDestY3D dd 28*FP_1+FP_HALF,17*FP_1+FP_HALF,13*FP_1+FP_HALF,14*FP_1+FP_HALF,9*FP_1+FP_HALF
dd 10*FP_1+FP_HALF,25*FP_1+FP_HALF,27*FP_1+FP_HALF,10*FP_1+FP_HALF,7*FP_1+FP_HALF
npcVisitRoute3D dd 1,5,8,2,3,6,7,9,1,0
npcStartDest3D dd 1,2,3,6,8,7
dd 10*FP_1+FP_HALF,25*FP_1+FP_HALF,19*FP_1+FP_HALF,10*FP_1+FP_HALF,10*FP_1+FP_HALF
dd 25*FP_1+FP_HALF,19*FP_1+FP_HALF
npcStartDest3D dd 1,2,3,6,8,7,4,5,9,11
npcSprite3D CityProp3D <0,0,FP_1*3/5,FP_1*6/5,0,OFFSET hallPatrons3D>
npcDrawOrder3D dd NPC_LIFE_COUNT dup(0)
npcDrawDepth3D dd NPC_LIFE_COUNT dup(0)
@ -91,7 +106,7 @@ npcJournalBasePtr3D dd 0
ENDIF
npcDateOverride3D dd 0
npcSystemTime3D dw 8 dup(0)
; One bounded journal per profile; visitors 0..5 and 6..11 alternate at the entrance.
; Retain the twelve historical journal entries for existing saves and contracts.
npcJournal3D LABEL DWORD
npcJournalMagic3D dd 3151504Eh
npcJournalDay3D dd 0
@ -157,4 +172,5 @@ npcQueue3D dd NPC_PATH_CAP dup(?)
npcSearchHead3D dd 0
npcSearchTail3D dd 0
npcSearchGoal3D dd 0
include src_graphics\npc_visits_data.inc
.code

511
src_graphics/npc_visits.inc Normal file
View file

@ -0,0 +1,511 @@
; Four additional visitors do not expand the six legacy poker personality records.
NpcEntity3D PROC slot:DWORD
mov eax,slot
cmp eax,6
jae nv_entity_extra
imul eax,SIZEOF NpcMindEntity
add eax,OFFSET casinoNpcs3D
ret
nv_entity_extra:
sub eax,6
imul eax,SIZEOF NpcMindEntity
add eax,OFFSET npcExtraActors3D
ret
NpcEntity3D ENDP
NpcVisitRandom3D PROC slot:DWORD,limit:DWORD
mov ecx,slot
mov eax,npcVisitRng3D[ecx*4]
imul eax,1664525
add eax,1013904223
mov npcVisitRng3D[ecx*4],eax
shr eax,8
xor edx,edx
div limit
mov eax,edx
ret
NpcVisitRandom3D ENDP
InitNpcVisits3D PROC uses ebx esi edi
lea edi,npcStationOwner3D
mov ecx,NPC_DEST_COUNT
mov eax,-1
rep stosd
xor esi,esi
nv_init_person:
imul ebx,esi,SIZEOF NpcLife3D
imul eax,esi,7919
add eax,071C4A931h
mov npcVisitRng3D[esi*4],eax
mov eax,npcVisitInitialChips3D[esi*4]
mov npcVisitChips3D[esi*4],eax
mov npcVisitRounds3D[esi*4],0
mov npcVisitThirst3D[esi*4],0
mov npcVisitMood3D[esi*4],0
mov npcVisitBlocked3D[esi*4],0
mov npcRemark3D[esi*4],0
mov npcRemarkTime3D[esi*4],0
mov eax,npcLife3D[ebx].destination
mov npcStationOwner3D[eax*4],esi
invoke NpcArrive3D,esi
; Different initial waits prevent a synchronized crowd surge.
imul eax,esi,1100
add npcLife3D[ebx].remaining,eax
inc esi
cmp esi,NPC_LIFE_COUNT
jb nv_init_person
ret
InitNpcVisits3D ENDP
NpcReleaseStation3D PROC uses esi slot:DWORD
xor esi,esi
nv_release_station:
mov eax,slot
cmp npcStationOwner3D[esi*4],eax
jne @F
mov npcStationOwner3D[esi*4],-1
@@:
inc esi
cmp esi,NPC_DEST_COUNT
jb nv_release_station
ret
NpcReleaseStation3D ENDP
NpcArrive3D PROC uses ebx esi slot:DWORD
mov esi,slot
imul ebx,esi,SIZEOF NpcLife3D
mov npcLife3D[ebx].state,NPC_STAND
mov npcLife3D[ebx].poseClock,0
mov npcSitTime3D[esi*4],0
mov npcRemark3D[esi*4],0
mov npcRemarkTime3D[esi*4],0
mov npcVisitBlocked3D[esi*4],0
mov npcVisitRounds3D[esi*4],0
mov eax,npcLife3D[ebx].destination
mov ecx,npcStationKind3D[eax*4]
mov npcVisitActivity3D[esi*4],ecx
cmp ecx,NV_LEAVE
jne @F
invoke NpcReleaseStation3D,esi
mov npcLife3D[ebx].state,NPC_AWAY
mov npcLife3D[ebx].remaining,8000
ret
@@:
cmp ecx,NV_ORDER
jne @F
mov npcLife3D[ebx].remaining,2400
mov npcRemark3D[esi*4],OFFSET npcRemarkOrder3D
mov npcRemarkTime3D[esi*4],2200
ret
@@:
cmp ecx,NV_REST
jne @F
mov npcLife3D[ebx].state,NPC_SIT
invoke NpcVisitRandom3D,esi,9000
add eax,14000
mov npcLife3D[ebx].remaining,eax
ret
@@:
cmp ecx,NV_WATCH
jne @F
invoke NpcVisitRandom3D,esi,10000
add eax,12000
mov npcLife3D[ebx].remaining,eax
ret
@@:
invoke NpcVisitRandom3D,esi,1800
add eax,2200
mov npcLife3D[ebx].remaining,eax
ret
NpcArrive3D ENDP
; Reserve a free destination before walking; neither a failed path nor a blocked
; walker keeps a permanent claim on furniture. Casino RNG and bankroll are unused.
NpcChooseVisit3D PROC uses ebx esi edi slot:DWORD
local candidate:DWORD,wasSeated:DWORD,attempt:DWORD
mov esi,slot
imul ebx,esi,SIZEOF NpcLife3D
mov eax,npcLife3D[ebx].state
mov wasSeated,eax
mov attempt,0
nv_choose_candidate:
cmp npcVisitChips3D[esi*4],20
jae nv_choose_thirst
mov candidate,0
jmp nv_choose_free
nv_choose_thirst:
cmp npcVisitMood3D[esi*4],-2
jle nv_choose_break
cmp npcVisitThirst3D[esi*4],3
jb nv_choose_random
nv_choose_break:
mov eax,6
test attempt,1
jz @F
mov eax,10
@@:
mov candidate,eax
cmp attempt,2
jb nv_choose_free
nv_choose_random:
invoke NpcVisitRandom3D,esi,100
cmp eax,45
jae nv_choose_bar
mov eax,npcVisitFavourite3D[esi*4]
mov candidate,eax
cmp attempt,0
je nv_choose_free
invoke NpcVisitRandom3D,esi,5
inc eax
mov candidate,eax
jmp nv_choose_free
nv_choose_bar:
cmp eax,65
jae nv_choose_rest
invoke NpcVisitRandom3D,esi,2
imul eax,4
add eax,6
mov candidate,eax
jmp nv_choose_free
nv_choose_rest:
cmp eax,83
jae nv_choose_watch
invoke NpcVisitRandom3D,esi,2
imul eax,4
add eax,7
mov candidate,eax
jmp nv_choose_free
nv_choose_watch:
invoke NpcVisitRandom3D,esi,2
add eax,8
mov candidate,eax
nv_choose_free:
mov edi,candidate
cmp edi,npcLife3D[ebx].destination
je nv_choose_next
cmp npcStationOwner3D[edi*4],-1
jne nv_choose_next
invoke NpcPlanPath3D,esi,edi
test eax,eax
jz nv_choose_next
invoke NpcReleaseStation3D,esi
mov npcStationOwner3D[edi*4],esi
mov npcLife3D[ebx].state,NPC_WALK
mov npcLife3D[ebx].poseClock,0
mov npcVisitActivity3D[esi*4],NV_IDLE
mov npcRise3D[esi*4],0
cmp wasSeated,NPC_SIT
jne @F
mov npcRise3D[esi*4],800
@@:
inc npcLife3D[ebx].routeStep
mov eax,1
ret
nv_choose_next:
inc attempt
cmp attempt,16
jb nv_choose_candidate
; Keep a seated visitor seated when all useful destinations are busy.
mov eax,wasSeated
cmp eax,NPC_AWAY
jne @F
mov eax,NPC_STAND
@@:
mov npcLife3D[ebx].state,eax
mov npcLife3D[ebx].remaining,1800
xor eax,eax
ret
NpcChooseVisit3D ENDP
NpcAdvanceVisit3D PROC uses ebx esi edi slot:DWORD
mov esi,slot
imul ebx,esi,SIZEOF NpcLife3D
mov eax,npcVisitActivity3D[esi*4]
cmp eax,NV_YIELD
jne @F
mov npcLife3D[ebx].state,NPC_WALK
mov npcLife3D[ebx].poseClock,0
ret
@@:
cmp eax,NV_GAME
je nv_play_result
cmp eax,NV_WIN
je nv_after_result
cmp eax,NV_LOSS
je nv_after_result
cmp eax,NV_ORDER
je nv_order_ready
cmp eax,NV_DRINK
je nv_social
cmp eax,NV_REST
je nv_social
cmp eax,NV_LEAVE
jne @F
mov eax,npcVisitInitialChips3D[esi*4]
mov npcVisitChips3D[esi*4],eax
mov npcVisitMood3D[esi*4],0
mov npcVisitThirst3D[esi*4],0
@@:
invoke NpcChooseVisit3D,esi
ret
nv_play_result:
mov edi,npcVisitStake3D[esi*4]
invoke NpcVisitRandom3D,esi,100
cmp eax,42
jae nv_play_loss
add npcVisitChips3D[esi*4],edi
cmp npcVisitMood3D[esi*4],3
jge @F
inc npcVisitMood3D[esi*4]
@@:
mov npcVisitActivity3D[esi*4],NV_WIN
mov npcRemark3D[esi*4],OFFSET npcRemarkWin3D
jmp nv_result_done
nv_play_loss:
sub npcVisitChips3D[esi*4],edi
jns @F
mov npcVisitChips3D[esi*4],0
@@:
cmp npcVisitMood3D[esi*4],-3
jle @F
dec npcVisitMood3D[esi*4]
@@:
mov npcVisitActivity3D[esi*4],NV_LOSS
mov npcRemark3D[esi*4],OFFSET npcRemarkLoss3D
nv_result_done:
inc npcVisitRounds3D[esi*4]
inc npcVisitThirst3D[esi*4]
mov npcLife3D[ebx].poseClock,0
mov npcLife3D[ebx].remaining,1800
mov npcRemarkTime3D[esi*4],1800
ret
nv_after_result:
cmp npcVisitMood3D[esi*4],-2
jle nv_take_break
mov eax,npcVisitRounds3D[esi*4]
cmp eax,npcVisitSession3D[esi*4]
jae nv_take_break
mov eax,npcVisitStake3D[esi*4]
cmp eax,npcVisitChips3D[esi*4]
ja nv_take_break
mov npcVisitActivity3D[esi*4],NV_GAME
mov npcLife3D[ebx].poseClock,0
invoke NpcVisitRandom3D,esi,1800
add eax,2200
mov npcLife3D[ebx].remaining,eax
ret
nv_take_break:
mov npcRemark3D[esi*4],OFFSET npcRemarkBreak3D
mov npcRemarkTime3D[esi*4],1600
invoke NpcChooseVisit3D,esi
ret
nv_order_ready:
sub npcVisitChips3D[esi*4],20
jns @F
mov npcVisitChips3D[esi*4],0
@@:
mov npcVisitThirst3D[esi*4],0
mov npcVisitMood3D[esi*4],0
mov npcVisitActivity3D[esi*4],NV_DRINK
mov npcLife3D[ebx].poseClock,0
mov npcLife3D[ebx].remaining,6500
ret
nv_social:
cmp npcVisitMood3D[esi*4],0
jge @F
inc npcVisitMood3D[esi*4]
@@:
; Only chat when another stationary visitor is actually nearby.
invoke NpcEntity3D,esi
mov edi,eax
xor ecx,ecx
nv_social_find:
cmp ecx,esi
je nv_social_next
push ecx
invoke NpcEntity3D,ecx
mov edx,[edi].NpcMindEntity.posX
sub edx,[eax].NpcMindEntity.posX
jns @F
neg edx
@@:
cmp edx,FP_1*5/2
ja nv_social_far
mov edx,[edi].NpcMindEntity.posY
sub edx,[eax].NpcMindEntity.posY
jns @F
neg edx
@@:
cmp edx,FP_1*5/2
ja nv_social_far
pop ecx
imul eax,ecx,SIZEOF NpcLife3D
cmp npcLife3D[eax].state,NPC_WALK
je nv_social_next
cmp npcLife3D[eax].state,NPC_AWAY
je nv_social_next
mov npcVisitActivity3D[esi*4],NV_CHAT
mov npcLife3D[ebx].poseClock,0
mov npcLife3D[ebx].remaining,6000
mov npcRemark3D[esi*4],OFFSET npcRemarkChat3D
mov npcRemarkTime3D[esi*4],1800
ret
nv_social_far:
pop ecx
nv_social_next:
inc ecx
cmp ecx,NPC_LIFE_COUNT
jb nv_social_find
invoke NpcChooseVisit3D,esi
ret
NpcAdvanceVisit3D ENDP
NpcResolveVisitPose3D PROC uses ebx esi slot:DWORD
mov esi,slot
imul ebx,esi,SIZEOF NpcLife3D
mov eax,npcLife3D[ebx].state
cmp eax,NPC_SIT
je nv_pose_seat
cmp npcRise3D[esi*4],0
jne nv_pose_rising
mov eax,npcVisitActivity3D[esi*4]
cmp eax,NV_WIN
je nv_pose_cheer
cmp eax,NV_DRINK
je nv_pose_drink
cmp eax,NV_CHAT
je nv_pose_talk
cmp eax,NV_ORDER
je nv_pose_talk
mov eax,CAST_IDLE
ret
nv_pose_cheer:
mov eax,CAST_CHEER
ret
nv_pose_drink:
mov eax,CAST_DRINK
ret
nv_pose_talk:
mov eax,CAST_TALK
ret
nv_pose_seat:
invoke NpcSeatOffset3D,npcSitTime3D[esi*4]
mov eax,CAST_SEAT
cmp npcVisitActivity3D[esi*4],NV_CHAT
jne @F
mov eax,CAST_SEAT_TALK
@@:
ret
nv_pose_rising:
invoke NpcSeatOffset3D,npcRise3D[esi*4]
mov eax,CAST_IDLE
ret
NpcResolveVisitPose3D ENDP
; Smoothstep into/out of the existing seat anchor. Activity clocks cannot replay it.
NpcSeatOffset3D PROC uses ebx milliseconds:DWORD
mov eax,milliseconds
cmp eax,800
jb nv_seat_fraction
mov eax,FP_1
jmp nv_seat_offset
nv_seat_fraction:
shl eax,16
xor edx,edx
mov ecx,800
div ecx
mov ebx,eax
imul eax
shrd eax,edx,16
mov ecx,3*FP_1
sub ecx,ebx
sub ecx,ebx
imul ecx
shrd eax,edx,16
nv_seat_offset:
mov ebx,eax
mov ecx,FP_HALF
imul ecx
shrd eax,edx,16
sub npcSprite3D.posX,eax
mov eax,ebx
mov ecx,FP_1*35/100
imul ecx
shrd eax,edx,16
sub npcSprite3D.posY,eax
ret
NpcSeatOffset3D ENDP
NpcDrawRemark3D PROC uses ebx esi edi slot:DWORD
local px:DWORD,py:DWORD,depth:DWORD,boxW:DWORD,textPtr:DWORD
mov esi,slot
cmp npcRemarkTime3D[esi*4],0
je nv_remark_done
imul ebx,esi,SIZEOF NpcLife3D
cmp npcLife3D[ebx].state,NPC_WALK
je nv_remark_done
invoke ProjectCityPoint3D,npcSprite3D.posX,npcSprite3D.posY
cmp edx,FP_1/2
jle nv_remark_done
cmp edx,5*FP_1
jg nv_remark_done
cmp eax,24
jl nv_remark_done
cmp eax,SCREEN_W-24
jg nv_remark_done
cmp edx,zBuffer[eax*4]
jg nv_remark_done
mov depth,edx
mov px,eax
mov eax,viewEye3D
sub eax,CAST_HEIGHT
imul eax,SCREEN_H
cdq
idiv depth
add eax,viewHorizon3D
sar eax,1
sub eax,20
cmp eax,22
jl nv_remark_done
cmp eax,194
jg nv_remark_done
mov py,eax
mov edi,npcRemark3D[esi*4]
test edi,edi
jz nv_remark_done
mov textPtr,edi
xor ecx,ecx
nv_remark_length:
cmp BYTE PTR [edi+ecx],0
je nv_remark_place
inc ecx
cmp ecx,24
jb nv_remark_length
ret
nv_remark_place:
lea ebx,[ecx*8+12]
mov boxW,ebx
shr ebx,1
mov eax,px
sar eax,1
sub eax,ebx
cmp eax,4
jge @F
mov eax,4
@@:
mov ecx,316
sub ecx,boxW
cmp eax,ecx
jle @F
mov eax,ecx
@@:
mov px,eax
invoke DrawBoxBorder,px,py,boxW,18,00151D22h,00899599h,1
mov eax,px
add eax,6
mov ecx,py
add ecx,5
invoke DrawString8x8,eax,ecx,textPtr,00E8D7ADh
nv_remark_done:
ret
NpcDrawRemark3D ENDP

View file

@ -0,0 +1,35 @@
NV_IDLE equ 0
NV_GAME equ 1
NV_WIN equ 2
NV_LOSS equ 3
NV_ORDER equ 4
NV_DRINK equ 5
NV_REST equ 6
NV_WATCH equ 7
NV_CHAT equ 8
NV_LEAVE equ 9
NV_YIELD equ 10
.data
npcVisitActivity3D dd NPC_LIFE_COUNT dup(0)
npcVisitRng3D dd NPC_LIFE_COUNT dup(0)
npcVisitChips3D dd NPC_LIFE_COUNT dup(0)
npcVisitRounds3D dd NPC_LIFE_COUNT dup(0)
npcVisitThirst3D dd NPC_LIFE_COUNT dup(0)
npcVisitMood3D dd NPC_LIFE_COUNT dup(0)
npcVisitBlocked3D dd NPC_LIFE_COUNT dup(0)
npcStationOwner3D dd NPC_DEST_COUNT dup(-1)
npcRemark3D dd NPC_LIFE_COUNT dup(0)
npcRemarkTime3D dd NPC_LIFE_COUNT dup(0)
npcVisitSpeed3D dd 55000,61000,47000,52000,58000,44000,59000,50000,46000,54000
npcVisitFavourite3D dd 5,3,2,1,4,2,5,1,3,4
npcVisitSession3D dd 3,4,2,2,4,1,3,2,1,3
npcVisitStake3D dd 40,60,20,30,80,20,50,30,20,60
npcVisitInitialChips3D dd 1200,900,800,1000,1800,500,1300,1000,650,1400
npcStationKind3D dd NV_LEAVE,NV_GAME,NV_GAME,NV_GAME,NV_GAME,NV_GAME
dd NV_ORDER,NV_REST,NV_WATCH,NV_WATCH,NV_ORDER,NV_REST
npcRemarkWin3D db 197,209,210,220,33,0
npcRemarkLoss3D db 206,207,223,210,220,32,204,200,204,206,46,46,46,0
npcRemarkOrder3D db 194,206,196,219,44,32,207,206,198,192,203,211,201,209,210,192,46,0
npcRemarkChat3D db 202,192,202,32,194,197,215,197,208,63,0
npcRemarkBreak3D db 207,206,208,192,32,207,197,208,197,196,206,213,205,211,210,220,46,0

View file

@ -0,0 +1,88 @@
"""Offline first-person atlas packing and original deterministic bowling Foley."""
from pathlib import Path
import json
import math
import random
import struct
import wave
from PIL import Image
from pack_bowling import tween, pack
ROOT = Path(__file__).resolve().parents[2]
ART = ROOT/'assets/sprites/bowling'
SOUND = ROOT/'assets/audio/bowling'
def wav(name, seconds, pulses, rolling=False):
rate = 22050
rng = random.Random(41309)
samples = []
low = 0.0
for i in range(round(rate*seconds)):
t = i/rate
noise = rng.uniform(-1,1)
low += .12*(noise-low)
value = 0.0
if rolling:
envelope = min(1,t/.03,(seconds-t)/.03)
value = .22*low*envelope*(.7+.3*math.sin(t*43))
for start, gain, frequency in pulses:
age = t-start
if 0 <= age < .45:
value += gain*(.42*math.sin(age*frequency*math.tau)*math.exp(-age*27)
+ .58*noise*math.exp(-age*45))
samples.append(max(-32767,min(32767,round(value*24000))))
with wave.open(str(SOUND/name),'wb') as output:
output.setparams((1,2,rate,0,'NONE','not compressed'))
output.writeframes(struct.pack(f'<{len(samples)}h',*samples))
def pinsetter_audio():
wav('pinsetter.wav',5.2,[(0,.24,120),(.4,.22,160),(1.7,.3,105),
(2.2,.24,130),(3.7,.2,105),(4,.22,160),(4.4,.25,140)],True)
def return_audio():
wav('return.wav',2.0,[(.35,.18,95),(.75,.12,130),(1.3,.16,160),
(1.65,.32,110),(1.78,.18,85)],True)
def main():
SOUND.mkdir(parents=True,exist_ok=True)
source = Image.open(ART/'player-underarm-v2.png').convert('RGBA')
keys = []
# Cell 7 contains a detached ball: release uses the actual simulated sphere.
for cell in (*range(7),8,9,10,11):
x,y = cell%4,cell//4
keys.append(source.crop((round(x*source.width/4),round(y*source.height/3),
round((x+1)*source.width/4),round((y+1)*source.height/3)))
.resize((256,256),Image.Resampling.LANCZOS))
frames=[]
for i,a in enumerate(keys):
b=keys[min(i+1,len(keys)-1)]
# Never morph the held ball into an empty hand across the release event.
frames += [a,a,a] if i==6 else [a,tween(a,b,1/3),tween(a,b,2/3)]
pack('player-underarm-v2.bin',frames)
frames[0].save(ART/'player-underarm-v2.gif',save_all=True,
append_images=frames[1:],duration=33,loop=0,disposal=2)
wav('release.wav',.35,[(0,.8,94),(.025,.3,130)])
wav('rolling.wav',14.5,[],True)
wav('pins-light.wav',.7,[(0,.65,430),(.08,.38,670),(.19,.24,510)])
wav('pins-heavy.wav',1.3,[(0,.9,390),(.025,.75,710),(.08,.65,510),
(.14,.6,830),(.24,.4,620),(.39,.32,440),(.57,.2,740)])
wav('applause.wav',1.4,[(i*.09,.25+(i%3)*.04,1700+i*23) for i in range(13)])
pinsetter_audio()
return_audio()
(ART/'player-underarm-v2.json').write_text(json.dumps({
'generator':'built-in image_gen','source':'player-underarm-v2.png',
'brief':'First-person right-handed underarm bowling: low pendulum swing beside thigh, '
'backswing leaves view, hand behind/below blue ball, ankle-height release, palm-up follow-through; '
'dark rolled sleeve; transparent 4 by 3 atlas. Replaces rejected forward-pushing draft.',
'selected_cells':[0,1,2,3,4,5,6,8,9,10,11],
'frames':len(frames),'frame_size':[256,256],'release_frame':21,
'audio':'Original seeded PCM synthesis, no sampled third-party recordings.'},indent=2)+'\n')
print('Packed',len(frames),'underarm frames and seven original PCM effects')
if __name__=='__main__':
main()

View file

@ -0,0 +1,455 @@
.data
bpFeelScoreRolls dd 10,7,3,0,8,5,4,9,0,6,2,8,1,7,2,9,0,10,7,3
bpFeelScorePath db 'build\hall-expansion\bowling-scorecard-full.bmp',0
bpFeelPinPath0 db 'build\hall-expansion\bowling-impact-0.bmp',0
bpFeelPinPath1 db 'build\hall-expansion\bowling-impact-1.bmp',0
bpFeelPinPath2 db 'build\hall-expansion\bowling-impact-2.bmp',0
bpFeelPinPath3 db 'build\hall-expansion\bowling-impact-3.bmp',0
bpFeelPinPaths dd OFFSET bpFeelPinPath0,OFFSET bpFeelPinPath1,OFFSET bpFeelPinPath2,OFFSET bpFeelPinPath3
bpFeelStatus db 'status bowlroll mode',0
bpFeelStatusLight db 'status bowllight mode',0
bpFeelStatusReset db 'status bowlreset mode',0
bpFeelStatusReturn db 'status bowlreturn mode',0
bpFeelReply db 64 dup(0)
bpFeelSampleAim dd -FP_1/2,-FP_1*45/100,-FP_1*4/10,-FP_1*35/100,-FP_1*3/10,-FP_1/4,-FP_1/5,-FP_1*15/100,-FP_1/10,-FP_1/20
dd 0,FP_1/20,FP_1/10,FP_1*15/100,FP_1/5,FP_1/4,FP_1*3/10,FP_1*35/100,FP_1*4/10,FP_1*45/100,FP_1/2
bpFeelSamplePower dd 0,50,100
bpFeelSamples dd 63*4 dup(0)
bpFeelSamplesPath db 'build\hall-expansion\bowling-physics-samples.bin',0
bpFeelSetterTimes dd 0,150,300,900,2000,3750,4150,4400
bpFeelEmptyPinPath db 'build\hall-expansion\bowling-shape-empty.bmp',0
bpFeelTinyPinPath db 'build\hall-expansion\bowling-shape-tiny.bmp',0
include tools/tests/bowling_hand_paths.inc
.code
BowlingFeelNativeTests PROC uses ebx esi edi
local window:DWORD,volume:DWORD,tilt:DWORD,aim:DWORD,power:DWORD,handle:DWORD,count:DWORD
mov expansionStage,170
call InitBowling3D
mov hallLevel3D,HALL_BOWLING
invoke BeginBowlingPlay3D,1
mov expansionStage,179
call ResetBowlingPlay3D
; Subpixel negative impulses must not collapse the pin's local basis.
invoke BowlingPlayKick3D,0,1,-1
cmp bpPinAxisX3D,0
jne bp_feel_fail
cmp bpPinAxisY3D,-FP_1
jne bp_feel_fail
invoke BowlingPlayKick3D,0,257,257
mov eax,bpPinAxisX3D
imul eax
shrd eax,edx,16
mov ebx,eax
mov eax,bpPinAxisY3D
imul eax
shrd eax,edx,16
add eax,ebx
cmp eax,FP_1*99/100
jb bp_feel_fail
cmp eax,FP_1*101/100
ja bp_feel_fail
mov bpState3D,BP_SETTLE
mov bpBallY3D,FP_1*28/10
mov bpPinTilt3D,128*256
mov bwRack3D[4],0
call RenderBowlingPlay3D
invoke ExpansionSnapshot,OFFSET bpFeelEmptyPinPath
mov bwRack3D[4],1
call RenderBowlingPlay3D
invoke ExpansionSnapshot,OFFSET bpFeelTinyPinPath
mov expansionStage,170
call ResetBowlingPlay3D
mov bwRack3D[4],1
mov bpGutter3D,1
mov bpPinVX3D,512
invoke BowlingPlayKick3D,0,512,0
cmp bpHitMask3D,0
jne bp_feel_fail
mov esi,200
bp_feel_weak:
call BowlingPlayStep3D
dec esi
jnz bp_feel_weak
cmp bpHitMask3D,0
jne bp_feel_fail
cmp bpPinTilt3D,24*256
jae bp_feel_fail
mov expansionStage,171
call ResetBowlingPlay3D
mov bwRack3D[4],1
mov bpGutter3D,1
mov bpPinVX3D,10000
invoke BowlingPlayKick3D,0,10000,0
cmp bpHitMask3D,0
jne bp_feel_fail
mov esi,120
bp_feel_topple:
call BowlingPlayStep3D
dec esi
jnz bp_feel_topple
test bpHitMask3D,1
jz bp_feel_fail
cmp bpPinTilt3D,100*256
jb bp_feel_fail
cmp bpPinX3D,FP_1*93/100
jg bp_feel_fail
mov expansionStage,172
; Opening contacts are separated but must not gain another impulse.
call ResetBowlingPlay3D
mov bpPinX3D,0
mov bpPinX3D[4],FP_1*15/100
mov bpPinY3D,4*FP_1
mov bpPinY3D[4],4*FP_1
mov bpPinVX3D,-2000
mov bpPinVX3D[4],2000
invoke BowlingPlayContact3D,0,1
cmp bpPinVX3D,-2000
jne bp_feel_fail
cmp bpPinVX3D[4],2000
jne bp_feel_fail
; A fallen pin can receive a second impact and transfer momentum again.
mov bpPinX3D,0
mov bpPinX3D[4],FP_1*15/100
mov bpPinVX3D,2000
mov bpPinVX3D[4],0
mov bpPinTilt3D,128*256
mov bpHitMask3D,1
invoke BowlingPlayContact3D,0,1
cmp bpPinVX3D[4],500
jb bp_feel_fail
cmp bpPinVX3D,2000
jae bp_feel_fail
mov expansionStage,173
call ResetBowlingPlay3D
mov bpEndTicks3D,100
mov bpPinTilt3D,40*256
call BowlingPlayReady3D
test eax,eax
jnz bp_feel_fail
mov bpPinTilt3D,128*256
call BowlingPlayReady3D
cmp eax,1
jne bp_feel_fail
mov expansionStage,174
call ResetBowlingPlay3D
lea esi,bpFeelScoreRolls
lea edi,bwRolls3D[192]
mov ecx,20
rep movsd
mov bwRollCount3D[8],20
invoke BowlingScore3D,2
invoke BowlingRollGlyph3D,2,0,0
cmp eax,'X'
jne bp_feel_fail
invoke BowlingRollGlyph3D,2,1,1
cmp eax,'/'
jne bp_feel_fail
invoke BowlingRollGlyph3D,2,2,0
cmp eax,'-'
jne bp_feel_fail
invoke BowlingRollGlyph3D,2,9,0
cmp eax,'X'
jne bp_feel_fail
invoke BowlingRollGlyph3D,2,9,1
cmp eax,'7'
jne bp_feel_fail
invoke BowlingRollGlyph3D,2,9,2
cmp eax,'/'
jne bp_feel_fail
mov bpScoreOpen3D,1
call RenderBowlingPlay3D
invoke ExpansionSnapshot,OFFSET bpFeelScorePath
mov bwRolls3D[192],0
mov bwRolls3D[196],10
mov bwRollCount3D[8],2
invoke BowlingRollGlyph3D,2,0,1
cmp eax,'/'
jne bp_feel_fail
lea esi,bpFeelScoreRolls
lea edi,bwRolls3D[192]
mov ecx,20
rep movsd
mov bwRollCount3D[8],20
mov bwRolls3D[192+18*4],0
mov bwRolls3D[192+19*4],10
invoke BowlingRollGlyph3D,2,9,2
cmp eax,'/'
jne bp_feel_fail
mov expansionStage,175
call ResetBowlingPlay3D
mov bpLockedAim3D,0
mov bpLockedPower3D,85
call BowlingPlayLaunch3D
mov bpState3D,BP_ROLL
mov esi,0
mov edi,0
bp_feel_impact:
call BowlingPlayStep3D
inc esi
cmp esi,160
jb bp_feel_impact
mov eax,esi
and eax,15
jnz bp_feel_impact
imul eax,esi,16
mov bpTime3D,eax
call RenderBowlingPlay3D
invoke ExpansionSnapshot,bpFeelPinPaths[edi*4]
inc edi
cmp edi,4
jb bp_feel_impact
mov power,0
mov aim,0
mov edi,0
bp_feel_sample:
call ResetBowlingPlay3D
mov eax,aim
mov eax,bpFeelSampleAim[eax*4]
mov bpLockedAim3D,eax
mov bpFeelSamples[edi],eax
mov eax,power
mov eax,bpFeelSamplePower[eax*4]
mov bpLockedPower3D,eax
mov bpFeelSamples[edi+4],eax
call BowlingPlayLaunch3D
mov esi,900
bp_feel_sample_steps:
call BowlingPlayStep3D
; Check the constraints throughout the roll, not just at settlement.
mov ebx,0
bp_feel_sample_bounds:
mov eax,bpPinX3D[ebx*4]
sub eax,bwPinX3D[ebx*4]
sar eax,8
imul eax,eax
mov ecx,eax
mov eax,bpPinY3D[ebx*4]
sub eax,bwPinY3D[ebx*4]
sar eax,8
imul eax,eax
add eax,ecx
cmp eax,132*132
ja bp_feel_fail
mov eax,bpPinAxisX3D[ebx*4]
imul eax
shrd eax,edx,16
mov ecx,eax
mov eax,bpPinAxisY3D[ebx*4]
imul eax
shrd eax,edx,16
add eax,ecx
cmp eax,FP_1*99/100
jb bp_feel_fail
cmp eax,FP_1*101/100
ja bp_feel_fail
inc ebx
cmp ebx,10
jb bp_feel_sample_bounds
dec esi
jnz bp_feel_sample_steps
mov eax,bpHitMask3D
mov bpFeelSamples[edi+8],eax
mov eax,bpPinTilt3D
mov bpFeelSamples[edi+12],eax
add edi,16
inc aim
cmp aim,21
jb bp_feel_sample
mov aim,0
inc power
cmp power,3
jb bp_feel_sample
invoke CreateFileA,OFFSET bpFeelSamplesPath,40000000h,0,0,2,80h,0
mov handle,eax
invoke WriteFile,handle,OFFSET bpFeelSamples,SIZEOF bpFeelSamples,ADDR count,0
invoke CloseHandle,handle
mov expansionStage,176
call ResetBowlingPlay3D
xor esi,esi
bp_feel_hand:
mov bpState3D,BP_RELEASE
imul eax,esi,33
mov bpTime3D,eax
cmp esi,21
jb @F
mov bpState3D,BP_ROLL
sub eax,21*33
mov bpTime3D,eax
@@:
call RenderBowlingPlay3D
invoke ExpansionSnapshot,bpFeelHandPaths[esi*4]
inc esi
cmp esi,33
jb bp_feel_hand
mov expansionStage,178
call ResetBowlingPlay3D
mov bpPinX3D,2*FP_1
mov bpPinVX3D,10000
invoke BowlingPlayTether3D,0
cmp bpPinX3D,FP_1*67/100
ja bp_feel_fail
cmp bpPinVX3D,2500
ja bp_feel_fail
mov bpPinTilt3D,128*256
mov bpPinAxisX3D,FP_1
mov bpPinAxisY3D,0
invoke BowlingPlayDeckBounds3D,0
cmp bpPinX3D,FP_1*48/100
ja bp_feel_fail
call ResetBowlingPlay3D
mov bpHitMask3D,1
mov bpPinTilt3D,128*256
call BowlingPlayFinishRoll3D
mov deltaTime3D,250
mov esi,7
bp_feel_start_setter:
call HandleBowlingPlay3D
dec esi
jnz bp_feel_start_setter
cmp bpState3D,BP_RESET
jne bp_feel_fail
cmp bwRollCount3D[8],1
jne bp_feel_fail
mov bpTime3D,1100
invoke BowlingPinsetterPose3D,0
test eax,eax
jz bp_feel_fail
cmp bpVisualPinLift3D,FP_1/2
jb bp_feel_fail
cmp bpPinTilt3D,128*256
jne bp_feel_fail
mov bpTime3D,1800
invoke BowlingPinsetterPose3D,0
cmp eax,1
jne bp_feel_fail
cmp bpVisualPinLift3D,FP_1
jb bp_feel_fail
mov bpTime3D,2450
invoke BowlingPinsetterPose3D,0
cmp eax,1
jne bp_feel_fail
cmp bpVisualPinLift3D,FP_1
jb bp_feel_fail
invoke BowlingPinsetterPose3D,1
cmp eax,1
jne bp_feel_fail
mov bwNewRack3D[4],1
invoke BowlingPinsetterPose3D,0
cmp eax,1
jne bp_feel_fail
mov bwNewRack3D[4],0
mov bpTime3D,3750
invoke BowlingPinsetterPose3D,1
cmp bpVisualPinLift3D,0
jne bp_feel_fail
mov esi,0
bp_feel_setter_capture:
mov eax,bpFeelSetterTimes[esi*4]
mov bpTime3D,eax
mov bpBallY3D,FP_1*28/10
call RenderBowlingPlay3D
invoke ExpansionSnapshot,bpFeelSetterPaths[esi*4]
inc esi
cmp esi,8
jb bp_feel_setter_capture
mov bpTime3D,BP_RESET_MS
mov deltaTime3D,0
call HandleBowlingPlay3D
cmp bpState3D,BP_RETURN
jne bp_feel_fail
mov bpTime3D,BP_RETURN_MS
call HandleBowlingPlay3D
cmp bpState3D,BP_DIRECTION
jne bp_feel_fail
cmp bwRack3D[4],1022
jne bp_feel_fail
cmp bwRollCount3D[8],1
jne bp_feel_fail
; First-ball miss leaves the standing rack alone and never runs the hoist.
call ResetBowlingPlay3D
call BowlingPlayFinishRoll3D
mov bpTime3D,1600
call HandleBowlingPlay3D
cmp bpState3D,BP_RETURN
jne bp_feel_fail
mov bpTime3D,BP_RETURN_MS
call HandleBowlingPlay3D
cmp bpState3D,BP_DIRECTION
jne bp_feel_fail
cmp bwRack3D[4],1023
jne bp_feel_fail
mov bpState3D,BP_RESET
mov bpTime3D,0
call BowlingPinsetterGate3D
cmp eax,FP_1
jne bp_feel_fail
mov bpTime3D,400
call BowlingPinsetterGate3D
cmp eax,FP_1*14/100
ja bp_feel_fail
mov bpTime3D,4400
call BowlingPinsetterGate3D
cmp eax,FP_1
jne bp_feel_fail
mov expansionStage,177
; Real MCI devices, muted: opens, volume, release/roll/impact and close.
mov eax,h3DWnd
mov window,eax
mov eax,settingsVolume
mov volume,eax
mov h3DWnd,1
mov settingsVolume,0
call BowlingAudioOpen3D
mov bpScoreOpen3D,0
mov bpState3D,BP_ROLL
mov bpEndTicks3D,0
mov bpImpactEnergy3D,6000
mov bpImpactCooldown3D,0
call BowlingAudioUpdate3D
invoke mciSendStringA,OFFSET bpFeelStatus,OFFSET bpFeelReply,64,0
test eax,eax
jnz bp_feel_audio_fail
cmp BYTE PTR bpFeelReply,'p'
jne bp_feel_audio_fail
invoke mciSendStringA,OFFSET bpFeelStatusLight,OFFSET bpFeelReply,64,0
test eax,eax
jnz bp_feel_audio_fail
invoke mciSendStringA,OFFSET bpFeelStatusReset,OFFSET bpFeelReply,64,0
test eax,eax
jnz bp_feel_audio_fail
mov bpScoreOpen3D,1
call BowlingAudioUpdate3D
cmp bpAudioRolling3D,0
jne bp_feel_audio_fail
mov bpScoreOpen3D,0
mov bpState3D,BP_RETURN
mov bpTime3D,500
call BowlingAudioUpdate3D
invoke mciSendStringA,OFFSET bpFeelStatusReturn,OFFSET bpFeelReply,64,0
test eax,eax
jnz bp_feel_audio_fail
cmp BYTE PTR bpFeelReply,'p'
jne bp_feel_audio_fail
mov bpScoreOpen3D,1
call BowlingAudioUpdate3D
cmp bpAudioReturning3D,0
jne bp_feel_audio_fail
call BowlingAudioClose3D
invoke mciSendStringA,OFFSET bpFeelStatus,OFFSET bpFeelReply,64,0
test eax,eax
jz bp_feel_audio_fail
invoke mciSendStringA,OFFSET bpFeelStatusReturn,OFFSET bpFeelReply,64,0
test eax,eax
jz bp_feel_audio_fail
mov eax,window
mov h3DWnd,eax
mov eax,volume
mov settingsVolume,eax
call LeaveBowlingPlay3D
ret
bp_feel_audio_fail:
call BowlingAudioClose3D
bp_feel_fail:
invoke ExitProcess,expansionStage
ret
BowlingFeelNativeTests ENDP

View file

@ -0,0 +1,52 @@
.data
bpFeelHand0 db 'build\hall-expansion\bowling-hand-00.bmp',0
bpFeelHand1 db 'build\hall-expansion\bowling-hand-01.bmp',0
bpFeelHand2 db 'build\hall-expansion\bowling-hand-02.bmp',0
bpFeelHand3 db 'build\hall-expansion\bowling-hand-03.bmp',0
bpFeelHand4 db 'build\hall-expansion\bowling-hand-04.bmp',0
bpFeelHand5 db 'build\hall-expansion\bowling-hand-05.bmp',0
bpFeelHand6 db 'build\hall-expansion\bowling-hand-06.bmp',0
bpFeelHand7 db 'build\hall-expansion\bowling-hand-07.bmp',0
bpFeelHand8 db 'build\hall-expansion\bowling-hand-08.bmp',0
bpFeelHand9 db 'build\hall-expansion\bowling-hand-09.bmp',0
bpFeelHand10 db 'build\hall-expansion\bowling-hand-10.bmp',0
bpFeelHand11 db 'build\hall-expansion\bowling-hand-11.bmp',0
bpFeelHand12 db 'build\hall-expansion\bowling-hand-12.bmp',0
bpFeelHand13 db 'build\hall-expansion\bowling-hand-13.bmp',0
bpFeelHand14 db 'build\hall-expansion\bowling-hand-14.bmp',0
bpFeelHand15 db 'build\hall-expansion\bowling-hand-15.bmp',0
bpFeelHand16 db 'build\hall-expansion\bowling-hand-16.bmp',0
bpFeelHand17 db 'build\hall-expansion\bowling-hand-17.bmp',0
bpFeelHand18 db 'build\hall-expansion\bowling-hand-18.bmp',0
bpFeelHand19 db 'build\hall-expansion\bowling-hand-19.bmp',0
bpFeelHand20 db 'build\hall-expansion\bowling-hand-20.bmp',0
bpFeelHand21 db 'build\hall-expansion\bowling-hand-21.bmp',0
bpFeelHand22 db 'build\hall-expansion\bowling-hand-22.bmp',0
bpFeelHand23 db 'build\hall-expansion\bowling-hand-23.bmp',0
bpFeelHand24 db 'build\hall-expansion\bowling-hand-24.bmp',0
bpFeelHand25 db 'build\hall-expansion\bowling-hand-25.bmp',0
bpFeelHand26 db 'build\hall-expansion\bowling-hand-26.bmp',0
bpFeelHand27 db 'build\hall-expansion\bowling-hand-27.bmp',0
bpFeelHand28 db 'build\hall-expansion\bowling-hand-28.bmp',0
bpFeelHand29 db 'build\hall-expansion\bowling-hand-29.bmp',0
bpFeelHand30 db 'build\hall-expansion\bowling-hand-30.bmp',0
bpFeelHand31 db 'build\hall-expansion\bowling-hand-31.bmp',0
bpFeelHand32 db 'build\hall-expansion\bowling-hand-32.bmp',0
bpFeelHandPaths LABEL DWORD
dd OFFSET bpFeelHand0,OFFSET bpFeelHand1,OFFSET bpFeelHand2,OFFSET bpFeelHand3,OFFSET bpFeelHand4
dd OFFSET bpFeelHand5,OFFSET bpFeelHand6,OFFSET bpFeelHand7,OFFSET bpFeelHand8,OFFSET bpFeelHand9
dd OFFSET bpFeelHand10,OFFSET bpFeelHand11,OFFSET bpFeelHand12,OFFSET bpFeelHand13,OFFSET bpFeelHand14
dd OFFSET bpFeelHand15,OFFSET bpFeelHand16,OFFSET bpFeelHand17,OFFSET bpFeelHand18,OFFSET bpFeelHand19
dd OFFSET bpFeelHand20,OFFSET bpFeelHand21,OFFSET bpFeelHand22,OFFSET bpFeelHand23,OFFSET bpFeelHand24
dd OFFSET bpFeelHand25,OFFSET bpFeelHand26,OFFSET bpFeelHand27,OFFSET bpFeelHand28,OFFSET bpFeelHand29
dd OFFSET bpFeelHand30,OFFSET bpFeelHand31,OFFSET bpFeelHand32
bpFeelSetter0 db 'build\hall-expansion\bowling-pinsetter-0.bmp',0
bpFeelSetter1 db 'build\hall-expansion\bowling-pinsetter-1.bmp',0
bpFeelSetter2 db 'build\hall-expansion\bowling-pinsetter-2.bmp',0
bpFeelSetter3 db 'build\hall-expansion\bowling-pinsetter-3.bmp',0
bpFeelSetter4 db 'build\hall-expansion\bowling-pinsetter-4.bmp',0
bpFeelSetter5 db 'build\hall-expansion\bowling-pinsetter-5.bmp',0
bpFeelSetter6 db 'build\hall-expansion\bowling-pinsetter-6.bmp',0
bpFeelSetter7 db 'build\hall-expansion\bowling-pinsetter-7.bmp',0
bpFeelSetterPaths dd OFFSET bpFeelSetter0,OFFSET bpFeelSetter1,OFFSET bpFeelSetter2,OFFSET bpFeelSetter3,OFFSET bpFeelSetter4,OFFSET bpFeelSetter5,OFFSET bpFeelSetter6,OFFSET bpFeelSetter7
.code

View file

@ -28,6 +28,13 @@ BowlingPlayNativeTests PROC uses ebx esi edi
jne bp_test_fail
cmp bwBusy3D[4],2
jne bp_test_fail
cmp bpState3D,BP_SELECT_BALL
jne bp_test_fail
mov deltaTime3D,0
mov pressedKeys3D[20h],1
call HandleBowlingPlay3D
cmp bpState3D,BP_DIRECTION
jne bp_test_fail
mov expansionStage,151
mov deltaTime3D,0
mov bpClock3D,0
@ -259,8 +266,15 @@ bp_test_flight:
mov eax,bankroll
cmp eax,bank
jne bp_test_fail
call BowlingFeelNativeTests
call BowlingReturnNativeTests
call BowlingWeightNativeTests
ret
bp_test_fail:
invoke ExitProcess,expansionStage
ret
BowlingPlayNativeTests ENDP
include tools/tests/bowling_feel_test.inc
include tools/tests/bowling_return_test.inc
include tools/tests/bowling_weight_test.inc

View file

@ -5,6 +5,7 @@ BowlingUiStart PROC
call Create3DWindow
test eax,eax
jz bp_ui_fail
call ToggleFullscreen3D
invoke SetWindowLongA,h3DWnd,-4,OFFSET WindowEvents3D
call InitBowling3D
call LoadBowling3D

View file

@ -0,0 +1,167 @@
.data
bpReturnTest0 db 'build\hall-expansion\bowling-return-0.bmp',0
bpReturnTest1 db 'build\hall-expansion\bowling-return-1.bmp',0
bpReturnTest2 db 'build\hall-expansion\bowling-return-2.bmp',0
bpReturnTest3 db 'build\hall-expansion\bowling-return-3.bmp',0
bpReturnTest4 db 'build\hall-expansion\bowling-return-4.bmp',0
bpReturnTest5 db 'build\hall-expansion\bowling-return-5.bmp',0
bpReturnTest6 db 'build\hall-expansion\bowling-return-6.bmp',0
bpReturnTest7 db 'build\hall-expansion\bowling-return-7.bmp',0
bpReturnTestPaths dd OFFSET bpReturnTest0,OFFSET bpReturnTest1,OFFSET bpReturnTest2,OFFSET bpReturnTest3
dd OFFSET bpReturnTest4,OFFSET bpReturnTest5,OFFSET bpReturnTest6,OFFSET bpReturnTest7
; Capture after the emerging ball clears the solid hood, not while inside it.
bpReturnTestTimes dd 0,400,900,1000,1300,1700,2200,2800
.code
BowlingReturnNativeTests PROC uses ebx esi edi
local position:DWORD
mov expansionStage,180
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,1
mov bpHitMask3D,1
call BowlingPlayFinishRoll3D
mov bpState3D,BP_RESET
mov bpTime3D,BP_RESET_MS
mov deltaTime3D,0
call HandleBowlingPlay3D
cmp bpState3D,BP_RETURN
jne bp_return_test_fail
cmp bpAfterReturn3D,BP_DIRECTION
jne bp_return_test_fail
cmp bwRollCount3D[8],1
jne bp_return_test_fail
cmp bwRack3D[4],1022
jne bp_return_test_fail
call BowlingReturnBallPose3D
test eax,eax
jnz bp_return_test_fail
; A queued throw cannot skip the return or choose the next direction.
mov mouseX3D,160
mov mouseY3D,120
mov mouseClick3D,1
mov pressedKeys3D[20h],1
call HandleBowlingPlay3D
cmp bpState3D,BP_RETURN
jne bp_return_test_fail
mov bpTime3D,1000
call BowlingReturnBallPose3D
cmp eax,1
jne bp_return_test_fail
cmp bpReturnedX3D,FP_1*395/100
jne bp_return_test_fail
mov eax,bpReturnedY3D
mov position,eax
mov bpTime3D,1700
call BowlingReturnBallPose3D
cmp eax,1
jne bp_return_test_fail
mov eax,bpReturnedY3D
cmp eax,position
jle bp_return_test_fail
cmp eax,FP_1*2616/100
ja bp_return_test_fail
cmp bpReturnedZ3D,FP_1*84/100
jne bp_return_test_fail
mov bpTime3D,400
invoke BowlingReturnCamera3D,FP_1*252/10
cmp playerAngle,320
jne bp_return_test_fail
cmp playerY,FP_1*268/10
jne bp_return_test_fail
mov bpTime3D,1000
mov bpScoreOpen3D,1
mov deltaTime3D,250
call HandleBowlingPlay3D
cmp bpTime3D,1000
jne bp_return_test_fail
mov bpScoreOpen3D,0
mov deltaTime3D,0
xor esi,esi
bp_return_test_capture:
mov eax,bpReturnTestTimes[esi*4]
mov bpTime3D,eax
call RenderBowlingPlay3D
invoke ExpansionSnapshot,bpReturnTestPaths[esi*4]
inc esi
cmp esi,8
jb bp_return_test_capture
call HandleBowlingPlay3D
cmp bpState3D,BP_DIRECTION
jne bp_return_test_fail
cmp bpTime3D,0
jne bp_return_test_fail
cmp bwRollCount3D[8],1
jne bp_return_test_fail
mov expansionStage,181
mov bpHitMask3D,1022
call BowlingPlayFinishRoll3D
call CompleteBowlingRoll3D
cmp bpAfterReturn3D,BP_BOT
jne bp_return_test_fail
cmp bwTurn3D[4],1
jne bp_return_test_fail
mov bpTime3D,BP_RETURN_MS
call HandleBowlingPlay3D
cmp bpState3D,BP_BOT
jne bp_return_test_fail
cmp bwRollCount3D[8],2
jne bp_return_test_fail
; Even the final ball returns before the result overlay appears.
call ResetBowlingPlay3D
mov esi,23
bp_return_test_perfect:
mov bpHitMask3D,1023
call BowlingPlayFinishRoll3D
call BowlingPlayNext3D
dec esi
jnz bp_return_test_perfect
mov bpHitMask3D,1023
call BowlingPlayFinishRoll3D
call CompleteBowlingRoll3D
cmp bpAfterReturn3D,BP_FINISHED
jne bp_return_test_fail
cmp bpState3D,BP_RETURN
jne bp_return_test_fail
mov bpTime3D,BP_RETURN_MS
call HandleBowlingPlay3D
cmp bpState3D,BP_FINISHED
jne bp_return_test_fail
cmp bwScores3D[8],300
jne bp_return_test_fail
cmp bwScores3D[12],300
jne bp_return_test_fail
call LeaveBowlingPlay3D
mov expansionStage,182
invoke BeginBowlingPlay3D,2
mov bpState3D,BP_RETURN
mov bpTime3D,1700
call BowlingReturnBallPose3D
cmp bpReturnedX3D,FP_1*38/10+FP_1*395/100
jne bp_return_test_fail
invoke BowlingReturnCamera3D,FP_1*252/10
cmp playerAngle,448
jne bp_return_test_fail
call LeaveBowlingPlay3D
cmp playerX,5*FP_1
jne bp_return_test_fail
cmp playerY,25*FP_1
jne bp_return_test_fail
cmp playerAngle,384
jne bp_return_test_fail
cmp engineMode,MODE_WALK
jne bp_return_test_fail
invoke BowlingCanWalk3D,FP_1*395/100,FP_1*264/10
test eax,eax
jnz bp_return_test_fail
invoke BowlingCanWalk3D,FP_1*49/10,FP_1*264/10
cmp eax,1
jne bp_return_test_fail
ret
bp_return_test_fail:
invoke ExitProcess,expansionStage
ret
BowlingReturnNativeTests ENDP

View file

@ -0,0 +1,299 @@
.data
bpMassTestPath db 'build\hall-expansion\bowling-mass-samples.bin',0
bpMassTest4Path db 'build\hall-expansion\bowling-weight-4.bmp',0
bpMassTest7Path db 'build\hall-expansion\bowling-weight-7.bmp',0
bpMassTestLockedPath db 'build\hall-expansion\bowling-weight-locked.bmp',0
bpMassTestRackPath db 'build\hall-expansion\bowling-weight-rack.bmp',0
bpMassTestRedPath db 'build\hall-expansion\bowling-weight-5.bmp',0
bpMassTestRackPairPath db 'build\hall-expansion\bowling-weight-rack-pair.bmp',0
bpMassTestPowers dd 0,50,100
bpMassShotsPath db 'build\hall-expansion\bowling-mass-shots.bin',0
bpMassShots dd 84*3 dup(0)
; kg, power, launch m/s Q16, inverse mass Q16, incoming vy, outgoing ball/pin vy, locked kg.
bpMassTestSamples dd 12*8 dup(0)
.code
BowlingWeightNativeTests PROC uses ebx esi edi
local bank:DWORD,rng:DWORD,kg:DWORD,power:DWORD,handle:DWORD,count:DWORD,inverse:DWORD
local aim:DWORD
mov expansionStage,183
call InitBowling3D
mov hallLevel3D,HALL_BOWLING
invoke BeginBowlingPlay3D,1
mov eax,bankroll
mov bank,eax
mov eax,bwRng3D
mov rng,eax
mov deltaTime3D,0
mov viewWidth3D,640
mov viewHeight3D,480
mov viewLeft3D,0
mov viewTop3D,0
cmp bpState3D,BP_SELECT_BALL
jne bp_mass_test_fail
call RenderBowlingPlay3D
invoke ExpansionSnapshot,OFFSET bpMassTestRackPath
invoke BowlingTintBallPixel3D,55203CC0h,4
cmp eax,5520C03Ch
jne bp_mass_test_fail
invoke BowlingTintBallPixel3D,55203CC0h,5
cmp eax,55C01E20h
jne bp_mass_test_fail
invoke BowlingTintBallPixel3D,55203CC0h,7
cmp eax,55C07E20h
jne bp_mass_test_fail
xor esi,esi
bp_mass_test_material:
lea eax,[esi+4]
invoke BowlingBallMaterial3D,eax
invoke ResolveCitySurface3D,eax
test eax,eax
jz bp_mass_test_fail
cmp ecx,64
jne bp_mass_test_fail
cmp edx,64
jne bp_mass_test_fail
cmp esi,3
jae @F
mov eax,bpRackHitX3D[esi*4+4]
sub eax,bpRackHitX3D[esi*4]
cmp eax,36
jl bp_mass_test_fail
@@:
inc esi
cmp esi,4
jb bp_mass_test_material
; A click away from the actual rack never selects a ball or starts a throw.
invoke WindowEvents3D,0,201h,1,00780014h
call HandleBowlingPlay3D
cmp bpState3D,BP_SELECT_BALL
jne bp_mass_test_fail
mov eax,bpRackHitY3D
shl eax,17
mov edx,bpRackHitX3D
shl edx,1
or eax,edx
invoke WindowEvents3D,0,201h,1,eax
call HandleBowlingPlay3D
cmp bpWeightKg3D,4
jne bp_mass_test_fail
cmp bpState3D,BP_DIRECTION
jne bp_mass_test_fail
call RenderBowlingPlay3D
invoke ExpansionSnapshot,OFFSET bpMassTest4Path
invoke WindowEvents3D,0,100h,34h,0
call HandleBowlingPlay3D
cmp bpWeightKg3D,7
jne bp_mass_test_fail
cmp bpState3D,BP_DIRECTION
jne bp_mass_test_fail
call HandleBowlingPlay3D
cmp bpState3D,BP_DIRECTION
jne bp_mass_test_fail
call RenderBowlingPlay3D
invoke ExpansionSnapshot,OFFSET bpMassTest7Path
; The weight label opens the in-world rack; labels and spheres share picking.
invoke WindowEvents3D,0,201h,1,003C0018h
call HandleBowlingPlay3D
cmp bpState3D,BP_SELECT_BALL
jne bp_mass_test_fail
call RenderBowlingPlay3D
mov eax,bpRackHitY3D[4]
add eax,bpRackHitRadius3D[4]
add eax,9
shl eax,17
mov edx,bpRackHitX3D[4]
shl edx,1
or eax,edx
invoke WindowEvents3D,0,201h,1,eax
call HandleBowlingPlay3D
cmp bpWeightKg3D,5
jne bp_mass_test_fail
cmp bpState3D,BP_DIRECTION
jne bp_mass_test_fail
call RenderBowlingPlay3D
invoke ExpansionSnapshot,OFFSET bpMassTestRedPath
; Test the source/scratch pixels directly: transparent hand borders can reveal
; different rack balls behind them and are not a valid skin screenshot mask.
mov esi,bpHandArt3D.pixels
lea edi,bpTintedHandPixels3D
mov ebx,256*256
xor ecx,ecx
bp_mass_test_hand_pixel:
mov eax,[esi]
mov edx,[edi]
xor edx,eax
test edx,0FF000000h
jnz bp_mass_test_fail
test edx,edx
jz bp_mass_test_hand_next
inc ecx
mov edx,eax
shr edx,16
and edx,255
and eax,255
cmp edx,eax
jae bp_mass_test_fail
bp_mass_test_hand_next:
add esi,4
add edi,4
dec ebx
jnz bp_mass_test_hand_pixel
cmp ecx,500
jb bp_mass_test_fail
mov eax,bankroll
cmp eax,bank
jne bp_mass_test_fail
mov eax,bwRng3D
cmp eax,rng
jne bp_mass_test_fail
mov expansionStage,184
mov kg,4
mov power,0
xor edi,edi
bp_mass_sample:
call ResetBowlingPlay3D
mov eax,kg
mov bpWeightKg3D,eax
mov bpMassTestSamples[edi],eax
mov eax,power
mov eax,bpMassTestPowers[eax*4]
mov bpLockedPower3D,eax
mov bpMassTestSamples[edi+4],eax
mov bpLockedAim3D,FP_1/2
call BowlingPlayLaunch3D
cmp bpBallVX3D,0
jne bp_mass_test_fail
mov eax,bpLaunchSpeed3D
mov bpMassTestSamples[edi+8],eax
mov eax,bpInvMass3D[40]
mov bpMassTestSamples[edi+12],eax
mov eax,bpBallVY3D
mov bpMassTestSamples[edi+16],eax
mov bpPinVY3D[40],eax
mov bpPinVX3D[40],0
mov bpPinX3D[40],0
mov bpPinY3D[40],FP_1*42/10
mov bpPinX3D,0
mov bpPinY3D,4*FP_1
invoke BowlingPlayContact3D,0,10
mov eax,bpPinVY3D[40]
mov bpMassTestSamples[edi+20],eax
mov eax,bpPinVY3D
mov bpMassTestSamples[edi+24],eax
mov eax,bpShotWeightKg3D
mov bpMassTestSamples[edi+28],eax
add edi,32
inc power
cmp power,3
jb bp_mass_sample
mov power,0
inc kg
cmp kg,7
jbe bp_mass_sample
invoke CreateFileA,OFFSET bpMassTestPath,40000000h,0,0,2,80h,0
mov handle,eax
invoke WriteFile,handle,OFFSET bpMassTestSamples,SIZEOF bpMassTestSamples,ADDR count,0
invoke CloseHandle,handle
; Exercise complete high-power rolls across all selectable masses.
mov kg,4
mov aim,0
xor edi,edi
bp_mass_shot:
call ResetBowlingPlay3D
mov eax,kg
mov bpWeightKg3D,eax
mov bpMassShots[edi],eax
mov eax,aim
mov eax,bpFeelSampleAim[eax*4]
mov bpLockedAim3D,eax
mov bpMassShots[edi+4],eax
mov bpLockedPower3D,100
call BowlingPlayLaunch3D
mov esi,900
bp_mass_shot_tick:
call BowlingPlayStep3D
xor ebx,ebx
bp_mass_shot_bounds:
mov eax,bpPinX3D[ebx*4]
cmp eax,-FP_1
jl bp_mass_test_fail
cmp eax,FP_1
jg bp_mass_test_fail
mov eax,bpPinY3D[ebx*4]
cmp eax,3*FP_1
jl bp_mass_test_fail
cmp eax,FP_1*55/10
jg bp_mass_test_fail
inc ebx
cmp ebx,10
jb bp_mass_shot_bounds
dec esi
jnz bp_mass_shot_tick
call BowlingPlayReady3D
cmp eax,1
jne bp_mass_test_fail
mov eax,bpHitMask3D
mov bpMassShots[edi+8],eax
add edi,12
inc aim
cmp aim,21
jb bp_mass_shot
mov aim,0
inc kg
cmp kg,7
jbe bp_mass_shot
invoke CreateFileA,OFFSET bpMassShotsPath,40000000h,0,0,2,80h,0
mov handle,eax
invoke WriteFile,handle,OFFSET bpMassShots,SIZEOF bpMassShots,ADDR count,0
invoke CloseHandle,handle
mov expansionStage,185
call ResetBowlingPlay3D
mov bpWeightKg3D,7
mov bpLockedAim3D,0
mov bpLockedPower3D,70
call BowlingPlayLaunch3D
mov eax,bpInvMass3D[40]
mov inverse,eax
mov bpWeightKg3D,4
mov pressedKeys3D[31h],1
call HandleBowlingPlay3D
cmp bpShotWeightKg3D,7
jne bp_mass_test_fail
mov eax,bpInvMass3D[40]
cmp eax,inverse
jne bp_mass_test_fail
cmp bpState3D,BP_RELEASE
jne bp_mass_test_fail
call RenderBowlingPlay3D
invoke ExpansionSnapshot,OFFSET bpMassTestLockedPath
mov bwTurn3D[4],1
mov bpWeightKg3D,7
call BowlingPlayLaunch3D
cmp bpShotWeightKg3D,6
jne bp_mass_test_fail
cmp bpWeightKg3D,7
jne bp_mass_test_fail
call LeaveBowlingPlay3D
invoke BeginBowlingPlay3D,2
call RenderBowlingPlay3D
invoke ExpansionSnapshot,OFFSET bpMassTestRackPairPath
mov eax,bpRackHitY3D[12]
shl eax,17
mov edx,bpRackHitX3D[12]
shl edx,1
or eax,edx
invoke WindowEvents3D,0,201h,1,eax
call HandleBowlingPlay3D
cmp bpWeightKg3D,7
jne bp_mass_test_fail
cmp bpState3D,BP_DIRECTION
jne bp_mass_test_fail
call LeaveBowlingPlay3D
invoke BowlingCanWalk3D,FP_1*45/10,FP_1*257/10
test eax,eax
jnz bp_mass_test_fail
ret
bp_mass_test_fail:
invoke ExitProcess,expansionStage
ret
BowlingWeightNativeTests ENDP

View file

@ -42,6 +42,7 @@ def prepare():
code += "\n" + expand("src_graphics/rooftop.inc")
code += "\n" + expand("src_graphics/bowling.inc")
code += "\n" + read("src_graphics/character_cast.inc")
code += "\n" + read("src_graphics/npc_visits.inc")
code += "\n" + proc(read("src_graphics/scene_animation.inc"),"LoadAnimPack3D")
code += "\n" + proc(read("src_graphics/unavailable.inc"),"BeginUnavailable3D")
code += "\n" + read("src_graphics/observer.inc")
@ -75,6 +76,7 @@ def prepare():
darts = read("src_graphics/darts_data.inc").replace(
"include src_graphics\\darts_art.inc", read("src_graphics/darts_art.inc"))
data += "\n" + re.sub(r"^\w+ PROTO[^\n]*\n", "", darts, flags=re.M)
data += "\n" + read("src_graphics/npc_visits_data.inc")
own_data = set(re.findall(r"^(\w+) (?:dd|db|dw|REAL4|REAL8|AnimPack3D|CityProp3D)\b",data,re.M))
own_data.update(re.findall(r"^(\w+) LABEL\b",data,re.M))
coff = Coff(ROOT / "build/graphics/poker.obj")
@ -90,7 +92,8 @@ def prepare():
"casinoNpcs3D":"NpcMindEntity","hallSpriteTemp3D":"CityProp3D",
"hallObjects3D":"CityProp3D","hallLights3D":"CityProp3D",
"hallLoungeDecor3D":"CityProp3D","hallGuests3D":"CityProp3D",
"npcSprite3D":"CityProp3D","scrStation3D":"CityProp3D"}
"npcSprite3D":"CityProp3D","scrStation3D":"CityProp3D",
"npcExtraActors3D":"NpcMindEntity"}
all_source = "\n".join(read(str(path.relative_to(ROOT))) for path in
[ROOT/"casino3d.inc",ROOT/"casino3d_data.inc",*sorted(path for path in
(ROOT/"src_graphics").glob("*.inc") if path.name != "renderer.inc")])
@ -137,13 +140,13 @@ def prepare():
for dep in re.findall(r"\b[A-Za-z_]\w*",constants[name]):
constant(dep)
ordered.append(f"{name} equ {constants[name]}")
for name in re.findall(r"\b[A-Za-z_]\w*",code+data+read("tools/tests/hall_expansion_test.inc")):
for name in re.findall(r"\b[A-Za-z_]\w*",code+data+expand("tools/tests/hall_expansion_test.inc")):
constant(name)
# Win32 prototypes are not declarations in the helper object.
for name,args in re.findall(r"^(\w+)\s+PROTO([^\n]*)",all_source,re.M):
protos.setdefault(name,args.count(":DWORD"))
protos.update({"ExitProcess":1,"WriteFile":5,"GetTickCount":0,"SetUnhandledExceptionFilter":1,
"CreateDirectoryA":2})
"CreateDirectoryA":2,"mciSendStringA":4})
header = ".386\n.model flat\noption casemap:none\n" + "\n".join(structures+ordered+externs)
header += "\noption language:stdcall\n" + "\n".join(
name+" PROTO"+(" "+",".join([":DWORD"]*size) if size else "")
@ -223,6 +226,10 @@ def check_bowling_room():
return False
if 2 <= x <= 17.3 and y < 22.3:
return False
if 26 <= y <= 26.6 and any(abs(x-(3.95+3.8*i)) <= .4 for i in range(4)):
return False
if 25.2 <= y <= 26.6 and any(.25 <= x-(3.95+3.8*i) <= .85 for i in range(4)):
return False
return not any(a <= x <= b and c <= y <= d for a,b,c,d in layout["collisions"])
start = (68,120)
reached,pending = {start},[start]
@ -248,18 +255,118 @@ def check_bowling_room():
assert len(captures) >= 10
for a,b,region in (
("direction","direction-left",(180,314,462,414)),
("power","power-low",(536,154,596,408)),
("power","power-low",(42,154,102,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")
sample_data = (OUT/'bowling-physics-samples.bin').read_bytes()
shots = list(struct.iter_unpack('<iiII', sample_data))
assert len(shots) == 63
assert all(mask < 1024 for _,_,mask,_ in shots)
assert len({mask for _,_,mask,_ in shots}) >= 4, 'Aim/power have no physical effect'
assert any(power==100 and mask==1023 for _,power,mask,_ in shots), 'No achievable physical strike'
assert max(m.bit_count() for _,p,m,_ in shots if p==0) < max(m.bit_count() for _,p,m,_ in shots if p==100)
for _,power,mask,_ in shots:
assert 0 <= mask.bit_count() <= 10, (power,mask)
print('Native pin-count ranges by power:', {p:(min(m.bit_count() for _,power,m,_ in shots if power==p),max(m.bit_count() for _,power,m,_ in shots if power==p)) for p in (0,50,100)})
hand = [Image.open(OUT/f'bowling-hand-{i:02}.bmp').convert('RGB') for i in range(33)]
assert len({im.crop((320,80,640,440)).tobytes() for im in hand}) >= 25
hand[0].save(OUT/'bowling-native-release.gif',save_all=True,append_images=hand[1:],duration=33,loop=0)
impacts = [Image.open(OUT/f'bowling-impact-{i}.bmp').convert('RGB') for i in range(4)]
assert len({im.tobytes() for im in impacts}) == 4
empty = Image.open(OUT/'bowling-shape-empty.bmp').convert('RGB')
tiny = Image.open(OUT/'bowling-shape-tiny.bmp').convert('RGB')
bounds = ImageChops.difference(empty,tiny).getbbox()
assert bounds and 8 <= bounds[2]-bounds[0] <= 120 and 8 <= bounds[3]-bounds[1] <= 120, bounds
setter = [Image.open(OUT/f'bowling-pinsetter-{i}.bmp').convert('RGB') for i in range(8)]
# A closed guard correctly hides intermediate hoist motion.
assert len({im.tobytes() for im in setter}) >= 6
setter[0].save(OUT/'bowling-native-pinsetter.gif',save_all=True,append_images=setter[1:],duration=320,loop=0)
print('PASS: weak rocking/strong toppling, separating contacts, fallen-pin impulses, late settlement, '
'stable unit axes and compact silhouettes after tiny impacts, per-step travel limits, '
'guard/hoist/respots, first-ball miss without cycling, no duplicate scoring, '
'full X/spare/miss scorecard, 33 native release frames and seven muted MCI channels/cleanup')
returned = [Image.open(OUT/f'bowling-return-{i}.bmp').convert('RGB') for i in range(8)]
assert len({im.tobytes() for im in returned}) == 8
returned[0].save(OUT/'bowling-native-return.gif',save_all=True,append_images=returned[1:],
duration=[400,500,100,300,400,500,600,250],loop=0)
print('PASS: visible return lift/rails, camera turn on either lane side, input/score invariants, '
'pause/resume, final-ball return, return-tray collision and exit restoration')
mass = list(struct.iter_unpack('<8i',(OUT/'bowling-mass-samples.bin').read_bytes()))
assert len(mass) == 12
for kg,power,speed,inv,incoming,ball,pin,locked in mass:
assert kg == locked and 4 <= kg <= 7
assert abs(inv/65536 - 1.6/kg) < .00002
work = kg*(speed/65536)**2/2
expected_work = 3*(3+6*power/100)**2
assert abs(work/expected_work-1) < .0001,(kg,power,work)
assert abs((-incoming*1000/16)/speed-1) < .0003
before = kg*incoming
after = kg*ball+1.6*pin
assert abs((after-before)/before) < .001,(kg,before,after)
assert abs((ball-pin)/-incoming-.5) < .002,(kg,ball,pin)
assert kg*ball**2+1.6*pin**2 <= kg*incoming**2
assert abs(pin/incoming - 1.5*kg/(kg+1.6)) < .002
for power in (0,50,100):
group = [s for s in mass if s[1] == power]
assert all(a[2] > b[2] for a,b in zip(group,group[1:]))
assert all(abs(a[5]/a[4]) < abs(b[5]/b[4]) for a,b in zip(group,group[1:]))
print('PASS: 4/5/6/7 kg selection, equal-work launch energy, momentum/restitution checks, '
'no impact energy gain, immutable shot mass and independent bot weight')
held = {kg:Image.open(OUT/f'bowling-weight-{kg}.bmp').convert('RGB') for kg in (4,5,7)}
assert len({im.crop((470,310,575,414)).tobytes() for im in held.values()}) == 3
rack = Image.open(OUT/'bowling-weight-rack.bmp').convert('RGB')
pair = Image.open(OUT/'bowling-weight-rack-pair.bmp').convert('RGB')
assert rack.tobytes() != pair.tobytes(), 'Rack selection must use each pair in the actual room'
print('PASS: in-world lengthwise ball rack, projected sphere/plate picking on both pairs, '
'non-overlapping labels, native colour/alpha checks, unchanged skin/sleeve and storage-rail collision')
mass_shots = list(struct.iter_unpack('<iiI',(OUT/'bowling-mass-shots.bin').read_bytes()))
assert len(mass_shots) == 84 and all(mask < 1024 for _,_,mask in mass_shots)
for kg in (4,5,6,7):
masks = [mask for k,_,mask in mass_shots if k == kg]
assert len(masks) == 21 and len(set(masks)) >= 4
print('Native high-power pin-count ranges by kg:',
{kg:(min(mask.bit_count() for k,_,mask in mass_shots if k==kg),
max(mask.bit_count() for k,_,mask in mass_shots if k==kg)) for kg in (4,5,6,7)})
print(f"PASS: eight bowling lanes, {len(reached)} connected walk points, reachable bar/dining/seats, native throw and room captures")
def check_npc_visits():
records = list(struct.iter_unpack('<8I', (OUT/'npc-visits.bin').read_bytes()))
assert len(records) == 10 and len({r[2] for r in records}) == 10
seen = 0
for slot,identity,cast,activities,places,walking,journeys,chips in records:
assert slot < 10 and identity < 12 and cast < 10
assert walking >= 50 and journeys >= 3 and places.bit_count() >= 2, records
assert chips < 100000
seen |= activities
assert seen & 0x1FE == 0x1FE, hex(seen)
captures = []
for kind in ('order','drink','seat','win','loss'):
picture = Image.open(OUT/f'npc-visit-{kind}.bmp').convert('RGB')
assert picture.size == (640,480) and max(ImageStat.Stat(picture).stddev) > 20
picture.save(OUT/f'npc-visit-{kind}.png')
captures.append(picture)
assert captures[0].tobytes() != captures[1].tobytes()
assert captures[3].tobytes() != captures[4].tobytes()
for action,count in (('sit',10),('rise',9)):
frames = [Image.open(OUT/f'npc-chair-{action}-{index}.bmp').convert('RGB')
for index in range(count)]
assert all(frame.size == (640,480) for frame in frames)
assert len({frame.tobytes() for frame in frames}) >= 8, action
frames[0].save(OUT/f'npc-chair-{action}.gif',save_all=True,
append_images=frames[1:],duration=100,loop=0)
print('PASS: ten stable visitor identities, 480 seconds of private schedules, exclusive '
'stations, game/bar/rest/chat transitions, grounded chair poses, pause, '
'unchanged player bankroll/RNG, real chair approach and sit/rise sequences')
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--parity",action="store_true")
parser.add_argument("--bowling-only",action="store_true")
parser.add_argument("--npc-only",action="store_true")
parser.add_argument("--prove-alpha-regression",action="store_true")
parser.add_argument("--prove-seam-regression",action="store_true")
parser.add_argument("--prove-distance-regression",action="store_true")
@ -291,6 +398,14 @@ def main():
source = source.replace(" call LoadHallAssets3D\n cmp characterCast3D.pixels,0",
" call LoadHallAssets3D\n call BowlingNativeTests\n invoke ExitProcess,0\n cmp characterCast3D.pixels,0")
(OUT/(stem+".asm")).write_text(source,encoding="cp1251")
if args.npc_only:
stem = "module-npc"
source = (OUT/"module.asm").read_text(encoding="cp1251")
source = source.replace("include tools/tests/hall_expansion_test.inc",
expand("tools/tests/hall_expansion_test.inc"))
source = source.replace(" call LoadHallAssets3D\n cmp characterCast3D.pixels,0",
" call LoadHallAssets3D\n call NpcVisitsNativeTests\n invoke ExitProcess,0\n cmp characterCast3D.pixels,0")
(OUT/(stem+".asm")).write_text(source,encoding="cp1251")
if args.prove_fate_anchor_regression:
stem = "module-fate-anchor-regression"
source = (OUT/"module.asm").read_text(encoding="cp1251")
@ -347,6 +462,10 @@ def main():
f"build/hall-expansion/{stem}.obj","build/hall-expansion/helpers.obj",
"kernel32.lib","user32.lib","gdi32.lib","winmm.lib","ws2_32.lib"],cwd=ROOT,check=True)
result = subprocess.run([str(OUT/(stem+".exe"))],cwd=ROOT,timeout=360)
if args.npc_only:
assert result.returncode == 0,result.returncode
check_npc_visits()
return
if args.bowling_only:
assert result.returncode == 0,result.returncode
check_bowling_room()
@ -373,6 +492,7 @@ def main():
if not args.prove_seam_regression:
check_rooftop_navigation()
check_bowling_room()
check_npc_visits()
check_character_cast()
closed = Image.open(OUT/"fortune-closed.bmp").tobytes()
opened = Image.open(OUT/"fortune-open.bmp").tobytes()

View file

@ -1000,8 +1000,9 @@ expansion_long_gait:
expansion_gait_bounds:
cmp npcWalkDistance3D[edi*4],FP_1*3/4
jae expansion_fail
imul ebx,edi,SIZEOF NpcMindEntity
invoke CanWalk3D,casinoNpcs3D[ebx].posX,casinoNpcs3D[ebx].posY
invoke NpcEntity3D,edi
mov ebx,eax
invoke CanWalk3D,[ebx].NpcMindEntity.posX,[ebx].NpcMindEntity.posY
cmp eax,1
jne expansion_fail
inc edi
@ -1175,6 +1176,7 @@ expansion_leaf_shut:
call ExpansionDartsTests
call RoofNativeTests
call BowlingNativeTests
call NpcVisitsNativeTests
invoke ExitProcess,0
expansion_fail:
invoke ExitProcess,expansionStage
@ -1187,3 +1189,4 @@ include tools/tests/darts_hall_test.inc
include tools/tests/rooftop_test.inc
include tools/tests/bowling_test.inc
include tools/tests/character_cast_test.inc
include tools/tests/npc_visits_test.inc

View file

@ -0,0 +1,240 @@
.data
ncTestSit0 db 'build\hall-expansion\npc-chair-sit-0.bmp',0
ncTestSit1 db 'build\hall-expansion\npc-chair-sit-1.bmp',0
ncTestSit2 db 'build\hall-expansion\npc-chair-sit-2.bmp',0
ncTestSit3 db 'build\hall-expansion\npc-chair-sit-3.bmp',0
ncTestSit4 db 'build\hall-expansion\npc-chair-sit-4.bmp',0
ncTestSit5 db 'build\hall-expansion\npc-chair-sit-5.bmp',0
ncTestSit6 db 'build\hall-expansion\npc-chair-sit-6.bmp',0
ncTestSit7 db 'build\hall-expansion\npc-chair-sit-7.bmp',0
ncTestSit8 db 'build\hall-expansion\npc-chair-sit-8.bmp',0
ncTestSit9 db 'build\hall-expansion\npc-chair-sit-9.bmp',0
ncTestChat db 'build\hall-expansion\npc-chair-chat.bmp',0
ncTestRise0 db 'build\hall-expansion\npc-chair-rise-0.bmp',0
ncTestRise1 db 'build\hall-expansion\npc-chair-rise-1.bmp',0
ncTestRise2 db 'build\hall-expansion\npc-chair-rise-2.bmp',0
ncTestRise3 db 'build\hall-expansion\npc-chair-rise-3.bmp',0
ncTestRise4 db 'build\hall-expansion\npc-chair-rise-4.bmp',0
ncTestRise5 db 'build\hall-expansion\npc-chair-rise-5.bmp',0
ncTestRise6 db 'build\hall-expansion\npc-chair-rise-6.bmp',0
ncTestRise7 db 'build\hall-expansion\npc-chair-rise-7.bmp',0
ncTestRise8 db 'build\hall-expansion\npc-chair-rise-8.bmp',0
ncTestSitPaths dd OFFSET ncTestSit0,OFFSET ncTestSit1,OFFSET ncTestSit2,OFFSET ncTestSit3,OFFSET ncTestSit4
dd OFFSET ncTestSit5,OFFSET ncTestSit6,OFFSET ncTestSit7,OFFSET ncTestSit8,OFFSET ncTestSit9
ncTestRisePaths dd OFFSET ncTestRise0,OFFSET ncTestRise1,OFFSET ncTestRise2,OFFSET ncTestRise3,OFFSET ncTestRise4
dd OFFSET ncTestRise5,OFFSET ncTestRise6,OFFSET ncTestRise7,OFFSET ncTestRise8
.code
NpcChairMotionNativeTests PROC uses ebx esi edi
local startX:DWORD,startY:DWORD,endX:DWORD,endY:DWORD,px:DWORD,py:DWORD
local ticks:DWORD,lastX:DWORD,lastY:DWORD
mov expansionStage,210
call InitNpcLife3D
mov playerX,FP_1*305/10
mov playerY,FP_1*305/10
invoke NpcPlanPath3D,0,7
cmp eax,1
jne nc_test_fail
mov eax,casinoNpcs3D.posX
mov startX,eax
mov eax,casinoNpcs3D.posY
mov startY,eax
mov esi,npcLife3D.pathCount
nc_test_edge:
dec esi
mov eax,npcPaths3D[esi*4]
mov edx,eax
and eax,31
shl eax,16
add eax,FP_HALF
mov endX,eax
shr edx,5
shl edx,16
add edx,FP_HALF
mov endY,edx
mov edi,1
nc_test_edge_sample:
mov eax,endX
sub eax,startX
imul eax,edi
sar eax,2
add eax,startX
mov px,eax
mov eax,endY
sub eax,startY
imul eax,edi
sar eax,2
add eax,startY
mov py,eax
invoke CanWalk3D,px,py
cmp eax,1
jne nc_test_fail
inc edi
cmp edi,4
jbe nc_test_edge_sample
mov eax,endX
mov startX,eax
mov eax,endY
mov startY,eax
test esi,esi
jnz nc_test_edge
mov expansionStage,211
mov npcLife3D.destination,7
mov casinoNpcs3D.posX,FP_1*135/10
mov casinoNpcs3D.posY,FP_1*195/10
invoke NpcArrive3D,0
mov npcSprite3D.posX,FP_1*135/10
mov npcSprite3D.posY,FP_1*195/10
invoke NpcResolveVisitPose3D,0
cmp npcSprite3D.posX,FP_1*135/10
jne nc_test_fail
cmp npcSprite3D.posY,FP_1*195/10
jne nc_test_fail
mov expansionStage,212
call InitNpcLife3D
mov esi,1
nc_test_hide:
imul ebx,esi,SIZEOF NpcLife3D
mov npcLife3D[ebx].state,NPC_AWAY
mov npcLife3D[ebx].remaining,1000000
invoke NpcReleaseStation3D,esi
inc esi
cmp esi,NPC_LIFE_COUNT
jb nc_test_hide
; The guest from the reported screenshot approaches the chair through gameplay.
mov npcLife3D.identity,3
invoke NpcReleaseStation3D,0
invoke NpcPlanPath3D,0,7
cmp eax,1
jne nc_test_fail
mov npcStationOwner3D[7*4],0
mov npcLife3D.state,NPC_WALK
mov npcVisitActivity3D,NV_IDLE
mov deltaTime3D,16
mov ticks,1500
nc_test_real_approach:
call UpdateNpcLife3D
cmp npcLife3D.state,NPC_SIT
je nc_test_sit_begin
cmp npcLife3D.state,NPC_WALK
jne nc_test_fail
dec ticks
jnz nc_test_real_approach
jmp nc_test_fail
nc_test_sit_begin:
cmp npcSitTime3D,0
jne nc_test_fail
mov playerX,14*FP_1
mov playerY,FP_1*222/10
mov playerAngle,384
call UpdateInteriorEye3D
xor esi,esi
mov deltaTime3D,0
nc_test_sit_frames:
call UpdateNpcLife3D
call ExpansionFrame
invoke ExpansionSnapshot,ncTestSitPaths[esi*4]
mov eax,npcSprite3D.frame
xor edx,edx
mov ecx,CAST_STRIDE
div ecx
mov ebx,edx
cmp esi,8
jae nc_test_sit_hold
lea eax,[esi+56]
cmp ebx,eax
jne nc_test_fail
jmp nc_test_sit_anchor
nc_test_sit_hold:
cmp ebx,12
jb nc_test_fail
cmp ebx,15
ja nc_test_fail
nc_test_sit_anchor:
mov eax,npcSprite3D.posX
test esi,esi
jz nc_test_sit_save
mov edx,lastX
sub edx,eax
cmp edx,0
jl nc_test_fail
cmp edx,FP_1/10
jg nc_test_fail
nc_test_sit_save:
mov lastX,eax
mov eax,npcSprite3D.posY
mov lastY,eax
mov deltaTime3D,100
inc esi
cmp esi,10
jb nc_test_sit_frames
mov expansionStage,213
; Changing from resting to conversation must not restart the sit-down strip.
invoke NpcEntity3D,1
mov [eax].NpcMindEntity.posX,FP_1*155/10
mov [eax].NpcMindEntity.posY,FP_1*195/10
mov npcLife3D[SIZEOF NpcLife3D].state,NPC_SIT
invoke NpcAdvanceVisit3D,0
cmp npcVisitActivity3D,NV_CHAT
jne nc_test_fail
cmp npcLife3D.poseClock,0
jne nc_test_fail
mov npcLife3D[SIZEOF NpcLife3D].state,NPC_AWAY
call ExpansionFrame
mov eax,npcSprite3D.frame
xor edx,edx
mov ecx,CAST_STRIDE
div ecx
cmp edx,16
jne nc_test_fail
mov eax,npcSprite3D.posX
cmp eax,lastX
jne nc_test_fail
mov eax,npcSprite3D.posY
cmp eax,lastY
jne nc_test_fail
invoke ExpansionSnapshot,OFFSET ncTestChat
invoke NpcChooseVisit3D,0
cmp eax,1
jne nc_test_fail
cmp npcRise3D,800
jne nc_test_fail
mov deltaTime3D,0
xor esi,esi
nc_test_rise_frames:
call UpdateNpcLife3D
call ExpansionFrame
invoke ExpansionSnapshot,ncTestRisePaths[esi*4]
mov eax,npcSprite3D.frame
xor edx,edx
mov ecx,CAST_STRIDE
div ecx
mov ebx,edx
test esi,esi
jnz nc_test_rise_anchor
mov eax,npcSprite3D.posX
cmp eax,lastX
jne nc_test_fail
mov eax,npcSprite3D.posY
cmp eax,lastY
jne nc_test_fail
nc_test_rise_anchor:
cmp esi,8
jae nc_test_rise_end
mov eax,63
sub eax,esi
cmp ebx,eax
jne nc_test_fail
jmp nc_test_rise_next
nc_test_rise_end:
cmp ebx,3
ja nc_test_fail
nc_test_rise_next:
mov deltaTime3D,100
inc esi
cmp esi,9
jb nc_test_rise_frames
ret
nc_test_fail:
invoke ExitProcess,expansionStage
ret
NpcChairMotionNativeTests ENDP

View file

@ -70,8 +70,9 @@ npc_test_simulate:
call UpdateNpcLife3D
xor esi,esi
npc_test_person_bounds:
imul ebx,esi,SIZEOF NpcMindEntity
invoke CanWalk3D,casinoNpcs3D[ebx].posX,casinoNpcs3D[ebx].posY
invoke NpcEntity3D,esi
mov ebx,eax
invoke CanWalk3D,[ebx].NpcMindEntity.posX,[ebx].NpcMindEntity.posY
cmp eax,1
jne npc_test_fail
imul edi,esi,SIZEOF NpcLife3D

View file

@ -0,0 +1,268 @@
.data
nvTestPath db 'build\hall-expansion\npc-visits.bin',0
nvTestOrder db 'build\hall-expansion\npc-visit-order.bmp',0
nvTestDrink db 'build\hall-expansion\npc-visit-drink.bmp',0
nvTestSeat db 'build\hall-expansion\npc-visit-seat.bmp',0
nvTestWin db 'build\hall-expansion\npc-visit-win.bmp',0
nvTestLoss db 'build\hall-expansion\npc-visit-loss.bmp',0
; slot, identity, cast, activities, destinations, walking ticks, journeys, chips.
nvTestRecords dd NPC_LIFE_COUNT*8 dup(0)
.code
NpcVisitsNativeTests PROC uses ebx esi edi
local ticks:DWORD,bank:DWORD,rng:DWORD,handle:DWORD,count:DWORD,bits:DWORD
local savedClock:DWORD,seenResults:DWORD
mov expansionStage,200
mov hallLevel3D,0
mov engineMode,MODE_WALK
mov worldMap3D,OFFSET casinoMap
mov playerX,FP_1*305/10
mov playerY,FP_1*305/10
mov eax,bankroll
mov bank,eax
mov eax,seed
mov rng,eax
call NpcChairMotionNativeTests
call InitNpcLife3D
mov bits,0
xor esi,esi
nv_test_identity:
imul ebx,esi,SIZEOF NpcLife3D
imul edi,esi,32
mov nvTestRecords[edi],esi
mov eax,npcLife3D[ebx].identity
mov nvTestRecords[edi+4],eax
mov eax,npcGuestAppearance3D[eax*4]
mov nvTestRecords[edi+8],eax
bts bits,eax
jc nv_test_fail
inc esi
cmp esi,NPC_LIFE_COUNT
jb nv_test_identity
cmp bits,1023
jne nv_test_fail
xor esi,esi
nv_test_paths:
invoke NpcPlanPath3D,0,esi
cmp eax,1
jne nv_test_fail
inc esi
cmp esi,NPC_DEST_COUNT
jb nv_test_paths
call InitNpcLife3D
mov expansionStage,201
mov deltaTime3D,50
mov ticks,9600
nv_test_tick:
call UpdateNpcLife3D
xor esi,esi
nv_test_person:
imul ebx,esi,SIZEOF NpcLife3D
imul edi,esi,32
mov eax,npcLife3D[ebx].identity
cmp eax,nvTestRecords[edi+4]
jne nv_test_fail
cmp npcVisitChips3D[esi*4],0
jl nv_test_fail
cmp npcVisitChips3D[esi*4],100000
ja nv_test_fail
mov eax,npcVisitActivity3D[esi*4]
cmp eax,NV_YIELD
ja nv_test_fail
bts nvTestRecords[edi+12],eax
mov eax,npcLife3D[ebx].destination
cmp eax,NPC_DEST_COUNT
jae nv_test_fail
cmp npcLife3D[ebx].state,NPC_WALK
jne @F
inc nvTestRecords[edi+20]
jmp nv_test_bounds
@@:
cmp npcVisitActivity3D[esi*4],NV_IDLE
je nv_test_bounds
cmp npcVisitActivity3D[esi*4],NV_YIELD
je nv_test_bounds
bts nvTestRecords[edi+16],eax
nv_test_bounds:
invoke NpcEntity3D,esi
mov ebx,eax
invoke CanWalk3D,[ebx].NpcMindEntity.posX,[ebx].NpcMindEntity.posY
cmp eax,1
jne nv_test_fail
inc esi
cmp esi,NPC_LIFE_COUNT
jb nv_test_person
xor esi,esi
mov bits,0
nv_test_station:
mov eax,npcStationOwner3D[esi*4]
cmp eax,-1
je nv_test_station_next
cmp eax,NPC_LIFE_COUNT
jae nv_test_fail
bts bits,eax
jc nv_test_fail
imul ebx,eax,SIZEOF NpcLife3D
cmp npcLife3D[ebx].destination,esi
jne nv_test_fail
cmp npcLife3D[ebx].state,NPC_AWAY
je nv_test_fail
nv_test_station_next:
inc esi
cmp esi,NPC_DEST_COUNT
jb nv_test_station
dec ticks
jnz nv_test_tick
mov expansionStage,202
xor esi,esi
nv_test_record:
imul edi,esi,32
imul ebx,esi,SIZEOF NpcLife3D
mov eax,npcLife3D[ebx].routeStep
mov nvTestRecords[edi+24],eax
mov eax,npcVisitChips3D[esi*4]
mov nvTestRecords[edi+28],eax
cmp nvTestRecords[edi+20],50
jb nv_test_fail
inc esi
cmp esi,NPC_LIFE_COUNT
jb nv_test_record
invoke CreateFileA,OFFSET nvTestPath,40000000h,0,0,2,80h,0
mov handle,eax
invoke WriteFile,handle,OFFSET nvTestRecords,SIZEOF nvTestRecords,ADDR count,0
invoke CloseHandle,handle
mov eax,bankroll
cmp eax,bank
jne nv_test_fail
mov eax,seed
cmp eax,rng
jne nv_test_fail
mov eax,npcLifeClock3D
mov savedClock,eax
mov engineMode,MODE_NPC_DIALOG
call UpdateNpcLife3D
mov eax,savedClock
cmp npcLifeClock3D,eax
jne nv_test_fail
mov engineMode,MODE_WALK
mov hallLevel3D,HALL_ROOF
call UpdateNpcLife3D
mov eax,savedClock
cmp npcLifeClock3D,eax
jne nv_test_fail
mov hallLevel3D,0
mov expansionStage,204
call InitNpcLife3D
mov esi,1
nv_test_yield_clear:
imul ebx,esi,SIZEOF NpcLife3D
mov npcLife3D[ebx].state,NPC_AWAY
inc esi
cmp esi,NPC_LIFE_COUNT
jb nv_test_yield_clear
invoke NpcReleaseStation3D,0
invoke NpcPlanPath3D,0,5
cmp eax,1
jne nv_test_fail
mov npcStationOwner3D[5*4],0
mov npcLife3D.state,NPC_WALK
mov eax,casinoNpcs3D.posX
mov playerX,eax
mov eax,casinoNpcs3D.posY
mov playerY,eax
invoke NpcMove3D,0,16
cmp npcVisitActivity3D,NV_YIELD
jne nv_test_fail
cmp npcLife3D.pathCount,0
je nv_test_fail
mov ticks,7
nv_test_yield_retry:
invoke NpcAdvanceVisit3D,0
invoke NpcMove3D,0,16
dec ticks
jnz nv_test_yield_retry
cmp npcLife3D.pathCount,0
jne nv_test_fail
cmp npcStationOwner3D[5*4],-1
jne nv_test_fail
cmp npcWalkDistance3D,0
jne nv_test_fail
mov expansionStage,203
call InitNpcLife3D
xor esi,esi
nv_test_hide:
imul ebx,esi,SIZEOF NpcLife3D
mov npcLife3D[ebx].state,NPC_AWAY
inc esi
cmp esi,NPC_LIFE_COUNT
jb nv_test_hide
mov npcLife3D.destination,10
mov casinoNpcs3D.posX,FP_1*65/10
mov casinoNpcs3D.posY,FP_1*255/10
invoke NpcArrive3D,0
mov playerX,FP_1*65/10
mov playerY,FP_1*282/10
mov playerAngle,384
call UpdateInteriorEye3D
call ExpansionFrame
invoke ExpansionSnapshot,OFFSET nvTestOrder
invoke NpcAdvanceVisit3D,0
cmp npcVisitActivity3D,NV_DRINK
jne nv_test_fail
mov npcLife3D.poseClock,360
call ExpansionFrame
invoke ExpansionSnapshot,OFFSET nvTestDrink
mov npcLife3D.destination,7
mov casinoNpcs3D.posX,FP_1*135/10
mov casinoNpcs3D.posY,FP_1*195/10
invoke NpcArrive3D,0
mov npcLife3D.poseClock,1200
mov npcSitTime3D,800
mov playerX,13*FP_1
mov playerY,FP_1*215/10
call ExpansionFrame
invoke ExpansionSnapshot,OFFSET nvTestSeat
mov npcSprite3D.posX,FP_1*135/10
mov npcSprite3D.posY,FP_1*195/10
invoke NpcResolveVisitPose3D,0
cmp eax,CAST_SEAT
jne nv_test_fail
cmp npcSprite3D.posX,13*FP_1
jne nv_test_fail
cmp npcSprite3D.posY,FP_1*195/10-FP_1*35/100
jne nv_test_fail
mov seenResults,0
mov ticks,32
mov npcLife3D.destination,2
mov casinoNpcs3D.posX,FP_1*105/10
mov casinoNpcs3D.posY,FP_1*135/10
mov playerX,FP_1*105/10
mov playerY,16*FP_1
nv_test_result:
invoke NpcArrive3D,0
invoke NpcAdvanceVisit3D,0
mov npcLife3D.poseClock,360
call ExpansionFrame
cmp npcVisitActivity3D,NV_WIN
jne nv_test_loss
invoke ExpansionSnapshot,OFFSET nvTestWin
or seenResults,1
jmp nv_test_result_next
nv_test_loss:
cmp npcVisitActivity3D,NV_LOSS
jne nv_test_fail
invoke ExpansionSnapshot,OFFSET nvTestLoss
or seenResults,2
nv_test_result_next:
cmp seenResults,3
je nv_test_done
dec ticks
jnz nv_test_result
jmp nv_test_fail
nv_test_done:
call InitNpcLife3D
ret
nv_test_fail:
invoke ExitProcess,expansionStage
ret
NpcVisitsNativeTests ENDP
include tools/tests/npc_chair_motion_test.inc