feat: add bowling hall and unified visitor animations

This commit is contained in:
Artvasil09 2026-10-30 18:39:59 +03:00
parent 1006ba444b
commit 996c01af9f
96 changed files with 6398 additions and 115 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 986 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 567 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

View file

@ -0,0 +1,244 @@
{
"materials": [
256,
256,
14
],
"bowlers": [
256,
256,
64
],
"seated": [
256,
256,
8
],
"lanes": 8,
"active_lanes": [
0,
3,
6
],
"room": [
30,
30,
6
],
"props": 80,
"collisions": [
[
2.3800000000000003,
5.52,
27.48,
28.720000000000002
],
[
3.18,
4.72,
26.529999999999998,
27.42
],
[
3.4000000000000004,
4.5,
24.63,
26.17
],
[
6.18,
9.32,
27.48,
28.720000000000002
],
[
6.98,
8.52,
26.529999999999998,
27.42
],
[
7.2,
8.3,
24.63,
26.17
],
[
9.98,
13.120000000000001,
27.48,
28.720000000000002
],
[
10.780000000000001,
12.32,
26.529999999999998,
27.42
],
[
11.0,
12.100000000000001,
24.63,
26.17
],
[
13.779999999999998,
16.919999999999998,
27.48,
28.720000000000002
],
[
14.579999999999998,
16.119999999999997,
26.529999999999998,
27.42
],
[
14.799999999999997,
15.899999999999999,
24.63,
26.17
],
[
20.83,
23.17,
11.08,
12.92
],
[
20.729999999999997,
23.270000000000003,
12.930000000000001,
14.17
],
[
20.83,
23.17,
17.08,
18.92
],
[
20.729999999999997,
23.270000000000003,
18.93,
20.17
],
[
25.83,
28.17,
11.08,
12.92
],
[
25.729999999999997,
28.270000000000003,
12.930000000000001,
14.17
],
[
25.83,
28.17,
17.08,
18.92
],
[
25.729999999999997,
28.270000000000003,
18.93,
20.17
],
[
23.83,
30.17,
4.33,
5.67
],
[
23.83,
30.17,
0.9799999999999999,
1.6199999999999999
],
[
24.75,
25.25,
6.53,
7.03
],
[
26.75,
27.25,
6.53,
7.03
],
[
28.75,
29.25,
6.53,
7.03
],
[
18.93,
19.470000000000002,
0.83,
24.970000000000002
]
],
"seats": [
[
3.95,
28.0,
384
],
[
7.75,
28.0,
384
],
[
11.55,
28.0,
384
],
[
15.349999999999998,
28.0,
384
],
[
22,
13.450000000000001,
384
],
[
22,
19.45,
384
],
[
27,
13.450000000000001,
384
],
[
27,
19.45,
384
],
[
25,
6.8,
384
],
[
27,
6.8,
384
],
[
29,
6.8,
384
]
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 MiB

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,18 @@
{
"mode": "built-in imagegen",
"reference": "person-00-source-v1.png (style/layout only for other identities)",
"prompt_template": "Production 2.5D game sprite sheet, exactly 32 full-body poses of ONE IDENTICAL adult PERSON in exactly 8 columns by 4 rows. Transparent alpha background, no scenery, text, grid lines or chairs. Reference image is STYLE/LAYOUT ONLY, create the NEW character described. Photorealistic painted adventure-game sprite style, crisp realistic anatomy, natural proportions, neutral soft lighting. Full shoes and head visible in every cell, with empty margins. Same face, hairstyle, body and costume throughout. Character: DESCRIPTION. Keep one physical scale and a shared feet baseline per row; seated figures must be naturally shorter, not enlarged. Row 1 left to right: front idle; front talking gesture; front drinking a small glass; seated idle knees bent feet on floor; seated talking; seated drinking; halfway lowering to a chair (no chair); bending to pick up a blue bowling ball. Row 2: six chronological full gait-cycle frames walking TOWARD camera, then standing BACK view, then celebrating. Row 3: six chronological full gait-cycle frames walking AWAY from camera, then three-quarter rear view holding a blue bowling ball ready, then full-body bowling backswing. Row 4: six chronological full gait-cycle frames walking RIGHT in profile, then full-body low UNDERARM bowling release with an empty throwing hand (no detached ball), then bowling follow-through. Exactly 8 figures per row. All limbs connected, no duplicates or cell overlaps. Not cartoon, not pixel art.",
"characters": [
"32-year-old slim man, neatly combed short black hair, clean shaven, open navy blazer, ivory shirt, burgundy trousers, brown leather loafers",
"29-year-old slim woman, auburn bob haircut, emerald trouser suit, ivory blouse, black flat loafers",
"55-year-old stocky bald man with prominent dark moustache, burgundy waistcoat, white shirt sleeves rolled to elbows, charcoal trousers, brown leather shoes",
"64-year-old woman, aged face, silver short wavy bob, plum cardigan, pearl-gray blouse, dark teal calf-length skirt, black comfortable flat shoes and pearl necklace",
"40-year-old Black man, dark brown skin, short tight curls and trimmed beard, athletic medium build, gray rolled-sleeve shirt, cobalt trousers, dark leather sneakers",
"36-year-old woman with olive skin and long dark hair in one braid, coral blouse, charcoal tailored trousers and black flat loafers",
"25-year-old slender man with dark round glasses, wavy chestnut hair, mustard knitted sweater over a white collar, navy chinos, white sneakers",
"45-year-old woman with short black pixie haircut, silver-gray tailored jacket over burgundy top, navy trousers, black flats",
"68-year-old silver-haired man with short neat beard, olive blazer over a burgundy turtleneck, charcoal trousers, black loafers",
"26-year-old woman with shoulder-length curly brown hair, cobalt-blue long-sleeve belted jumpsuit, white flat sneakers"
],
"note": "First five sheets used the same 32-pose layout and original equivalent character-specific briefs; full original raster sheets are retained. Whole sheets, not independently generated action frames."
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 577 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 627 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 773 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 719 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 686 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 692 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 712 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 594 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 654 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 641 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 627 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 729 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

View file

@ -0,0 +1,76 @@
{
"mode": "built-in imagegen",
"prompt": "Create production walking sprites of the EXACT referenced bald moustached stocky man (burgundy vest, white rolled sleeves, charcoal pants, brown shoes). Replace the broken same-leg poses with a real walk. TRANSPARENT background, 3 rows x 4 columns, 12 full body cutouts. ROW1 FRONT view. ROW2 REAR view. ROW3 RIGHT-facing SIDE view. COLUMN1: wide stride contact, LEFT leg forward, RIGHT leg back. COLUMN2: narrow PASSING pose, feet nearly together under hips, RIGHT KNEE lifted/bent to step through. COLUMN3: wide stride contact, RIGHT leg forward, LEFT leg back, counter-swing opposite arm. COLUMN4: narrow PASSING pose, LEFT KNEE lifted/bent stepping through. This is a chronological animation cycle: wide, narrow, wide opposite, narrow opposite. Especially in side row columns2 and4 MUST have legs CLOSE TOGETHER under the hips, NOT another wide stride. Preserve this man's recognizable identity and clothing, fixed head size, entire head and shoes visible, aligned floor in each row. No idle/talk/ball poses, no panels, no text. 1536x1024 canvas.",
"characters": [
{
"id": 0,
"description": "slim black-haired man navy blazer ivory shirt burgundy trousers brown loafers",
"source": "person-00-source-v1.png",
"output": "person-00-walk-source-v2.png",
"generated": "C:\\Users\\user\\.codex\\generated_images\\019e0a0d-b269-7211-8574-91b6289060a2\\exec-b25bc01c-dc99-4912-b299-5b5718f9716a.png"
},
{
"id": 1,
"description": "auburn bob-haired woman emerald trouser suit ivory blouse black flats",
"source": "person-01-source-v1.png",
"output": "person-01-walk-source-v2.png",
"generated": "C:\\Users\\user\\.codex\\generated_images\\019e0a0d-b269-7211-8574-91b6289060a2\\exec-43e73f66-1c2d-4c65-aad4-c51e8d905e24.png"
},
{
"id": 2,
"description": "older stocky bald moustached man burgundy waistcoat white rolled-sleeve shirt charcoal trousers brown shoes",
"source": "person-02-source-v1.png",
"output": "person-02-walk-source-v2.png",
"generated": "C:\\Users\\user\\.codex\\generated_images\\019e0a0d-b269-7211-8574-91b6289060a2\\exec-19b83697-2a18-4cc9-a8a3-b3e8a1b0251b.png"
},
{
"id": 3,
"description": "silver bob-haired older woman plum cardigan teal knee-length skirt pearl necklace black flats",
"source": "person-03-source-v1.png",
"output": "person-03-walk-source-v2.png",
"generated": "C:\\Users\\user\\.codex\\generated_images\\019e0a0d-b269-7211-8574-91b6289060a2\\exec-35140d2f-0648-4075-a44c-18e736db4df5.png"
},
{
"id": 4,
"description": "dark-skinned bearded man gray rolled-sleeve shirt cobalt trousers dark sneakers",
"source": "person-04-source-v1.png",
"output": "person-04-walk-source-v2.png",
"generated": "C:\\Users\\user\\.codex\\generated_images\\019e0a0d-b269-7211-8574-91b6289060a2\\exec-8f98702f-5ee4-492d-8fad-153b3b854507.png"
},
{
"id": 5,
"description": "dark-braided woman coral blouse charcoal trousers black loafers",
"source": "person-05-source-v1.png",
"output": "person-05-walk-source-v2.png",
"generated": "C:\\Users\\user\\.codex\\generated_images\\019e0a0d-b269-7211-8574-91b6289060a2\\exec-89ab73fb-b907-4d67-af08-4f2c99617719.png"
},
{
"id": 6,
"description": "young wavy-haired man glasses mustard sweater navy chinos white sneakers",
"source": "person-06-source-v1.png",
"output": "person-06-walk-source-v2.png",
"generated": "C:\\Users\\user\\.codex\\generated_images\\019e0a0d-b269-7211-8574-91b6289060a2\\exec-037ccc00-51c1-4b3b-9802-47d6cf38cf3f.png"
},
{
"id": 7,
"description": "black pixie-haired woman gray jacket burgundy top navy trousers black flats",
"source": "person-07-source-v1.png",
"output": "person-07-walk-source-v2.png",
"generated": "C:\\Users\\user\\.codex\\generated_images\\019e0a0d-b269-7211-8574-91b6289060a2\\exec-3d667111-3659-4d90-83ba-3e3788c5dad3.png"
},
{
"id": 8,
"description": "silver-haired bearded man olive blazer burgundy turtleneck charcoal trousers dark shoes",
"source": "person-08-source-v1.png",
"output": "person-08-walk-source-v2.png",
"generated": "C:\\Users\\user\\.codex\\generated_images\\019e0a0d-b269-7211-8574-91b6289060a2\\exec-a6c66cea-f171-4d99-a7b3-fd957fabcaaa.png"
},
{
"id": 9,
"description": "curly-haired woman cobalt belted jumpsuit white sneakers",
"source": "person-09-source-v1.png",
"output": "person-09-walk-source-v2.png",
"generated": "C:\\Users\\user\\.codex\\generated_images\\019e0a0d-b269-7211-8574-91b6289060a2\\exec-feafc4a0-9339-457f-b4f7-18aacd013ad5.png"
}
]
}

Binary file not shown.

View file

@ -8,7 +8,7 @@ namespace AssemblerCasinoLauncher
{
static class Program
{
const string ExpectedPokerSha256 = "CF04525CD5C9C87412001FF4F4CBBCA1C7019F71A53FEBA3BB8051323B0EB634";
const string ExpectedPokerSha256 = "035EC055C92D19129F07250F062EAB6FB91F417D3341F8A98BCE3188064F2196";
[STAThread]
static void Main()

Binary file not shown.

View file

@ -5353,6 +5353,8 @@ check_walk_mode:
; The roof owns all planes/depth; an indoor prepass would be discarded.
cmp hallLevel3D,HALL_ROOF
je walk_world_ready
cmp hallLevel3D,HALL_BOWLING
je walk_world_ready
call RenderCeilingAndFloor
call RenderWalls
call PrepareCityDepth3D

Binary file not shown.

63
docs/BOWLING.md Normal file
View file

@ -0,0 +1,63 @@
# Bowling Lounge
The bowling lounge is a walkable room, not a player-controlled bowling minigame.
Enter through the wide opening in the north wall of the main casino, near the
slot-machine side of the hall. Walk through to enter or leave; no interaction
button or timed stair/elevator animation is used.
## Room
- Eight lanes with wooden approaches, gutters, ten pins and overhead displays.
- Three occupied lanes and five free lanes.
- Paired sofas, ball returns, dining booths, a staffed bar and circulating guests.
- All ten visitors use the shared character cast, including seated, drinking,
walking, pick-up, throwing and return animations. See `SHARED_CHARACTERS.md`.
- E sits on an available lounge seat or gets up. W/S also gets up.
- The bar uses the existing drink purchase flow and restores the room camera.
All room geometry, rendering, input, scoring and visitor simulation run in MASM.
Furniture is built from fixed world-space surfaces, not rotating room billboards.
Human sprites retain the game's existing 2.5D presentation.
## Simulation
Each occupied lane owns a timer, rack mask, two players, frame counters and roll
history. The sequence is pick-up, approach/release, ball travel, pin impact,
pinsetter sweep, return and a pause before the next attempt/player.
Scoreboards use native ten-frame bowling scoring, including strike/spare bonuses
and tenth-frame bonus balls. These exhibition games do not change the bankroll.
The room owns its random state; visitor games do not consume the casino RNG.
Simulation pauses when the player is in an unrelated room. Artwork is loaded on
the first visible entrance/visit, retained for subsequent visits and freed with
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.
- `tools/generators/pack_bowling.py`: offline atlas packing, frame normalization,
in-between frames and furniture/collision tables.
- `assets/sprites/bowling/`: retained image-generation sources.
- `assets/animations/bowling/`: packed shipping assets.
Image-generation source briefs: twelve bowling-room material swatches (maple,
teal/coral textile, acoustic panels, mural, marble, metal and ball-return front);
a whole-body male approach/underarm-release/pick-up sequence; the corresponding
female bowler sequence; four distinct seated adult visitors in idle and
conversation/drinking poses. The edited female sheet preserves transparency.
These are original generated assets, not extracted FNAF artwork.
Run the offline packer with Python, then build the MASM executable normally.
## Checks
`tools/tests/check_hall_expansion.py --bowling-only` compiles fresh assembly and
checks entrance visibility, walkable access, both room transitions, lane/furniture
collision, sitting/restoration, 600 simulated seconds of lane lifecycles, perfect
game/all-spare/open-frame score vectors, and captures room/throw screenshots.
The 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.
Screenshots are native framebuffer captures, not a substitute for final manual
playtesting on the target laptop.

77
docs/SHARED_CHARACTERS.md Normal file
View file

@ -0,0 +1,77 @@
# Shared Visitor Cast
Ten generated adult visitors now share one identity-locked animation system in
the main casino, upper lounge, rooftop and bowling room. Dealers, bartenders and
stage performers remain separate job-specific characters.
## Identity And Animation
Each identity owns a fixed block of 100 slots in `cast-v1.bin`. Changing an action
never selects a different character block. The renderer chooses front, back,
left or right gait from actual movement relative to the camera. Walk frames are
distance-driven and stop when movement stops.
The block contains idle, conversation, drinking, seated idle/conversation/drinking,
four gait directions, sitting down, standing up (reverse of sitting), picking up
a ball, underarm throw/follow-through and celebration. Idle slots deliberately
hold a stable pose. Optical-flow in-betweens are generated offline, not by the
game. These remain 2.5D sprites, not skeletal 3D humans.
All states use the same physical canvas, scale and foot anchor. Seated sprites
are shorter poses on that canvas, not separately enlarged portraits. A walking
casino visitor pauses movement while standing up from a chair.
The canvas height is 1.3 world units (192:224 aspect), calibrated against the
hall's 1.05-unit standing eye height. The former 1.95-unit canvas made visitors
oversized compared with the camera, staff and furniture.
Walking uses replacement `person-*-walk-source-v2.png` sheets with contact and
passing poses in front, rear and right profile. The left profile is mirrored.
Each sheet has a common scale and ground anchor. The generator records these
crops in the cast manifest; `walking-generation-v2.json` records built-in image
generation provenance. Non-walking actions retain their original source art.
Bowling uses three occupied lanes with six persistent players. Four other
identities occupy the restaurant/bar, giving ten distinct visitors without
duplicating an active bowler as a seated stranger. Players walk to the ball,
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.
## Assets
- `assets/sprites/characters/person-00-source-v1.png` through `person-09-source-v1.png`:
original full sheets generated with built-in imagegen.
- `assets/sprites/characters/generation-v1.json`: generation brief and identities.
- `assets/sprites/characters/cast-v1.json`: action offsets, source crops and scale.
- `assets/sprites/characters/roster-v1.png`: normalized cast preview.
- `assets/sprites/characters/person-*-motion-v1.gif`: motion review previews.
- `assets/animations/characters/cast-v1.bin`: native ANM1 shipping pack.
`tools/generators/pack_character_cast.py` rebuilds the pack from retained project
sources. It isolates silhouettes with alpha, handles extra generated walking
poses, normalizes each identity once and creates intermediate frames.
The raw pack uses about 164 MiB of process RAM and is loaded once, shared by all
locations, then released with the hall. It is CPU sprite data, not a separate
texture allocation per location. The ordinary animation-loader limits remain
unchanged; only this exact cast pointer, dimensions, count, payload and file size
can use the larger pack. No Python, neural model or network is used at runtime.
## Verification
`tools/tests/character_cast_test.inc` executes the native state resolver for all
ten identities and twelve states, checks identity boundaries and stable scale,
and captures sixty native poses plus eighty native walking frames.
`tools/tests/check_character_cast.py` checks
distinct identities, four animated gait directions, mirrored profile frames,
seated proportions, transparent borders, unclipped feet and throw progression.
It also checks native eye-level body scale and a substantial difference between
wide contact and narrow passing foot spans. Distinct pixel hashes alone did not
catch the old sheets' repeated leading-leg poses.
The full hall regression includes room navigation, roof, bars, doors and stairs.
Shipping/tested assembly parity is checked separately.
The older guest and bowling source packs are retained as historical assets;
the visitor renderers no longer load or use them.

BIN
poker.exe

Binary file not shown.

254
src_graphics/bowling.inc Normal file
View file

@ -0,0 +1,254 @@
; Native room, automatic lane simulation and full-body visitors; no AI service.
InitBowling3D PROC uses esi edi
mov bwClock3D,-1
mov bwMonitorClock3D,-1
mov bwSeat3D,0
xor esi,esi
bw_init_lane:
mov bwState3D[esi*4],0
imul eax,esi,349
mov bwTime3D[esi*4],eax
mov bwTurn3D[esi*4],0
mov bwBall3D[esi*4],0
mov bwRack3D[esi*4],1023
mov bwHit3D[esi*4],0
mov bwNewRack3D[esi*4],0
mov bwFrameDone3D[esi*4],0
inc esi
cmp esi,BW_LANES
jb bw_init_lane
lea edi,bwFrames3D
xor eax,eax
mov ecx,16
rep stosd
lea edi,bwRollCount3D
mov ecx,16
rep stosd
lea edi,bwScores3D
mov ecx,16
rep stosd
ret
InitBowling3D ENDP
LoadBowling3D PROC
cmp bwArt3D.pixels,0
jne bw_load_done
invoke LoadAnimPack3D,OFFSET bwArt3D,OFFSET bwArtPath3D
invoke ValidateCityPack3D,OFFSET bwArt3D,256,256,14
call LoadCharacterCast3D
call RefreshBowlingMonitors3D
bw_load_done:
ret
LoadBowling3D ENDP
FreeBowling3D PROC
invoke FreeAnimPack3D,OFFSET bwArt3D
invoke FreeAnimPack3D,OFFSET bwMotionArt3D
invoke FreeAnimPack3D,OFFSET bwSeatedArt3D
ret
FreeBowling3D ENDP
ResolveBowlingSurface3D PROC material:DWORD
mov eax,material
cmp eax,252
jne @F
mov eax,OFFSET bwSignPixels3D
mov ecx,128
mov edx,16
ret
@@:
cmp eax,244
jb bw_material_atlas
sub eax,244
cmp eax,7
ja bw_material_missing
shl eax,15
add eax,OFFSET bwMonitors3D
mov ecx,128
mov edx,64
ret
bw_material_atlas:
sub eax,BW_MATERIAL
cmp eax,13
ja bw_material_missing
cmp bwArt3D.pixels,0
je bw_material_missing
shl eax,18
add eax,bwArt3D.pixels
mov ecx,256
mov edx,256
ret
bw_material_missing:
xor eax,eax
ret
ResolveBowlingSurface3D ENDP
BowlingTravel3D PROC
cmp engineMode,MODE_WALK
jne bw_travel_done
cmp hallLevel3D,HALL_BOWLING
je bw_travel_out
cmp hallLevel3D,0
jne bw_travel_done
cmp playerX,3*FP_1
jb bw_travel_done
cmp playerX,7*FP_1
ja bw_travel_done
cmp playerY,FP_1*15/10
jae bw_travel_done
add playerX,12*FP_1
add playerY,29*FP_1
mov hallLevel3D,HALL_BOWLING
mov worldMap3D,OFFSET bwMap3D
mov floorTexture3D,231
call LoadBowling3D
call BowlingEye3D
ret
bw_travel_out:
cmp playerY,FP_1*305/10
jbe bw_travel_done
cmp playerX,15*FP_1
jb bw_travel_done
cmp playerX,19*FP_1
ja bw_travel_done
sub playerX,12*FP_1
sub playerY,29*FP_1
mov hallLevel3D,0
mov worldMap3D,OFFSET casinoMap
mov floorTexture3D,100
mov bwSeat3D,0
mov viewCeiling3D,FP_1*5/2
mov viewEye3D,HALL_STANDING_EYE
bw_travel_done:
ret
BowlingTravel3D ENDP
BowlingEye3D PROC
mov viewHorizon3D,HALF_H
mov viewCeiling3D,6*FP_1
mov viewEye3D,HALL_STANDING_EYE
cmp bwSeat3D,0
je @F
mov viewEye3D,FP_1*12/10
@@:
ret
BowlingEye3D ENDP
BowlingCanWalk3D PROC uses esi edi px:DWORD,py:DWORD
cmp bwSeat3D,0
jne bw_walk_block
cmp px,FP_1*12/10
jb bw_walk_block
cmp px,FP_1*308/10
ja bw_walk_block
cmp py,FP_1*12/10
jb bw_walk_block
cmp py,FP_1*308/10
ja bw_walk_block
cmp px,19*FP_1
jae bw_walk_furniture
cmp px,2*FP_1
jb bw_walk_furniture
cmp px,FP_1*173/10
ja bw_walk_furniture
cmp py,FP_1*223/10
jb bw_walk_block
bw_walk_furniture:
lea esi,bwCollision3D
mov edi,BOWL_COLLISION_COUNT
bw_walk_prop:
mov eax,px
cmp eax,[esi]
jb bw_walk_next
cmp eax,[esi+4]
ja bw_walk_next
mov eax,py
cmp eax,[esi+8]
jb bw_walk_next
cmp eax,[esi+12]
jbe bw_walk_block
bw_walk_next:
add esi,16
dec edi
jnz bw_walk_prop
mov eax,1
ret
bw_walk_block:
xor eax,eax
ret
BowlingCanWalk3D ENDP
CheckBowlingInteractions3D PROC uses esi edi
cmp bwSeat3D,0
je bw_check_seats
mov interactTargetId,INTERACT_BOWL_SEAT
mov interactPromptPtr,OFFSET bwStandPrompt3D
ret
bw_check_seats:
lea esi,bwSeats3D
xor edi,edi
bw_check_seat:
cmp edi,4
jb bw_check_available_seat
cmp edi,9
jne bw_check_next_seat
bw_check_available_seat:
mov interactTargetId,0
invoke HallTarget3D,[esi],[esi+4],INTERACT_BOWL_SEAT,OFFSET bwSeatPrompt3D
cmp interactTargetId,INTERACT_BOWL_SEAT
jne @F
mov bwSeatTarget3D,edi
ret
@@:
bw_check_next_seat:
add esi,12
inc edi
cmp edi,BOWL_SEAT_COUNT
jb bw_check_seat
invoke HallTarget3D,27*FP_1,FP_1*59/10,INTERACT_BAR,OFFSET bwBarName3D
ret
CheckBowlingInteractions3D ENDP
TryBowlingInteraction3D PROC uses esi
cmp hallLevel3D,HALL_BOWLING
jne bw_try_none
cmp interactTargetId,INTERACT_BOWL_SEAT
jne bw_try_none
cmp bwSeat3D,0
jne bw_try_stand
mov eax,playerX
mov bwReturnX3D,eax
mov eax,playerY
mov bwReturnY3D,eax
mov eax,playerAngle
mov bwReturnAngle3D,eax
mov esi,bwSeatTarget3D
imul esi,12
mov eax,bwSeats3D[esi]
mov playerX,eax
mov eax,bwSeats3D[esi+4]
mov playerY,eax
mov eax,bwSeats3D[esi+8]
mov playerAngle,eax
mov bwSeat3D,1
jmp bw_try_handled
bw_try_stand:
mov eax,bwReturnX3D
mov playerX,eax
mov eax,bwReturnY3D
mov playerY,eax
mov eax,bwReturnAngle3D
mov playerAngle,eax
mov bwSeat3D,0
bw_try_handled:
call BowlingEye3D
call ClearKeys3D
mov eax,1
ret
bw_try_none:
xor eax,eax
ret
TryBowlingInteraction3D ENDP
include src_graphics\bowling_simulation.inc
include src_graphics\bowling_render.inc

View file

@ -0,0 +1,98 @@
HALL_BOWLING equ 5
INTERACT_BOWL_SEAT equ 53
INTERACT_BOWL_WATCH equ 54
BW_MATERIAL equ 230
BW_LAST_MATERIAL equ 252
BW_LANES equ 8
BW_LANE_STEP equ FP_1*19/10
InitBowling3D PROTO
LoadBowling3D PROTO
FreeBowling3D PROTO
ResolveBowlingSurface3D PROTO :DWORD
UpdateBowling3D PROTO
BowlingTravel3D PROTO
BowlingEye3D PROTO
BowlingCanWalk3D PROTO :DWORD,:DWORD
CheckBowlingInteractions3D PROTO
TryBowlingInteraction3D PROTO
RenderBowling3D PROTO
RenderBowlingEntrance3D PROTO
RenderBowlingPlanes3D PROTO
RenderBowlingLanes3D PROTO
RenderBowlingVisitors3D PROTO
DrawBowlingPin3D PROTO :DWORD,:DWORD,:DWORD
DrawBowlingBall3D PROTO :DWORD,:DWORD,:DWORD
BowlingImpact3D PROTO :DWORD
BowlingRegisterRoll3D PROTO :DWORD,:DWORD
BowlingScore3D PROTO :DWORD
BowlingMonitorText3D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
RefreshBowlingMonitors3D PROTO
.data
bwArt3D AnimPack3D <>
bwMotionArt3D AnimPack3D <>
bwSeatedArt3D AnimPack3D <>
bwArtPath3D db "assets\animations\bowling\materials-v1.bin",0
bwMotionPath3D db "assets\animations\bowling\bowlers-v1.bin",0
bwSeatedPath3D db "assets\animations\bowling\seated-v1.bin",0
bwEntrancePrompt3D db 193,206,211,203,200,205,195,0
bwExitPrompt3D db 202,192,199,200,205,206,0
bwSeatPrompt3D db 207,208,200,209,197,209,210,220,0
bwStandPrompt3D db 194,209,210,192,210,220,0
bwWatchPrompt3D db 205,192,193,203,222,196,192,210,220,32,199,192,32,200,195,208,206,201,0
bwBarName3D db 193,192,208,32,193,206,211,203,200,205,195,192,0
bwLaneWord3D db "LANE ",0
bwPlayerA3D db "A",0
bwPlayerB3D db "B",0
bwFreeText3D db "FREE",0
bwFrameText3D db "FRAME",0
bwPhaseTexts3D dd OFFSET bwRestText3D,OFFSET bwPickText3D,OFFSET bwThrowText3D
dd OFFSET bwRollText3D,OFFSET bwSweepText3D,OFFSET bwReturnText3D,OFFSET bwRestText3D
bwRestText3D db "NEXT PLAYER",0
bwPickText3D db "READY",0
bwThrowText3D db "APPROACH",0
bwRollText3D db "BALL IN PLAY",0
bwSweepText3D db "PINSETTER",0
bwReturnText3D db "BALL RETURN",0
bwSignText3D db "BOWLING",0
bwLoungeText3D db "LOUNGE",0
bwEightText3D db "8 LANES",0
bwClock3D dd -1
bwMonitorClock3D dd -1
bwRng3D dd 312871
bwSeat3D dd 0
bwSeatTarget3D dd 0
bwWatchLane3D dd 0
bwReturnX3D dd 0
bwReturnY3D dd 0
bwReturnAngle3D dd 0
bwBusy3D dd 1,0,0,1,0,0,1,0
bwCharacterIds3D dd 0,1,6,7,8,9,2,3,6,7,8,9,4,5,6,7
bwState3D dd BW_LANES dup(0)
bwTime3D dd BW_LANES dup(0)
bwTurn3D dd BW_LANES dup(0)
bwBall3D dd BW_LANES dup(0)
bwFirstHit3D dd BW_LANES dup(0)
bwRack3D dd BW_LANES dup(1023)
bwHit3D dd BW_LANES dup(0)
bwAim3D dd BW_LANES dup(0)
bwNewRack3D dd BW_LANES dup(0)
bwFrameDone3D dd BW_LANES dup(0)
bwFrames3D dd 16 dup(0)
bwRollCount3D dd 16 dup(0)
bwScores3D dd 16 dup(0)
bwRolls3D dd 16*24 dup(0)
bwPinX3D dd 0,-FP_1*16/100,FP_1*16/100,-FP_1*32/100,0,FP_1*32/100
dd -FP_1*48/100,-FP_1*16/100,FP_1*16/100,FP_1*48/100
bwPinY3D dd FP_1*47/10,FP_1*44/10,FP_1*44/10
dd FP_1*41/10,FP_1*41/10,FP_1*41/10
dd FP_1*38/10,FP_1*38/10,FP_1*38/10,FP_1*38/10
bwVisitorX3D dd 22*FP_1,27*FP_1,25*FP_1,29*FP_1,25*FP_1,27*FP_1,29*FP_1
bwVisitorY3D dd FP_1*134/10,FP_1*194/10,FP_1*68/10,FP_1*68/10
dd FP_1*68/10,FP_1*68/10,FP_1*68/10
bwActor3D CityProp3D <0,0,FP_1*19/10,FP_1*19/10,0,OFFSET bwMotionArt3D>
bwSmallActor3D CityProp3D <0,0,FP_1*135/100,FP_1*135/100,0,OFFSET bwSeatedArt3D>
.data?
bwMonitors3D dd BW_LANES*128*64 dup(?)
bwSignPixels3D dd 128*64 dup(?)
.code
include src_graphics\bowling_layout.inc

View file

@ -0,0 +1,159 @@
; Generated by pack_bowling.py.
BOWL_PROP_COUNT equ 80
.data
bwProps3D LABEL DWORD
dd 167117,350618,1812070,1871053,5243,19661,236
dd 173670,344064,1813381,1865155,19661,26214,235
dd 167117,350618,1861222,1875640,19661,55706,235
dd 167117,177603,1812070,1871053,19661,36700,240
dd 340132,350618,1812070,1871053,19661,36700,240
dd 219546,298189,1749811,1785856,24904,30147,237
dd 251003,266732,1759642,1772749,0,26214,238
dd 233964,283771,1625293,1703936,3277,40632,236
dd 237240,280494,1628570,1690829,40632,47186,238
dd 416154,599654,1812070,1871053,5243,19661,236
dd 422707,593101,1813381,1865155,19661,26214,235
dd 416154,599654,1861222,1875640,19661,55706,235
dd 416154,426639,1812070,1871053,19661,36700,240
dd 589169,599654,1812070,1871053,19661,36700,240
dd 468582,547226,1749811,1785856,24904,30147,237
dd 500040,515768,1759642,1772749,0,26214,238
dd 483000,532808,1625293,1703936,3277,40632,236
dd 486277,529531,1628570,1690829,40632,47186,238
dd 665190,848691,1812070,1871053,5243,19661,236
dd 671744,842138,1813381,1865155,19661,26214,235
dd 665190,848691,1861222,1875640,19661,55706,235
dd 665190,675676,1812070,1871053,19661,36700,240
dd 838205,848691,1812070,1871053,19661,36700,240
dd 717619,796262,1749811,1785856,24904,30147,237
dd 749076,764805,1759642,1772749,0,26214,238
dd 732037,781844,1625293,1703936,3277,40632,236
dd 735314,778568,1628570,1690829,40632,47186,238
dd 914227,1097728,1812070,1871053,5243,19661,236
dd 920781,1091174,1813381,1865155,19661,26214,235
dd 914227,1097728,1861222,1875640,19661,55706,235
dd 914227,924713,1812070,1871053,19661,36700,240
dd 1087242,1097728,1812070,1871053,19661,36700,240
dd 966656,1045299,1749811,1785856,24904,30147,237
dd 998113,1013842,1759642,1772749,0,26214,238
dd 981074,1030881,1625293,1703936,3277,40632,236
dd 984351,1027604,1628570,1690829,40632,47186,238
dd 1376256,1507328,737280,835584,31457,36045,237
dd 1435238,1448346,779878,792986,0,32113,238
dd 1369702,1513882,858522,917504,5243,19661,236
dd 1376256,1507328,859832,911606,19661,26214,235
dd 1369702,1513882,907674,922092,19661,55706,235
dd 1369702,1380188,858522,917504,19661,36700,240
dd 1503396,1513882,858522,917504,19661,36700,240
dd 1376256,1507328,1130496,1228800,31457,36045,237
dd 1435238,1448346,1173094,1186202,0,32113,238
dd 1369702,1513882,1251738,1310720,5243,19661,236
dd 1376256,1507328,1253048,1304822,19661,26214,235
dd 1369702,1513882,1300890,1315308,19661,55706,235
dd 1369702,1380188,1251738,1310720,19661,36700,240
dd 1503396,1513882,1251738,1310720,19661,36700,240
dd 1703936,1835008,737280,835584,31457,36045,237
dd 1762918,1776026,779878,792986,0,32113,238
dd 1697382,1841562,858522,917504,5243,19661,236
dd 1703936,1835008,859832,911606,19661,26214,235
dd 1697382,1841562,907674,922092,19661,55706,235
dd 1697382,1707868,858522,917504,19661,36700,240
dd 1831076,1841562,858522,917504,19661,36700,240
dd 1703936,1835008,1130496,1228800,31457,36045,237
dd 1762918,1776026,1173094,1186202,0,32113,238
dd 1697382,1841562,1251738,1310720,5243,19661,236
dd 1703936,1835008,1253048,1304822,19661,26214,235
dd 1697382,1841562,1300890,1315308,19661,55706,235
dd 1697382,1707868,1251738,1310720,19661,36700,240
dd 1831076,1841562,1251738,1310720,19661,36700,240
dd 1572864,1966080,294912,360448,0,68813,236
dd 1566310,1972634,288358,367002,68813,76022,237
dd 1572864,1966080,75366,95027,0,203162,236
dd 1572864,1966080,75366,117965,58982,64225,238
dd 1572864,1966080,75366,117965,111411,116654,238
dd 1572864,1966080,75366,117965,163840,169083,238
dd 1633157,1643643,439091,449577,0,52429,238
dd 1618739,1658061,425984,465306,52429,58982,240
dd 1764229,1774715,439091,449577,0,52429,238
dd 1749811,1789133,425984,465306,52429,58982,240
dd 1895301,1905787,439091,449577,0,52429,238
dd 1880883,1920205,425984,465306,52429,58982,240
dd 1251738,1264845,65536,1625293,0,58982,236
dd 1248461,1268122,65536,1625293,58982,65536,238
dd 75366,114688,131072,176947,0,183501,233
dd 1192755,1232077,131072,176947,0,183501,233
BOWL_COLLISION_COUNT equ 26
bwCollision3D LABEL DWORD
dd 155976,361759,1800929,1882194
dd 208404,309330,1738670,1796997
dd 222822,294912,1614152,1715077
dd 405012,610796,1800929,1882194
dd 457441,558367,1738670,1796997
dd 471859,543949,1614152,1715077
dd 654049,859832,1800929,1882194
dd 706478,807404,1738670,1796997
dd 720896,792986,1614152,1715077
dd 903086,1108869,1800929,1882194
dd 955515,1056440,1738670,1796997
dd 969933,1042022,1614152,1715077
dd 1365115,1518469,726139,846725
dd 1358561,1525023,847380,928645
dd 1365115,1518469,1119355,1239941
dd 1358561,1525023,1240596,1321861
dd 1692795,1846149,726139,846725
dd 1686241,1852703,847380,928645
dd 1692795,1846149,1119355,1239941
dd 1686241,1852703,1240596,1321861
dd 1561723,1977221,283771,371589
dd 1561723,1977221,64225,106168
dd 1622016,1654784,427950,460718
dd 1753088,1785856,427950,460718
dd 1884160,1916928,427950,460718
dd 1240596,1275986,54395,1636434
BOWL_SEAT_COUNT equ 11
bwSeats3D LABEL DWORD
dd 258867,1835008,384
dd 507904,1835008,384
dd 756941,1835008,384
dd 1005978,1835008,384
dd 1441792,881459,384
dd 1441792,1274675,384
dd 1769472,881459,384
dd 1769472,1274675,384
dd 1638400,445645,384
dd 1769472,445645,384
dd 1900544,445645,384
bwMap3D LABEL BYTE
db 232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232
db 232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,232
db 232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,232
db 232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,232
db 232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,232
db 232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,232
db 232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,232
db 232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,232
db 232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,232
db 232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,232
db 232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,232
db 232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,232
db 232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,232
db 232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,232
db 232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,232
db 232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,232
db 232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,232
db 232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,232
db 232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,232
db 232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,232
db 232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,232
db 232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,232
db 232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,232
db 232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,232
db 232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,232
db 232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,232
db 232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,232
db 232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,232
db 232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,232
db 232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,232
db 232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,232
db 232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232
.code

View file

@ -0,0 +1,678 @@
RenderBowlingPlanes3D PROC uses ebx esi edi
local row:DWORD,depth:DWORD,wx:DWORD,wy:DWORD,stepX:DWORD,stepY:DWORD
local base:DWORD,tile:DWORD,ceiling:DWORD,color:DWORD,shade:DWORD
mov eax,bwArt3D.pixels
test eax,eax
jz bw_planes_done
mov base,eax
mov row,0
xor edi,edi
bw_planes_row:
mov ceiling,0
mov eax,row
sub eax,viewHorizon3D
jns bw_planes_floor
neg eax
mov ecx,eax
mov eax,viewCeiling3D
sub eax,viewEye3D
mov ceiling,1
jmp bw_planes_depth
bw_planes_floor:
inc eax
mov ecx,eax
mov eax,viewEye3D
bw_planes_depth:
imul eax,SCREEN_H
cdq
idiv ecx
mov depth,eax
shr eax,16
imul eax,3
mov ecx,250
sub ecx,eax
cmp ecx,160
jae @F
mov ecx,160
@@:
cmp ceiling,0
je @F
sub ecx,65
@@:
mov shade,ecx
mov eax,depth
imul cityRayX3D[0]
shrd eax,edx,16
add eax,playerX
mov wx,eax
mov eax,depth
imul cityRayY3D[0]
shrd eax,edx,16
add eax,playerY
mov wy,eax
mov eax,depth
imul playerPlaneX
shrd eax,edx,16
shl eax,1
cdq
mov ecx,SCREEN_W
idiv ecx
mov stepX,eax
mov eax,depth
imul playerPlaneY
shrd eax,edx,16
shl eax,1
cdq
idiv ecx
mov stepY,eax
mov esi,SCREEN_W
bw_planes_pixel:
mov tile,1
cmp ceiling,0
jne bw_planes_ceiling_tile
cmp wx,FP_1*205/100
jl bw_planes_sample
cmp wx,FP_1*1725/100
jge bw_planes_sample
cmp wy,FP_1*255/10
jg bw_planes_sample
mov tile,0
cmp wy,FP_1*225/10
jg bw_planes_sample
mov eax,wx
sub eax,FP_1*205/100
cdq
mov ecx,BW_LANE_STEP
idiv ecx
cmp edx,FP_1*18/100
jb bw_planes_gutter
cmp edx,FP_1*172/100
jbe bw_planes_sample
bw_planes_gutter:
mov tile,3
jmp bw_planes_sample
bw_planes_ceiling_tile:
mov tile,3
bw_planes_sample:
cmp ceiling,0
je bw_planes_texture
mov eax,0021262Bh
mov edx,wx
and edx,1FFFFh
cmp edx,1800
jb bw_planes_ceiling_seam
mov edx,wy
and edx,1FFFFh
cmp edx,1800
jae bw_planes_shade
bw_planes_ceiling_seam:
mov eax,0014181Ch
jmp bw_planes_shade
bw_planes_texture:
mov eax,wy
sar eax,9
and eax,255
shl eax,8
mov edx,wx
sar edx,9
and edx,255
add eax,edx
mov ebx,tile
shl ebx,18
add ebx,base
mov eax,[ebx+eax*4]
bw_planes_shade:
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
mov screenBuffer[edi],eax
mov eax,depth
mov cityDepthBuffer3D[edi],eax
mov eax,stepX
add wx,eax
mov eax,stepY
add wy,eax
add edi,4
dec esi
jnz bw_planes_pixel
inc row
cmp row,SCREEN_H
jb bw_planes_row
bw_planes_done:
ret
RenderBowlingPlanes3D ENDP
DrawBowlingBall3D PROC uses ebx esi x:DWORD,y:DWORD,mat:DWORD
invoke DrawRoofEllipsoid3D,x,y,FP_1*135/1000,FP_1*12/100,FP_1*12/100,FP_1*12/100,mat
ret
DrawBowlingBall3D 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
mov esi,lane
mov edi,pin
mov eax,bwPinX3D[edi*4]
add eax,centre
mov x,eax
mov eax,bwPinY3D[edi*4]
mov y,eax
mov z,FP_1*15/100
mov ry,FP_1/10
mov rz,FP_1*13/100
mov headY,eax
mov headZ,FP_1*34/100
mov ecx,edi
mov ebx,1
shl ebx,cl
test bwRack3D[esi*4],ebx
jz bw_pin_done
cmp bwState3D[esi*4],4
jne bw_pin_draw
test bwHit3D[esi*4],ebx
jz bw_pin_draw
cmp bwTime3D[esi*4],1200
ja bw_pin_done
mov eax,bwTime3D[esi*4]
imul edx,edi,23
sub eax,edx
jle bw_pin_draw
cmp eax,500
jbe @F
mov eax,500
@@:
mov t,eax
imul eax,FP_1*7/100
cdq
mov ecx,500
idiv ecx
sub z,eax
sub rz,eax
mov eax,t
imul eax,FP_1*14/100
cdq
idiv ecx
add ry,eax
mov eax,t
imul eax,FP_1*30/100
cdq
idiv ecx
sub headY,eax
mov eax,t
imul eax,FP_1*24/100
cdq
idiv ecx
sub headZ,eax
bw_pin_draw:
invoke DrawRoofEllipsoid3D,x,y,z,FP_1/10,ry,rz,239
invoke DrawRoofEllipsoid3D,x,headY,headZ,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
bw_pin_done:
ret
DrawBowlingPin3D ENDP
RenderBowlingLanes3D PROC uses ebx esi edi
local centre:DWORD,lo:DWORD,hi:DWORD,time:DWORD,ballX:DWORD,ballY:DWORD,mat:DWORD
local monitorLo:DWORD,monitorHi:DWORD
xor esi,esi
bw_render_lane:
imul eax,esi,BW_LANE_STEP
add eax,3*FP_1
mov centre,eax
sub eax,FP_1*83/100
mov lo,eax
add eax,FP_1*166/100
mov hi,eax
invoke DrawRoofSolid3D,lo,hi,FP_1*16/10,FP_1*29/10,0,FP_1*17/10,233
invoke DrawCityVertical3D,1,FP_1*291/100,lo,hi,FP_1*13/10,FP_1*18/10,234
mov eax,centre
sub eax,FP_1*65/100
mov ebx,centre
add ebx,FP_1*65/100
mov monitorLo,eax
mov monitorHi,ebx
invoke DrawRoofSolid3D,eax,ebx,FP_1*216/10,FP_1*219/10,FP_1*29/10,FP_1*38/10,238
lea edx,[esi+244]
invoke DrawCityVertical3D,1,FP_1*2191/100,monitorLo,monitorHi,3*FP_1,FP_1*37/10,edx
mov eax,centre
sub eax,FP_1*4/100
mov ebx,centre
add ebx,FP_1*4/100
invoke DrawRoofSolid3D,eax,ebx,FP_1*217/10,FP_1*218/10,FP_1*38/10,6*FP_1,238
xor edi,edi
bw_render_pin:
invoke DrawBowlingPin3D,esi,edi,centre
inc edi
cmp edi,10
jb bw_render_pin
cmp bwBusy3D[esi*4],0
je bw_render_lane_next
mov time,-1
cmp bwState3D[esi*4],2
jne @F
mov eax,bwTime3D[esi*4]
sub eax,1800
js bw_render_lane_next
mov time,eax
@@:
cmp bwState3D[esi*4],3
jne @F
mov eax,bwTime3D[esi*4]
add eax,600
mov time,eax
@@:
cmp time,-1
je bw_render_sweeper
mov eax,time
imul bwAim3D[esi*4]
mov ecx,3000
idiv ecx
add eax,centre
mov ballX,eax
mov eax,time
imul eax,FP_1*183/10
cdq
idiv ecx
mov edx,FP_1*225/10
sub edx,eax
mov ballY,edx
mov mat,242
cmp bwTurn3D[esi*4],0
je @F
mov mat,243
@@:
invoke DrawBowlingBall3D,ballX,ballY,mat
bw_render_sweeper:
cmp bwState3D[esi*4],4
jne bw_render_lane_next
mov eax,bwTime3D[esi*4]
cmp eax,700
jb bw_render_lane_next
sub eax,700
cmp eax,1000
jbe @F
mov eax,1000
@@:
imul eax,2*FP_1
cdq
mov ecx,1000
idiv ecx
mov ebx,5*FP_1
sub ebx,eax
mov eax,ebx
add eax,FP_1/8
invoke DrawRoofSolid3D,lo,hi,ebx,eax,FP_1/5,FP_1*35/100,243
bw_render_lane_next:
inc esi
cmp esi,BW_LANES
jb bw_render_lane
ret
RenderBowlingLanes3D ENDP
BowlingHome3D PROC lane:DWORD,owner:DWORD
mov eax,lane
shr eax,1
imul eax,FP_1*38/10
add eax,FP_1*395/100
sub eax,FP_1*65/100
test lane,1
jz @F
add eax,FP_1*9/10
@@:
cmp owner,0
je @F
add eax,FP_1*55/100
@@:
ret
BowlingHome3D ENDP
RenderBowlingVisitors3D PROC uses ebx esi edi
local centre:DWORD,homeX:DWORD,person:DWORD,phase:DWORD,t:DWORD
local action:DWORD,clock:DWORD,vx:DWORD,vy:DWORD
xor esi,esi
bw_cast_lane:
cmp bwBusy3D[esi*4],0
je bw_cast_lane_next
imul eax,esi,BW_LANE_STEP
add eax,3*FP_1
mov centre,eax
invoke BowlingHome3D,esi,bwTurn3D[esi*4]
mov homeX,eax
mov bwActor3D.posX,eax
mov bwActor3D.posY,FP_1*279/10
lea ebx,[esi*2]
add ebx,bwTurn3D[esi*4]
mov eax,bwCharacterIds3D[ebx*4]
mov person,eax
mov eax,bwState3D[esi*4]
mov phase,eax
mov eax,bwTime3D[esi*4]
mov t,eax
mov clock,eax
mov action,CAST_SEAT_TALK
mov vx,0
mov vy,0
cmp phase,0
jne bw_cast_not_rest
cmp eax,1500
jb bw_cast_draw
sub clock,1500
mov action,CAST_STAND_UP
jmp bw_cast_draw
bw_cast_not_rest:
cmp phase,6
jne bw_cast_not_seat
cmp eax,800
jae bw_cast_draw
mov action,CAST_SIT_DOWN
jmp bw_cast_draw
bw_cast_not_seat:
cmp phase,1
jne bw_cast_not_pick
cmp eax,1800
jae bw_cast_pick
mov action,CAST_WALK
mov vy,-FP_1
mov eax,centre
sub eax,homeX
mov vx,eax
imul t
mov ecx,1800
idiv ecx
add eax,homeX
mov bwActor3D.posX,eax
mov eax,t
imul eax,FP_1*24/10
cdq
idiv ecx
mov clock,eax
mov edx,FP_1*279/10
sub edx,eax
mov bwActor3D.posY,edx
jmp bw_cast_draw
bw_cast_pick:
mov eax,centre
mov bwActor3D.posX,eax
mov bwActor3D.posY,FP_1*255/10
mov action,CAST_PICK_UP
sub clock,1800
jmp bw_cast_draw
bw_cast_not_pick:
mov eax,centre
mov bwActor3D.posX,eax
mov bwActor3D.posY,FP_1*226/10
mov action,CAST_IDLE
cmp phase,2
jne bw_cast_not_throw
mov action,CAST_THROW
mov eax,t
imul eax,FP_1*29/10
cdq
mov ecx,2400
idiv ecx
mov edx,FP_1*255/10
sub edx,eax
mov bwActor3D.posY,edx
jmp bw_cast_draw
bw_cast_not_throw:
cmp phase,3
jne @F
mov action,CAST_THROW
mov clock,2300
@@:
cmp phase,4
jne bw_cast_not_impact
cmp bwHit3D[esi*4],0
je bw_cast_draw
mov action,CAST_CHEER
jmp bw_cast_draw
bw_cast_not_impact:
cmp phase,5
jne bw_cast_draw
mov action,CAST_WALK
mov vy,FP_1
mov eax,homeX
sub eax,centre
mov vx,eax
imul t
mov ecx,3600
idiv ecx
add eax,centre
mov bwActor3D.posX,eax
mov eax,t
imul eax,FP_1*53/10
cdq
idiv ecx
mov clock,eax
add eax,FP_1*226/10
mov bwActor3D.posY,eax
bw_cast_draw:
invoke ConfigureCharacter3D,OFFSET bwActor3D,person,action,clock,vx,vy
invoke DrawCityObject3D,OFFSET bwActor3D,0
; The other member of this lane keeps their own identity and their own seat.
mov eax,bwTurn3D[esi*4]
xor eax,1
mov edi,eax
invoke BowlingHome3D,esi,edi
mov bwSmallActor3D.posX,eax
mov bwSmallActor3D.posY,FP_1*279/10
lea ebx,[esi*2]
add ebx,edi
invoke ConfigureCharacter3D,OFFSET bwSmallActor3D,bwCharacterIds3D[ebx*4],CAST_SEAT_DRINK,animationClock3D,0,0
invoke DrawCityObject3D,OFFSET bwSmallActor3D,0
bw_cast_lane_next:
inc esi
cmp esi,BW_LANES
jb bw_cast_lane
; Four other visitors occupy the dining/bar area; ten visitors, no body doubles.
xor esi,esi
bw_cast_dining:
mov eax,bwVisitorX3D[esi*4]
mov bwSmallActor3D.posX,eax
mov eax,bwVisitorY3D[esi*4]
mov bwSmallActor3D.posY,eax
lea edi,[esi+6]
invoke ConfigureCharacter3D,OFFSET bwSmallActor3D,edi,CAST_SEAT_TALK,animationClock3D,0,0
cmp esi,2
jb @F
add bwSmallActor3D.groundZ,FP_1/4
@@:
invoke DrawCityObject3D,OFFSET bwSmallActor3D,0
inc esi
cmp esi,4
jb bw_cast_dining
mov bwActor3D.posX,27*FP_1
mov bwActor3D.posY,3*FP_1
mov bwActor3D.asset,OFFSET hallBartender3D
mov bwActor3D.worldW,FP_1
mov bwActor3D.worldH,FP_1
mov bwActor3D.groundZ,FP_1*9/10
mov eax,animationClock3D
shr eax,9
and eax,7
cmp engineMode,MODE_BAR
jne bw_bartender_ready
xor eax,eax
cmp barPhase3D,1
jne @F
mov eax,barTime3D
xor edx,edx
mov ecx,375
div ecx
cmp eax,7
jbe @F
mov eax,7
@@:
mov ecx,barSelection3D
shl ecx,3
add eax,ecx
bw_bartender_ready:
mov bwActor3D.frame,eax
invoke DrawCityObject3D,OFFSET bwActor3D,0
ret
RenderBowlingVisitors3D ENDP
RenderBowlingEntrance3D PROC
cmp portalRendering3D,0
jne bw_entrance_done
cmp hallLevel3D,0
jne bw_entrance_done
cmp playerY,FP_1*15/10
jbe bw_entrance_done
invoke DrawInteriorView3D,1,FP_1*15/10,3*FP_1,7*FP_1,0,FP_1*205/100,12*FP_1,29*FP_1,0,OFFSET bwMap3D,MODE_WALK,HALL_BOWLING,231,0
invoke DrawCityVertical3D,1,FP_1*151/100,3*FP_1,7*FP_1,FP_1*205/100,FP_1*246/100,252
bw_entrance_done:
ret
RenderBowlingEntrance3D ENDP
RenderBowlingDetails3D PROC uses ebx esi edi
local centerX:DWORD,x0:DWORD,x1:DWORD,centerY:DWORD
xor esi,esi
bw_detail_return:
imul eax,esi,FP_1*38/10
add eax,FP_1*395/100
mov centerX,eax
sub eax,FP_1*32/100
mov x0,eax
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
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
mov eax,centerX
add eax,FP_1*29/100
invoke DrawRoofEllipsoid3D,eax,27*FP_1,FP_1*58/100,FP_1*5/100,FP_1*5/100,FP_1*12/100,243
inc esi
cmp esi,4
jb bw_detail_return
xor esi,esi
bw_detail_dining:
mov eax,esi
and eax,1
imul eax,5*FP_1
add eax,22*FP_1
mov centerX,eax
mov eax,esi
shr eax,1
imul eax,6*FP_1
add eax,12*FP_1
mov centerY,eax
mov eax,centerX
sub eax,FP_1*45/100
invoke DrawRoofEllipsoid3D,eax,centerY,FP_1*58/100,FP_1*22/100,FP_1*22/100,FP_1*2/100,239
mov eax,centerX
add eax,FP_1*55/100
invoke DrawRoofEllipsoid3D,eax,centerY,FP_1*69/100,FP_1*6/100,FP_1*6/100,FP_1*14/100,243
invoke DrawRoofEllipsoid3D,centerX,centerY,FP_1*63/100,FP_1*7/100,FP_1*7/100,FP_1*8/100,242
inc esi
cmp esi,4
jb bw_detail_dining
ret
RenderBowlingDetails3D ENDP
RenderBowling3D PROC uses ebx esi edi
local lo:DWORD,hi:DWORD
call UpdateBowling3D
call LoadBowling3D
call PrepareCityRays3D
lea edi,zBuffer
mov eax,7FFFFFFFh
mov ecx,SCREEN_W
rep stosd
call RenderBowlingPlanes3D
xor esi,esi
bw_room_walls:
imul eax,esi,3*FP_1
add eax,FP_1
mov lo,eax
add eax,3*FP_1
mov hi,eax
invoke DrawCityVertical3D,0,FP_1,lo,hi,0,6*FP_1,232
invoke DrawCityVertical3D,0,31*FP_1,lo,hi,0,6*FP_1,232
mov ebx,234
cmp esi,6
jb @F
mov ebx,232
@@:
invoke DrawCityVertical3D,1,FP_1,lo,hi,0,6*FP_1,ebx
invoke DrawCityVertical3D,1,31*FP_1,lo,hi,0,6*FP_1,232
inc esi
cmp esi,10
jb bw_room_walls
invoke DrawCityVertical3D,0,FP_1*101/100,FP_1,31*FP_1,FP_1*45/10,FP_1*455/100,242
invoke DrawCityVertical3D,0,FP_1*3099/100,FP_1,31*FP_1,FP_1*45/10,FP_1*455/100,243
lea esi,bwProps3D
mov edi,BOWL_PROP_COUNT
bw_room_prop:
invoke DrawRoofSolid3D,[esi],[esi+4],[esi+8],[esi+12],[esi+16],[esi+20],[esi+24]
add esi,28
dec edi
jnz bw_room_prop
; Bottle shelves occupy only the wall behind the counter.
invoke DrawCityVertical3D,1,FP_1*146/100,24*FP_1,30*FP_1,FP_1,3*FP_1,ROOF_BOTTLES
call RenderBowlingDetails3D
xor esi,esi
bw_room_lights:
imul eax,esi,4*FP_1
add eax,3*FP_1
mov ebx,eax
add ebx,FP_1*22/10
invoke DrawCityHorizontal3D,eax,ebx,6*FP_1,FP_1*62/10,FP_1*55/10,242
invoke DrawCityHorizontal3D,eax,ebx,16*FP_1,FP_1*162/10,FP_1*55/10,243
inc esi
cmp esi,7
jb bw_room_lights
call RefreshBowlingMonitors3D
call RenderBowlingLanes3D
call RenderBowlingVisitors3D
cmp portalRendering3D,0
jne bw_room_done
cmp playerY,FP_1*305/10
jae bw_room_done
invoke DrawInteriorView3D,1,FP_1*305/10,15*FP_1,19*FP_1,0,FP_1*24/10,-12*FP_1,-29*FP_1,0,OFFSET casinoMap,MODE_WALK,0,100,0
bw_room_done:
ret
RenderBowling3D ENDP

View file

@ -0,0 +1,391 @@
BowlingScore3D PROC uses ebx esi edi owner:DWORD
local frame:DWORD,roll:DWORD,total:DWORD,available:DWORD
mov ebx,owner
mov eax,bwRollCount3D[ebx*4]
mov available,eax
imul ebx,96
lea esi,bwRolls3D[ebx]
mov frame,0
mov roll,0
mov total,0
bw_score_frame:
mov edi,roll
cmp edi,available
jae bw_score_done
mov eax,[esi+edi*4]
add total,eax
cmp eax,10
je bw_score_strike
inc edi
cmp edi,available
jae bw_score_done
add eax,[esi+edi*4]
mov edx,[esi+edi*4]
add total,edx
inc edi
cmp eax,10
jne bw_score_advance_two
cmp edi,available
jae bw_score_advance_two
mov eax,[esi+edi*4]
add total,eax
bw_score_advance_two:
add roll,2
jmp bw_score_advance_frame
bw_score_strike:
inc edi
cmp edi,available
jae bw_score_advance_one
mov eax,[esi+edi*4]
add total,eax
inc edi
cmp edi,available
jae bw_score_advance_one
mov eax,[esi+edi*4]
add total,eax
bw_score_advance_one:
inc roll
bw_score_advance_frame:
inc frame
cmp frame,10
jb bw_score_frame
bw_score_done:
mov ebx,owner
mov eax,total
mov bwScores3D[ebx*4],eax
ret
BowlingScore3D ENDP
BowlingRegisterRoll3D PROC uses ebx esi edi lane:DWORD,hit:DWORD
mov esi,lane
lea ebx,[esi*2]
add ebx,bwTurn3D[esi*4]
mov edi,bwRollCount3D[ebx*4]
cmp edi,21
jae bw_register_done
imul eax,ebx,96
lea edi,[eax+edi*4]
mov eax,hit
mov bwRolls3D[edi],eax
inc bwRollCount3D[ebx*4]
invoke BowlingScore3D,ebx
mov bwNewRack3D[esi*4],0
mov bwFrameDone3D[esi*4],0
cmp bwBall3D[esi*4],0
jne bw_register_after_first
mov eax,hit
mov bwFirstHit3D[esi*4],eax
cmp eax,10
jne bw_register_next_ball
cmp bwFrames3D[ebx*4],9
jb bw_register_complete
mov bwNewRack3D[esi*4],1
jmp bw_register_next_ball
bw_register_after_first:
cmp bwFrames3D[ebx*4],9
jb bw_register_complete
cmp bwBall3D[esi*4],2
jae bw_register_complete
mov eax,bwFirstHit3D[esi*4]
add eax,hit
cmp eax,10
jb bw_register_complete
cmp bwFirstHit3D[esi*4],10
jne bw_register_bonus_rack
cmp hit,10
jne bw_register_next_ball
bw_register_bonus_rack:
mov bwNewRack3D[esi*4],1
bw_register_next_ball:
inc bwBall3D[esi*4]
ret
bw_register_complete:
inc bwFrames3D[ebx*4]
mov bwNewRack3D[esi*4],1
mov bwFrameDone3D[esi*4],1
mov bwBall3D[esi*4],0
bw_register_done:
ret
BowlingRegisterRoll3D ENDP
BowlingImpact3D PROC uses ebx esi edi lane:DWORD
local pinMask:DWORD,count:DWORD
mov esi,lane
mov pinMask,0
mov count,0
mov eax,bwAim3D[esi*4]
test eax,eax
jns @F
neg eax
@@:
cmp eax,FP_1*68/100
ja bw_impact_finish
xor edi,edi
bw_impact_pin:
mov ecx,edi
mov ebx,1
shl ebx,cl
test bwRack3D[esi*4],ebx
jz bw_impact_next
mov eax,bwPinX3D[edi*4]
sub eax,bwAim3D[esi*4]
test eax,eax
jns @F
neg eax
@@:
cmp eax,FP_1*44/100
ja bw_impact_next
or pinMask,ebx
inc count
bw_impact_next:
inc edi
cmp edi,10
jb bw_impact_pin
mov eax,bwAim3D[esi*4]
add eax,FP_1*18/100
cmp eax,FP_1*36/100
ja bw_impact_finish
; A central pocket can propagate through the complete rack.
test bwRng3D,3
jz bw_impact_finish
mov eax,bwRack3D[esi*4]
mov pinMask,eax
mov count,0
@@:
test eax,eax
jz bw_impact_finish
lea edx,[eax-1]
and eax,edx
inc count
jmp @B
bw_impact_finish:
mov eax,pinMask
mov bwHit3D[esi*4],eax
invoke BowlingRegisterRoll3D,esi,count
ret
BowlingImpact3D ENDP
UpdateBowling3D PROC uses ebx esi edi
cmp hallLevel3D,HALL_BOWLING
je bw_update_active
cmp hallLevel3D,0
jne bw_update_done
cmp playerY,10*FP_1
ja bw_update_done
cmp playerX,10*FP_1
ja bw_update_done
bw_update_active:
mov eax,animationClock3D
cmp eax,bwClock3D
je bw_update_done
mov bwClock3D,eax
cmp bwSeat3D,0
je bw_update_lanes
cmp keys3D[57h],0
jne bw_update_stand
cmp keys3D[53h],0
je bw_update_lanes
bw_update_stand:
mov interactTargetId,INTERACT_BOWL_SEAT
call TryBowlingInteraction3D
bw_update_lanes:
xor esi,esi
bw_update_lane:
cmp bwBusy3D[esi*4],0
je bw_update_next
mov eax,deltaTime3D
add bwTime3D[esi*4],eax
mov eax,bwState3D[esi*4]
cmp eax,0
je bw_update_rest
cmp eax,1
je bw_update_pick
cmp eax,2
je bw_update_throw
cmp eax,3
je bw_update_roll
cmp eax,4
je bw_update_sweep
cmp eax,5
je bw_update_return
cmp bwTime3D[esi*4],4000
jb bw_update_next
cmp bwFrameDone3D[esi*4],0
je bw_update_new_approach
xor bwTurn3D[esi*4],1
lea ebx,[esi*2]
add ebx,bwTurn3D[esi*4]
cmp bwFrames3D[ebx*4],10
jb bw_update_new_approach
xor bwTurn3D[esi*4],1
lea ebx,[esi*2]
add ebx,bwTurn3D[esi*4]
cmp bwFrames3D[ebx*4],10
jb bw_update_new_approach
; Both games finished: start a new pair without affecting the casino bankroll.
lea ebx,[esi*8]
mov bwFrames3D[ebx],0
mov bwFrames3D[ebx+4],0
mov bwRollCount3D[ebx],0
mov bwRollCount3D[ebx+4],0
mov bwScores3D[ebx],0
mov bwScores3D[ebx+4],0
bw_update_new_approach:
mov bwState3D[esi*4],0
jmp bw_update_reset_timer
bw_update_rest:
cmp bwTime3D[esi*4],2300
jb bw_update_next
mov bwState3D[esi*4],1
jmp bw_update_reset_timer
bw_update_pick:
cmp bwTime3D[esi*4],2700
jb bw_update_next
mov bwState3D[esi*4],2
mov eax,bwRng3D
imul eax,1664525
add eax,1013904223
mov bwRng3D,eax
shr eax,16
xor edx,edx
mov ecx,171
div ecx
sub edx,85
imul eax,edx,FP_1
cdq
mov ecx,100
idiv ecx
mov bwAim3D[esi*4],eax
jmp bw_update_reset_timer
bw_update_throw:
cmp bwTime3D[esi*4],2400
jb bw_update_next
mov bwState3D[esi*4],3
jmp bw_update_reset_timer
bw_update_roll:
cmp bwTime3D[esi*4],2400
jb bw_update_next
invoke BowlingImpact3D,esi
mov bwState3D[esi*4],4
jmp bw_update_reset_timer
bw_update_sweep:
cmp bwTime3D[esi*4],1800
jb bw_update_next
mov eax,bwHit3D[esi*4]
not eax
and bwRack3D[esi*4],eax
mov bwHit3D[esi*4],0
mov bwState3D[esi*4],5
jmp bw_update_reset_timer
bw_update_return:
cmp bwTime3D[esi*4],3600
jb bw_update_next
cmp bwNewRack3D[esi*4],0
je @F
mov bwRack3D[esi*4],1023
@@:
mov bwState3D[esi*4],6
bw_update_reset_timer:
mov bwTime3D[esi*4],0
bw_update_next:
inc esi
cmp esi,BW_LANES
jb bw_update_lane
bw_update_done:
ret
UpdateBowling3D ENDP
BowlingMonitorText3D PROC uses ebx esi edi target:DWORD,x:DWORD,y:DWORD,value:DWORD,color:DWORD
local row:DWORD,bits:DWORD,atX:DWORD
mov esi,value
mov eax,x
mov atX,eax
bw_monitor_char:
movzx eax,BYTE PTR [esi]
test eax,eax
jz bw_monitor_text_done
cmp atX,120
ja bw_monitor_text_done
lea ebx,font8x8[eax*8]
mov row,0
bw_monitor_row:
mov ecx,row
movzx eax,BYTE PTR [ebx+ecx]
mov bits,eax
mov eax,row
add eax,y
shl eax,7
add eax,atX
lea edi,[eax*4]
add edi,target
mov ecx,8
bw_monitor_pixel:
test bits,80h
jz @F
mov eax,color
stosd
jmp bw_monitor_pixel_next
@@:
add edi,4
bw_monitor_pixel_next:
shl bits,1
dec ecx
jnz bw_monitor_pixel
inc row
cmp row,8
jb bw_monitor_row
inc esi
add atX,8
jmp bw_monitor_char
bw_monitor_text_done:
ret
BowlingMonitorText3D ENDP
RefreshBowlingMonitors3D PROC uses ebx esi edi
local target:DWORD,number[16]:BYTE
mov eax,animationClock3D
shr eax,8
cmp eax,bwMonitorClock3D
je bw_monitors_done
mov bwMonitorClock3D,eax
lea edi,bwSignPixels3D
mov eax,0FF10282Fh
mov ecx,128*64
rep stosd
invoke BowlingMonitorText3D,OFFSET bwSignPixels3D,36,4,OFFSET bwSignText3D,0FF55D9CFh
xor esi,esi
bw_monitor_lane:
mov eax,esi
shl eax,15
add eax,OFFSET bwMonitors3D
mov target,eax
mov edi,eax
mov eax,0FF101F27h
mov ecx,128*64
rep stosd
invoke BowlingMonitorText3D,target,8,5,OFFSET bwLaneWord3D,0FF55D9CFh
lea ebx,[esi+1]
invoke FormatNumber,ebx,ADDR number
invoke BowlingMonitorText3D,target,52,5,ADDR number,0FFFFFFFFh
cmp bwBusy3D[esi*4],0
jne bw_monitor_scores
invoke BowlingMonitorText3D,target,36,30,OFFSET bwFreeText3D,0FF55D9CFh
jmp bw_monitor_advance
bw_monitor_scores:
invoke BowlingMonitorText3D,target,8,21,OFFSET bwPlayerA3D,0FFFF9F89h
invoke BowlingMonitorText3D,target,70,21,OFFSET bwPlayerB3D,0FF55D9CFh
lea ebx,[esi*8]
invoke FormatNumber,bwScores3D[ebx],ADDR number
invoke BowlingMonitorText3D,target,8,33,ADDR number,0FFFFFFFFh
invoke FormatNumber,bwScores3D[ebx+4],ADDR number
invoke BowlingMonitorText3D,target,70,33,ADDR number,0FFFFFFFFh
mov eax,bwState3D[esi*4]
mov eax,bwPhaseTexts3D[eax*4]
invoke BowlingMonitorText3D,target,8,51,eax,0FFFFBF70h
bw_monitor_advance:
inc esi
cmp esi,BW_LANES
jb bw_monitor_lane
bw_monitors_done:
ret
RefreshBowlingMonitors3D ENDP

View file

@ -0,0 +1,170 @@
LoadCharacterCast3D PROC
cmp characterCast3D.pixels,0
jne cast_loaded
invoke LoadAnimPack3D,OFFSET characterCast3D,OFFSET characterCastPath3D
invoke ValidateCityPack3D,OFFSET characterCast3D,192,224,CAST_COUNT*CAST_STRIDE
cast_loaded:
ret
LoadCharacterCast3D ENDP
FreeCharacterCast3D PROC
invoke FreeAnimPack3D,OFFSET characterCast3D
ret
FreeCharacterCast3D ENDP
; Identity chooses a fixed block. Action and camera-relative facing stay in it.
; Walking clocks are travelled distance, not wall time, so feet stop with the body.
ConfigureCharacter3D PROC uses ebx esi edi object:DWORD,identity:DWORD,action:DWORD,clock:DWORD,velocityX:DWORD,velocityY:DWORD
local base:DWORD,offsetFrame:DWORD,dot:DWORD,cross:DWORD,sideMagnitude:DWORD
call LoadCharacterCast3D
mov esi,object
mov eax,identity
xor edx,edx
mov ecx,CAST_COUNT
div ecx
mov characterLastId3D,edx
imul eax,edx,CAST_STRIDE
mov base,eax
mov eax,action
cmp eax,CAST_WALK
jbe @F
xor eax,eax
@@:
mov characterLastAction3D,eax
mov edi,eax
mov eax,characterOffsets3D[eax*4]
mov offsetFrame,eax
cmp edi,CAST_WALK
je cast_walk
cmp edi,CAST_THROW
je cast_throw
cmp edi,CAST_SIT_DOWN
je cast_transition
cmp edi,CAST_STAND_UP
je cast_transition
cmp edi,CAST_PICK_UP
je cast_pickup
mov eax,clock
xor edx,edx
mov ecx,180
div ecx
and eax,3
jmp cast_frame_ready
cast_throw:
mov ecx,100
mov ebx,23
jmp cast_clamped_time
cast_pickup:
mov ecx,112
mov ebx,7
jmp cast_clamped_time
cast_transition:
mov ecx,100
mov ebx,7
cast_clamped_time:
mov eax,clock
xor edx,edx
div ecx
cmp eax,ebx
jbe @F
mov eax,ebx
@@:
cmp edi,CAST_STAND_UP
jne cast_frame_ready
neg eax
add eax,7
jmp cast_frame_ready
cast_walk:
mov eax,velocityX
imul playerDirX
shrd eax,edx,16
mov dot,eax
mov eax,velocityY
imul playerDirY
shrd eax,edx,16
add dot,eax
mov eax,velocityY
imul playerDirX
shrd eax,edx,16
mov cross,eax
mov eax,velocityX
imul playerDirY
shrd eax,edx,16
sub cross,eax
mov eax,cross
cdq
xor eax,edx
sub eax,edx
mov sideMagnitude,eax
mov eax,dot
cmp eax,sideMagnitude
jg cast_walk_back
neg eax
cmp eax,sideMagnitude
jg cast_walk_front
mov offsetFrame,40
cmp cross,0
jge cast_walk_phase
mov offsetFrame,48
jmp cast_walk_phase
cast_walk_back:
mov offsetFrame,32
jmp cast_walk_phase
cast_walk_front:
mov offsetFrame,24
cast_walk_phase:
mov eax,clock
xor edx,edx
mov ecx,FP_1*3/32
div ecx
and eax,7
cast_frame_ready:
add eax,offsetFrame
add eax,base
mov characterLastFrame3D,eax
mov [esi].CityProp3D.frame,eax
mov [esi].CityProp3D.asset,OFFSET characterCast3D
mov [esi].CityProp3D.worldW,CAST_WIDTH
mov [esi].CityProp3D.worldH,CAST_HEIGHT
mov [esi].CityProp3D.groundZ,-CAST_HEIGHT*4/224
ret
ConfigureCharacter3D ENDP
ConfigureRoofVisitor3D PROC uses ebx esi edi slot:DWORD
local phase:DWORD,clock:DWORD,vx:DWORD,vy:DWORD
mov esi,slot
mov phase,CAST_TALK
mov eax,roofGuestClock3D
mov clock,eax
shr eax,12
add eax,esi
and eax,1
jz @F
mov phase,CAST_DRINK
@@:
mov vx,0
mov vy,0
cmp roofGuestMoving3D[esi*4],0
je cast_roof_ready
mov phase,CAST_WALK
mov eax,roofGuestDistance3D[esi*4]
mov clock,eax
mov eax,roofGuestDir3D[esi*4]
cmp eax,0
jne @F
mov vx,FP_1
jmp cast_roof_ready
@@:
cmp eax,1
jne @F
mov vx,-FP_1
jmp cast_roof_ready
@@:
mov vy,FP_1
cmp eax,2
je cast_roof_ready
mov vy,-FP_1
cast_roof_ready:
invoke ConfigureCharacter3D,OFFSET roofActor3D,roofGuestIdentity3D[esi*4],phase,clock,vx,vy
ret
ConfigureRoofVisitor3D ENDP

View file

@ -0,0 +1,30 @@
CAST_COUNT equ 10
CAST_STRIDE equ 100
CAST_IDLE equ 0
CAST_TALK equ 1
CAST_DRINK equ 2
CAST_SEAT equ 3
CAST_SEAT_TALK equ 4
CAST_SEAT_DRINK equ 5
CAST_SIT_DOWN equ 6
CAST_STAND_UP equ 7
CAST_PICK_UP equ 8
CAST_THROW equ 9
CAST_CHEER equ 10
CAST_WALK equ 11
; The hall eye height is 1.05 units. A 1.3-unit canvas puts faces at eye level.
CAST_HEIGHT equ FP_1*13/10
CAST_WIDTH equ CAST_HEIGHT*192/224
LoadCharacterCast3D PROTO
FreeCharacterCast3D PROTO
ConfigureCharacter3D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
ConfigureRoofVisitor3D PROTO :DWORD
.data
characterCast3D AnimPack3D <>
characterCastPath3D db "assets\animations\characters\cast-v1.bin",0
characterOffsets3D dd 0,4,8,12,16,20,56,56,64,72,96,24
characterLastId3D dd 0
characterLastAction3D dd 0
characterLastFrame3D dd 0
roofGuestIdentity3D dd 0,4,2,1,3,5,6,7
.code

View file

@ -5,6 +5,13 @@ cityRayY3D dd SCREEN_W dup(?)
; Exterior-only materials do not consume or overwrite the hall's texture slots.
ResolveCitySurface3D PROC material:DWORD
mov eax,material
cmp eax,BW_MATERIAL
jb @F
cmp eax,BW_LAST_MATERIAL
ja @F
invoke ResolveBowlingSurface3D,material
ret
@@:
cmp eax,MATERIAL_JACKBOX_FRONT
jb @F
cmp eax,MATERIAL_JACKBOX_BACK
@ -187,7 +194,10 @@ city_vertical_setup:
mov column, 0
mov endColumn,SCREEN_W
cmp hallLevel3D,HALL_ROOF
je city_vertical_clip
cmp hallLevel3D,HALL_BOWLING
jne city_vertical_column
city_vertical_clip:
mov eax,coordinate
mov edx,loBound
cmp axis,0
@ -469,7 +479,10 @@ city_horizontal_next_corner:
cmp corner, 4
jb city_horizontal_corner
cmp hallLevel3D,HALL_ROOF
je city_horizontal_check_visible
cmp hallLevel3D,HALL_BOWLING
jne @F
city_horizontal_check_visible:
cmp visibleCorners,0
je city_horizontal_done
@@:

View file

@ -197,6 +197,13 @@ ShadeCityGround3D ENDP
; Bright warm panes and lantern glass emit light; dark windows/brick do not.
IsCityEmissive3D PROC uses ebx rgb:DWORD,material:DWORD
cmp material,242
jb @F
cmp material,BW_LAST_MATERIAL
ja @F
mov eax,1
ret
@@:
cmp cityLighting3D, 0
je city_not_emissive
mov eax, material

View file

@ -170,4 +170,6 @@ include src_graphics\fortune_room_data.inc
include src_graphics\fortune_dialog_data.inc
include src_graphics\cabaret_data.inc
include src_graphics\rooftop_data.inc
include src_graphics\bowling_data.inc
include src_graphics\character_cast_data.inc
.code

View file

@ -194,6 +194,10 @@ interior_begin_finish:
BeginWalkableStairs3D ENDP
UpdateInteriorTravel3D PROC
call BowlingTravel3D
call UpdateBowling3D
cmp hallLevel3D,HALL_BOWLING
je interior_travel_done
call UpdateRoof3D
cmp hallLevel3D,HALL_ROOF
je interior_travel_done
@ -413,6 +417,11 @@ UpdateInteriorEye3D PROC
je UpdateStairHeight3D
cmp engineMode,MODE_WALK
jne interior_eye_done
cmp hallLevel3D,HALL_BOWLING
jne @F
call BowlingEye3D
ret
@@:
cmp hallLevel3D,HALL_ROOF
jne @F
call UpdateRoofEye3D
@ -746,7 +755,6 @@ upper_draw_portal:
div ecx
mov upperLever3D.frame,eax
invoke DrawCityObject3D,OFFSET upperLever3D,0
mov upperGuest3D.asset,OFFSET npcGuestArt3D
xor esi,esi
upper_draw_guest:
mov eax,upperGuestX3D[esi*4]
@ -754,7 +762,10 @@ upper_draw_guest:
mov eax,upperGuestY3D[esi*4]
mov upperGuest3D.posY,eax
mov eax,upperGuestFrame3D[esi*4]
mov upperGuest3D.frame,eax
xor edx,edx
mov ecx,6
div ecx
invoke ConfigureCharacter3D,OFFSET upperGuest3D,eax,CAST_SEAT_TALK,animationClock3D,0,0
invoke DrawCityObject3D,OFFSET upperGuest3D,0
inc esi
cmp esi,3

View file

@ -357,6 +357,7 @@ hall_sprites_done:
RenderSprites ENDP
InitHall3D PROC uses esi edi
call InitBowling3D
call InitRoof3D
lea esi, hallLayout3D
lea edi, casinoMap
@ -419,12 +420,7 @@ LoadHallAssets3D PROC
invoke ValidateCityPack3D, OFFSET cabaretPianoArt3D,384,256,4
invoke LoadAnimPack3D, OFFSET cabaretGuitarArt3D, OFFSET cabaretGuitarPath3D
invoke ValidateCityPack3D, OFFSET cabaretGuitarArt3D,160,256,4
invoke LoadAnimPack3D, OFFSET npcGuestArt3D, OFFSET npcGuestPath3D
invoke ValidateCityPack3D, OFFSET npcGuestArt3D,128,224,36
invoke LoadAnimPack3D, OFFSET npcWalkArt3D, OFFSET npcWalkPath3D
invoke ValidateCityPack3D, OFFSET npcWalkArt3D,128,224,48
invoke LoadAnimPack3D, OFFSET npcWalkLeftArt3D, OFFSET npcWalkLeftPath3D
invoke ValidateCityPack3D, OFFSET npcWalkLeftArt3D,128,224,48
call LoadCharacterCast3D
invoke LoadAnimPack3D, OFFSET basementDoorArt3D, OFFSET basementDoorPath3D
invoke ValidateCityPack3D, OFFSET basementDoorArt3D,256,512,18
call LoadWorldDressing3D
@ -498,6 +494,8 @@ hall_load_sign:
LoadHallAssets3D ENDP
FreeHallAssets3D PROC
call FreeCharacterCast3D
call FreeBowling3D
call FreeRoofAssets3D
call FreeFortuneArt3D
invoke FreeAnimPack3D, OFFSET upperMaterialArt3D
@ -603,6 +601,11 @@ surface_missing:
ResolveWorldSurface3D ENDP
RenderHall3D PROC uses ebx esi
cmp hallLevel3D,HALL_BOWLING
jne @F
call RenderBowling3D
ret
@@:
cmp hallLevel3D,HALL_ROOF
jne @F
call RenderRoof3D
@ -795,6 +798,11 @@ hall_target_done:
HallTarget3D ENDP
CheckHallInteractions3D PROC
cmp hallLevel3D,HALL_BOWLING
jne @F
call CheckBowlingInteractions3D
ret
@@:
cmp hallLevel3D,HALL_ROOF
jne @F
call CheckRoofInteractions3D
@ -840,6 +848,9 @@ hall_check_bunker:
CheckHallInteractions3D ENDP
TriggerHallInteraction3D PROC
call TryBowlingInteraction3D
test eax,eax
jnz hall_trigger_handled
cmp interactTargetId,INTERACT_JACKBOX
jne @F
call BeginJackbox3D
@ -1027,13 +1038,21 @@ RenderBar3D PROC uses ebx
jne @F
mov playerX,15*FP_1
mov playerY,FP_1*63/10
@@:
cmp hallLevel3D,HALL_BOWLING
jne @F
mov playerX,27*FP_1
mov playerY,FP_1*66/10
@@:
mov viewHorizon3D, HALF_H
mov viewEye3D, HALL_STANDING_EYE
call UpdateCamera3D
cmp hallLevel3D,HALL_BOWLING
je bar_world_ready
call RenderCeilingAndFloor
call RenderWalls
call PrepareCityDepth3D
bar_world_ready:
call RenderHall3D
cmp barPhase3D, 0
jne bar_active_scene
@ -1060,6 +1079,10 @@ bar_draw_title:
cmp hallLevel3D,HALL_ROOF
jne @F
mov eax,OFFSET roofBarName3D
@@:
cmp hallLevel3D,HALL_BOWLING
jne @F
mov eax,OFFSET bwBarName3D
@@:
invoke DrawString8x8, 18,15,eax,00E7CA88h
cmp barPhase3D, 2
@ -1148,6 +1171,11 @@ city_world_guard_right:
RenderCityGuards3D ENDP
CanWalkHall3D PROC uses esi ebx px:DWORD,py:DWORD
cmp hallLevel3D,HALL_BOWLING
jne @F
invoke BowlingCanWalk3D,px,py
ret
@@:
cmp hallLevel3D,HALL_ROOF
jne @F
invoke RoofCanWalk3D,px,py
@ -1466,6 +1494,8 @@ include src_graphics\world_dressing.inc
include src_graphics\terminal.inc
include src_graphics\hall_expansion.inc
include src_graphics\rooftop.inc
include src_graphics\bowling.inc
include src_graphics\character_cast.inc
include src_graphics\steel_door.inc
include src_graphics\interior_portals.inc
include src_graphics\fortune_room.inc

View file

@ -118,6 +118,10 @@ portal_mask_next:
mov oldLevel,eax
mov eax,targetLevel
mov hallLevel3D,eax
cmp eax,HALL_BOWLING
jne @F
mov viewCeiling3D,6*FP_1
@@:
mov eax,floorTexture3D
mov oldFloor,eax
mov eax,targetFloor
@ -128,10 +132,13 @@ portal_mask_next:
mov stairRoute3D,eax
mov portalRendering3D,1
call UpdateCamera3D
cmp targetLevel,HALL_BOWLING
je portal_target_world_ready
call RenderCeilingAndFloor
call RenderWalls
call PrepareCityDepth3D
call RenderSprites
portal_target_world_ready:
call RenderHall3D
mov portalRendering3D,0
mov eax,oldX
@ -198,6 +205,10 @@ RenderInteriorViews3D PROC
cmp portalRendering3D,0
jne interior_views_done
mov portalPixelsWritten3D,0
cmp hallLevel3D,0
jne @F
call RenderBowlingEntrance3D
@@:
cmp hallLevel3D,3
je RenderFortuneViews3D
cmp engineMode,MODE_STAIRS

View file

@ -18,6 +18,7 @@ npc_life_init:
mov npcWalkDistance3D[esi*4],0
mov npcWalkVelocityX3D[esi*4],0
mov npcWalkVelocityY3D[esi*4],0
mov npcRise3D[esi*4],0
mov eax,npcStartDest3D[esi*4]
mov npcLife3D[ebx].destination,eax
imul edi,esi,SIZEOF NpcMindEntity
@ -31,6 +32,7 @@ npc_life_init:
jne npc_life_init_next
@@:
mov npcLife3D[ebx].state,NPC_SIT
mov npcLife3D[ebx].poseClock,0
npc_life_init_next:
inc esi
cmp esi,NPC_LIFE_COUNT
@ -352,6 +354,7 @@ npc_move_arrived:
@@:
mov npcLife3D[ebx].state,NPC_SIT
mov npcLife3D[ebx].remaining,12000
mov npcLife3D[ebx].poseClock,0
jmp npc_move_done
npc_move_blocked:
mov npcLife3D[ebx].state,NPC_STAND
@ -410,6 +413,13 @@ npc_update_person:
add npcLife3D[ebx].poseClock,eax
cmp npcLife3D[ebx].state,NPC_WALK
jne npc_update_wait
cmp npcRise3D[esi*4],0
je npc_update_move
sub npcRise3D[esi*4],eax
ja npc_update_sync
mov npcRise3D[esi*4],0
jmp npc_update_sync
npc_update_move:
invoke NpcMove3D,esi,milliseconds
jmp npc_update_sync
npc_update_wait:
@ -433,12 +443,19 @@ npc_update_choose:
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
npc_update_sync:
@ -461,6 +478,7 @@ npc_update_done:
UpdateNpcLife3D ENDP
RenderNpcLife3D PROC uses ebx esi edi
local castId:DWORD,castAction:DWORD,castClock:DWORD,slot:DWORD
cmp npcLifeReady3D,1
jne npc_draw_done
xor esi,esi
@ -501,55 +519,39 @@ npc_draw_person:
mov npcSprite3D.posX,eax
mov eax,casinoNpcs3D[edi].posY
mov npcSprite3D.posY,eax
mov npcSprite3D.worldW,FP_1*3/5
mov npcSprite3D.worldH,FP_1*6/5
mov npcSprite3D.groundZ,-FP_1*6/5*4/224
mov npcSprite3D.asset,OFFSET npcGuestArt3D
cmp npcGuestArt3D.pixels,0
je npc_draw_next
mov eax,npcLife3D[ebx].identity
mov eax,npcGuestAppearance3D[eax*4]
imul eax,6
mov npcSprite3D.frame,eax
mov castId,eax
mov castAction,CAST_TALK
mov eax,npcLife3D[ebx].poseClock
mov castClock,eax
mov eax,npcDrawOrder3D[esi*4]
mov slot,eax
cmp npcLife3D[ebx].state,NPC_SIT
jne npc_draw_walk
add npcSprite3D.frame,5
; Seated pose is already shorter within the same foot-anchored canvas.
mov npcSprite3D.worldW,FP_1*3/5
mov npcSprite3D.worldH,FP_1*6/5
mov castAction,CAST_SEAT_TALK
cmp castClock,800
jae npc_draw_sprite
mov castAction,CAST_SIT_DOWN
jmp npc_draw_sprite
npc_draw_walk:
cmp npcLife3D[ebx].state,NPC_WALK
jne npc_draw_sprite
cmp npcWalkArt3D.pixels,0
je npc_draw_sprite
mov npcSprite3D.asset,OFFSET npcWalkArt3D
mov npcSprite3D.groundZ,0
mov eax,npcLife3D[ebx].identity
mov eax,npcGuestAppearance3D[eax*4]
shl eax,3
mov npcSprite3D.frame,eax
mov ebx,npcDrawOrder3D[esi*4]
mov ebx,slot
cmp npcRise3D[ebx*4],0
je npc_draw_moving
mov castAction,CAST_STAND_UP
mov eax,800
sub eax,npcRise3D[ebx*4]
mov castClock,eax
jmp npc_draw_sprite
npc_draw_moving:
mov castAction,CAST_WALK
mov eax,npcWalkDistance3D[ebx*4]
xor edx,edx
mov ecx,FP_1*3/32
div ecx
and eax,7
add npcSprite3D.frame,eax
; Match the drawn facing to the actual camera-relative direction of travel.
mov eax,npcWalkVelocityY3D[ebx*4]
imul playerDirX
shrd eax,edx,16
mov edi,eax
mov eax,npcWalkVelocityX3D[ebx*4]
imul playerDirY
shrd eax,edx,16
sub edi,eax
jns npc_draw_sprite
cmp npcWalkLeftArt3D.pixels,0
je npc_draw_sprite
mov npcSprite3D.asset,OFFSET npcWalkLeftArt3D
mov castClock,eax
npc_draw_sprite:
mov ebx,slot
invoke ConfigureCharacter3D,OFFSET npcSprite3D,castId,castAction,castClock,npcWalkVelocityX3D[ebx*4],npcWalkVelocityY3D[ebx*4]
invoke DrawCityObject3D,OFFSET npcSprite3D,0
npc_draw_next:
inc esi

View file

@ -53,8 +53,9 @@ npcWalkLeftPath3D db "assets\animations\hall\guest-walk-left-v2.bin",0
npcWalkDistance3D dd NPC_LIFE_COUNT dup(0)
npcWalkVelocityX3D dd NPC_LIFE_COUNT dup(0)
npcWalkVelocityY3D dd NPC_LIFE_COUNT dup(0)
; The two rotations share six appearances, with a mixed cast on every visit.
npcGuestAppearance3D dd 0,2,4,1,3,5,2,0,4,1,3,5
; Keep the twelve-slot historical journal, but resolve every appearance to the shared ten.
npcRise3D 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
dd OFFSET npcName8,OFFSET npcGuestOlga3D,OFFSET npcGuestDaria3D,OFFSET npcName11

View file

@ -1117,50 +1117,7 @@ roof_guest_draw:
mov roofActor3D.posX,eax
mov eax,roofGuestY3D[esi*4]
mov roofActor3D.posY,eax
mov eax,roofGuestFrame3D[esi*4]
mov roofActor3D.frame,eax
mov roofActor3D.asset,OFFSET npcGuestArt3D
cmp roofGuestMoving3D[esi*4],0
je roof_guest_idle
cmp npcWalkArt3D.pixels,0
je roof_guest_idle
xor edx,edx
mov ecx,6
div ecx
shl eax,3
mov ebx,eax
mov eax,roofGuestDistance3D[esi*4]
xor edx,edx
mov ecx,FP_1*3/32
div ecx
and eax,7
add eax,ebx
mov roofActor3D.frame,eax
mov roofActor3D.asset,OFFSET npcWalkArt3D
mov eax,roofGuestDir3D[esi*4]
mov ecx,playerDirY
neg ecx
cmp eax,0
je roof_guest_facing
neg ecx
cmp eax,1
je roof_guest_facing
mov ecx,playerDirX
cmp eax,2
je roof_guest_facing
neg ecx
roof_guest_facing:
test ecx,ecx
jns roof_guest_draw_sprite
mov roofActor3D.asset,OFFSET npcWalkLeftArt3D
jmp roof_guest_draw_sprite
roof_guest_idle:
mov eax,roofGuestClock3D
shr eax,10
add eax,esi
and eax,1
add roofActor3D.frame,eax
roof_guest_draw_sprite:
invoke ConfigureRoofVisitor3D,esi
invoke DrawCityObject3D,OFFSET roofActor3D,0
inc esi
cmp esi,ROOF_GUEST_COUNT

View file

@ -93,11 +93,24 @@ LoadAnimPack3D PROC uses ebx esi edi pack:DWORD,path:DWORD
mov ecx, [esi].AnimPack3D.frames
test ecx, ecx
jz anim_close_failure
cmp esi,OFFSET characterCast3D
jne anim_regular_pack
; Only the fixed, shared cast may exceed the ordinary 64-frame/32-MiB cap.
cmp [esi].AnimPack3D.imageWidth,192
jne anim_close_failure
cmp [esi].AnimPack3D.height,224
jne anim_close_failure
cmp ecx,CAST_COUNT*CAST_STRIDE
jne anim_close_failure
mov eax,192*224*CAST_COUNT*CAST_STRIDE*4
jmp anim_check_payload
anim_regular_pack:
cmp ecx, 64
ja anim_close_failure
imul eax, ecx
cmp eax, 32*1024*1024
ja anim_close_failure
anim_check_payload:
cmp eax, [esi].AnimPack3D.bytes
jne anim_close_failure
invoke GetFileSize, ebx, 0

View file

@ -0,0 +1,141 @@
"""Offline bowling art, furniture and collision data. Runtime is native MASM."""
from pathlib import Path
import json
import struct
import cv2
import numpy as np
from PIL import Image
ROOT = Path(__file__).resolve().parents[2]
ART = ROOT/'assets/sprites/bowling'
OUT = ROOT/'assets/animations/bowling'
def pack(name, frames):
w,h = frames[0].size
data = b''.join(f.tobytes('raw','BGRA') for f in frames)
(OUT/name).write_bytes(struct.pack('<4s4I',b'ANM1',w,h,len(frames),len(data))+data)
def cells(name, columns, rows, seated=False):
source = Image.open(ART/name).convert('RGBA')
frames = []
for y in range(rows):
for x in range(columns):
cell = source.crop((round(x*source.width/columns),round(y*source.height/rows),
round((x+1)*source.width/columns),round((y+1)*source.height/rows)))
a = np.asarray(cell).copy()
a[:,:,3] = np.where(a[:,:,3] >= 128,255,0)
# Keep the actor, not detached released balls or neighbour-cell fragments.
if not seated:
count,labels,stats,_ = cv2.connectedComponentsWithStats((a[:,:,3]>0).astype(np.uint8))
keep = 1+int(np.argmax(stats[1:,cv2.CC_STAT_AREA]))
a[:,:,3] = np.where(labels == keep,255,0)
cell = Image.fromarray(a)
frames.append(cell)
scale = min(240/max(f.getbbox()[3]-f.getbbox()[1] for f in frames),
248/max(f.width for f in frames))
result = []
for cell in frames:
box = cell.getbbox()
resized = cell.resize((round(cell.width*scale),round(cell.height*scale)),Image.Resampling.LANCZOS)
frame = Image.new('RGBA',(256,256))
frame.paste(resized,((256-resized.width)//2,250-round(box[3]*scale)))
result.append(frame)
return result
def tween(a,b,t):
a,b = np.asarray(a),np.asarray(b)
ga,gb = cv2.cvtColor(a,cv2.COLOR_RGBA2GRAY),cv2.cvtColor(b,cv2.COLOR_RGBA2GRAY)
fa = cv2.calcOpticalFlowFarneback(ga,gb,None,.5,4,25,4,5,1.2,0)
fb = cv2.calcOpticalFlowFarneback(gb,ga,None,.5,4,25,4,5,1.2,0)
xy = np.mgrid[:256,:256][::-1].transpose(1,2,0).astype(np.float32)
wa = cv2.remap(a,xy-fa*t,None,cv2.INTER_LINEAR,borderMode=cv2.BORDER_CONSTANT)
wb = cv2.remap(b,xy-fb*(1-t),None,cv2.INTER_LINEAR,borderMode=cv2.BORDER_CONSTANT)
return Image.fromarray(np.clip(wa*(1-t)+wb*t,0,255).astype(np.uint8))
def main():
OUT.mkdir(parents=True,exist_ok=True)
source = Image.open(ART/'materials-v1.png').convert('RGBA')
materials = [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) for y in range(3) for x in range(4)]
materials += [Image.new('RGBA',(256,256),(40,210,208,255)),
Image.new('RGBA',(256,256),(229,79,65,255))]
pack('materials-v1.bin',materials)
man,woman = cells('bowler-man-v1.png',4,4),cells('bowler-woman-v1.png',4,2)
actors = []
for poses in (man,woman):
motion = []
for a,b in zip(poses[:7],poses[1:8]):
motion += [a,tween(a,b,1/3),tween(a,b,2/3)]
motion += [poses[7]]*3
extras = poses[8:16] if len(poses) == 16 else [poses[0]]*4+[poses[7]]*4
actors += motion+extras
pack('bowlers-v1.bin',actors)
pack('seated-v1.bin',cells('seated-v1.png',4,2,True))
preview = Image.new('RGBA',(8*256,2*256))
for i,f in enumerate(actors[:24:3]+actors[32:56:3]):
preview.paste(f,((i%8)*256,(i//8)*256))
preview.save(ART/'bowling-preview.png')
props,collisions,seats = [],[],[]
def box(a,b,c,d,e,f,m,solid=False):
props.append((a,b,c,d,e,f,m))
if solid:
collisions.append((a-.17,b+.17,c-.17,d+.17))
def sofa(x,y,w=2.8):
box(x-w/2,x+w/2,y-.45,y+.45,.08,.3,236,True)
box(x-w/2+.1,x+w/2-.1,y-.43,y+.36,.3,.4,235)
box(x-w/2,x+w/2,y+.3,y+.52,.3,.85,235)
for dx in (-w/2,w/2-.16):
box(x+dx,x+dx+.16,y-.45,y+.45,.3,.56,240)
seats.append((x,y-.1,384))
for pair in range(4):
x = 3.95+3.8*pair
sofa(x,28.1)
box(x-.6,x+.6,26.7,27.25,.38,.46,237,True)
box(x-.12,x+.12,26.85,27.05,0,.4,238)
box(x-.38,x+.38,24.8,26,.05,.62,236,True)
box(x-.33,x+.33,24.85,25.8,.62,.72,238)
for x in (22,27):
for y in (12,18):
box(x-1,x+1,y-.75,y+.75,.48,.55,237,True)
box(x-.1,x+.1,y-.1,y+.1,0,.49,238)
sofa(x,y+1.55,2.2)
box(24,30,4.5,5.5,0,1.05,236,True)
box(23.9,30.1,4.4,5.6,1.05,1.16,237)
box(24,30,1.15,1.45,0,3.1,236,True)
for z in (.9,1.7,2.5):
box(24,30,1.15,1.8,z,z+.08,238)
for x in (25,27,29):
box(x-.08,x+.08,6.7,6.86,0,.8,238,True)
box(x-.3,x+.3,6.5,7.1,.8,.9,240)
seats.append((x,6.8,384))
box(19.1,19.3,1,24.8,0,.9,236,True)
box(19.05,19.35,1,24.8,.9,1,238)
# Floor-standing speakers and the pinsetter housings have real side/back faces.
for x in (1.45,18.5):
box(x-.3,x+.3,2,2.7,0,2.8,233)
data = ['; Generated by pack_bowling.py.','BOWL_PROP_COUNT equ '+str(len(props)),'.data',
'bwProps3D LABEL DWORD']
for p in props:
data.append(' dd '+','.join(str(round(v*65536)) for v in p[:6])+','+str(p[6]))
data += ['BOWL_COLLISION_COUNT equ '+str(len(collisions)),'bwCollision3D LABEL DWORD']
data += [' dd '+','.join(str(round(v*65536)) for v in p) for p in collisions]
data += ['BOWL_SEAT_COUNT equ '+str(len(seats)),'bwSeats3D LABEL DWORD']
data += [' dd '+','.join([str(round(p[0]*65536)),str(round(p[1]*65536)),str(p[2])]) for p in seats]
room = [[232 if x in (0,31) or y in (0,31) else 0 for x in range(32)] for y in range(32)]
data += ['bwMap3D LABEL BYTE']
data += [' db '+','.join(map(str,row)) for row in room]
data += ['.code']
(ROOT/'src_graphics/bowling_layout.inc').write_text('\n'.join(data)+'\n',encoding='ascii')
(ART/'packed-v1.json').write_text(json.dumps({'materials':[256,256,14],
'bowlers':[256,256,64],'seated':[256,256,8],'lanes':8,'active_lanes':[0,3,6],
'room':[30,30,6],'props':len(props),'collisions':collisions,'seats':seats},indent=2)+'\n')
print('Packed bowling:',len(props),'solid prop parts, 64 whole-body motion frames, 8 seated poses')
if __name__ == '__main__':
main()

View file

@ -0,0 +1,203 @@
"""Offline normalization of the ten shared, identity-locked visitor sheets."""
from pathlib import Path
import json
import struct
import cv2
import numpy as np
from PIL import Image, ImageOps
ROOT = Path(__file__).resolve().parents[2]
ART = ROOT / 'assets/sprites/characters'
OUT = ROOT / 'assets/animations/characters'
GENERATED = Path.home()/'.codex/generated_images/019e0a0d-b269-7211-8574-91b6289060a2'
SOURCES = [
'9d00967e-d22f-43e4-95eb-703f2483d9de', '2ffe5ab3-4c2d-4eaf-beee-7e0c61d55391',
'870dca7b-988c-4cba-8555-ecdb4874fd50', 'd2bc0f10-dd34-4dfe-a9be-41593724845d',
'a1e8be10-a51c-4c00-b509-993d98fd893a', 'b2fb75d3-169f-479e-b2f6-c8ade1d332b9',
'28ab7bb4-a834-48a8-b348-9242957ff8d0', '4dd13bb9-5536-4a1c-99fd-ff24419e3977',
'7d44d86f-8047-467f-9321-cb7d2c9980f6', '850f855a-e2d6-469c-844b-afe8756ed291',
]
DESCRIPTIONS = [
'Black-haired man, navy blazer, ivory shirt, burgundy trousers, brown loafers',
'Auburn-bob woman, emerald trouser suit, ivory blouse, black flats',
'Older stocky bald man, moustache, burgundy waistcoat, white rolled sleeves',
'Silver-bob older woman, plum cardigan, teal skirt, pearl necklace',
'Dark-skinned bearded man, gray shirt, cobalt trousers, dark sneakers',
'Dark-braided woman, coral blouse, charcoal trousers, black loafers',
'Young wavy-haired man, glasses, mustard sweater, navy chinos, white sneakers',
'Black-pixie woman, gray jacket, burgundy top, navy trousers, black flats',
'Silver-haired bearded man, olive blazer, burgundy turtleneck, charcoal trousers',
'Curly-haired woman, cobalt belted jumpsuit, white sneakers',
]
W,H,STRIDE = 192,224,100
ACTIONS = {'idle':(0,4), 'talk':(4,4), 'drink':(8,4), 'seat':(12,4),
'seat_talk':(16,4), 'seat_drink':(20,4), 'walk_front':(24,8),
'walk_back':(32,8), 'walk_right':(40,8), 'walk_left':(48,8),
'sit_down':(56,8), 'pick_up':(64,8), 'throw':(72,24), 'cheer':(96,4)}
def isolate_rows(source):
a = np.asarray(source.convert('RGBA')).copy()
mask = (a[:,:,3] >= 220).astype(np.uint8)
projection = mask.sum(axis=1)
bounds = [0]
for i in range(1,4):
middle = a.shape[0]*i//4
radius = a.shape[0]//24
choices = np.arange(middle-radius,middle+radius+1)
cost = projection[choices]+abs(choices-middle)*.15
bounds.append(int(choices[np.argmin(cost)]))
bounds.append(a.shape[0])
poses, boxes = [],[]
for row,(top,bottom) in enumerate(zip(bounds,bounds[1:])):
band = mask[top:bottom]
_,labels,stats,_ = cv2.connectedComponentsWithStats(band)
parts = [(i,s) for i,s in enumerate(stats[1:],1)
if s[cv2.CC_STAT_AREA] > 650 and s[cv2.CC_STAT_HEIGHT] > (bottom-top)*.35]
parts.sort(key=lambda p:p[1][0]+p[1][2]/2)
if row == 3 and len(parts) > 8:
walking = parts[:-2]
parts = [walking[round(i*(len(walking)-1)/5)] for i in range(6)]+parts[-2:]
assert len(parts) == 8,(row,len(parts),stats.tolist())
for label,s in parts:
x,y,w,h,_ = map(int,s)
crop = a[top+y:top+y+h,x:x+w].copy()
crop[:,:,3] = np.where(labels[y:y+h,x:x+w] == label,255,0)
poses.append(Image.fromarray(crop))
boxes.append([x,top+y,w,h])
reference_height = float(np.median([poses[i].height for i in [0,*range(8,14),*range(16,22)]]))
scale = min(202/reference_height,184/max(p.width for p in poses))
result = []
for pose in poses:
size = tuple(max(1,round(v*scale)) for v in pose.size)
pose = pose.resize(size,Image.Resampling.LANCZOS)
frame = Image.new('RGBA',(W,H))
frame.alpha_composite(pose,((W-size[0])//2,220-size[1]))
result.append(frame)
return result,{'row_boundaries':bounds,'boxes':boxes,'scale':scale,'foot_anchor':[W//2,220]}
def sequence(poses,keys,count,loop=False):
frames = []
arrays = [np.asarray(poses[k]) for k in keys]
xy = np.mgrid[:H,:W][::-1].transpose(1,2,0).astype(np.float32)
pairs = list(zip(arrays,arrays[1:]+arrays[:1])) if loop else list(zip(arrays,arrays[1:]))
flows = []
for a,b in pairs:
ga,gb = cv2.cvtColor(a,cv2.COLOR_RGBA2GRAY),cv2.cvtColor(b,cv2.COLOR_RGBA2GRAY)
fa = cv2.calcOpticalFlowFarneback(ga,gb,None,.5,3,19,3,5,1.2,0)
fb = cv2.calcOpticalFlowFarneback(gb,ga,None,.5,3,19,3,5,1.2,0)
# The support plane is invariant; flow must not drag shoes off the canvas.
vertical = np.clip((H-12-xy[:,:,1])/36,0,1)
horizontal = np.clip(np.minimum(xy[:,:,0],W-1-xy[:,:,0])/12,0,1)
for flow in (fa,fb):
flow[:,:,1] *= vertical
flow[:,:,0] *= horizontal
flows.append((fa,fb))
for i in range(count):
position = i*len(pairs)/(count if loop else count-1)
at = min(int(position),len(pairs)-1)
t = min(position-at,1)
a,b = pairs[at]
if t < .001:
frames.append(Image.fromarray(a)); continue
if t > .999:
frames.append(Image.fromarray(b)); continue
fa,fb = flows[at]
wa = cv2.remap(a,xy-fa*t,None,cv2.INTER_LINEAR,borderMode=cv2.BORDER_CONSTANT)
wb = cv2.remap(b,xy-fb*(1-t),None,cv2.INTER_LINEAR,borderMode=cv2.BORDER_CONSTANT)
# Warp one intact pose: crossfading disjoint hands creates extra ghost arms.
mix = (wa if t < .5 else wb).copy()
mix[:,:,3] = np.where(mix[:,:,3] >= 128,255,0)
frames.append(Image.fromarray(mix))
return frames
def walking_poses(source, standing_height):
"""Crop the replacement contact/passing sheets, sharing one scale per sheet."""
a = np.asarray(source.convert('RGBA')).copy()
mask = (a[:,:,3] >= 220).astype(np.uint8)
projection = mask.sum(axis=1)
bounds = [0]
for row in (1,2):
middle = a.shape[0]*row//3
choices = np.arange(middle-a.shape[0]//30,middle+a.shape[0]//30+1)
bounds.append(int(choices[np.argmin(projection[choices]+abs(choices-middle)*.2)]))
bounds.append(a.shape[0])
crops, boxes = [], []
for top,bottom in zip(bounds,bounds[1:]):
_,labels,stats,_ = cv2.connectedComponentsWithStats(mask[top:bottom])
parts = [(i,s) for i,s in enumerate(stats[1:],1)
if s[cv2.CC_STAT_AREA] > 650 and s[cv2.CC_STAT_HEIGHT] > (bottom-top)*.5]
parts.sort(key=lambda p:p[1][0]+p[1][2]/2)
assert len(parts) == 4, ('walking row',top,len(parts))
for label,s in parts:
x,y,w,h,_ = map(int,s)
crop = a[top+y:top+y+h,x:x+w].copy()
crop[:,:,3] = np.where(labels[y:y+h,x:x+w] == label,255,0)
crops.append(Image.fromarray(crop))
boxes.append([x,top+y,w,h])
scale = min(standing_height/float(np.median([p.height for p in crops])),
184/max(p.width for p in crops),214/max(p.height for p in crops))
poses = []
for crop in crops:
size = tuple(max(1,round(v*scale)) for v in crop.size)
crop = crop.resize(size,Image.Resampling.LANCZOS)
frame = Image.new('RGBA',(W,H))
frame.alpha_composite(crop,((W-size[0])//2,220-size[1]))
poses.append(frame)
return poses, {'row_boundaries':bounds,'boxes':boxes,'scale':scale}
def main():
ART.mkdir(parents=True,exist_ok=True)
OUT.mkdir(parents=True,exist_ok=True)
metadata,roster = [],Image.new('RGBA',(W*10,H),(22,27,31,255))
with (OUT/'cast-v1.bin').open('wb') as target:
target.write(struct.pack('<4s4I',b'ANM1',W,H,10*STRIDE,10*STRIDE*W*H*4))
for identity,source_id in enumerate(SOURCES):
path = ART/f'person-{identity:02}-source-v1.png'
if not path.exists():
path.write_bytes((GENERATED/f'exec-{source_id}.png').read_bytes())
poses,info = isolate_rows(Image.open(path))
frames = [poses[0]]*4
for keys in ([0,1,0],[0,2,0]):
frames += sequence(poses,keys,4)
frames += [poses[3]]*4
for keys in ([3,4,3],[3,5,3]):
frames += sequence(poses,keys,4)
walk_path = ART/f'person-{identity:02}-walk-source-v2.png'
walk,walk_info = walking_poses(Image.open(walk_path),poses[0].getbbox()[3]-poses[0].getbbox()[1])
info['walking_v2'] = {'source':walk_path.name,**walk_info}
front = sequence(walk,list(range(4)),8,True)
back = sequence(walk,list(range(4,8)),8,True)
right = sequence(walk,list(range(8,12)),8,True)
walk_contact = Image.new('RGBA',(W*8,H*4),(22,27,31,255))
for index,frame in enumerate(front+back+right+[ImageOps.mirror(f) for f in right]):
walk_contact.alpha_composite(frame,((index%8)*W,(index//8)*H))
walk_contact.save(ART/f'person-{identity:02}-walk-normalized-v2.png')
frames += front+back+right+[ImageOps.mirror(f) for f in right]
frames += sequence(poses,[0,6,3],8)
frames += sequence(poses,[0,7,22],8)
frames += sequence(poses,[22,23,30,31],24)
frames += sequence(poses,[0,15,0],4)
assert len(frames) == STRIDE
for frame in frames:
target.write(frame.tobytes('raw','BGRA'))
roster.alpha_composite(poses[0],(identity*W,0))
contact = Image.new('RGBA',(W*8,H*4),(22,27,31,255))
for i,frame in enumerate(poses):
contact.alpha_composite(frame,((i%8)*W,(i//8)*H))
contact.save(ART/f'person-{identity:02}-normalized-v1.png')
motion = front+back+right+frames[56:96]
motion[0].save(ART/f'person-{identity:02}-motion-v1.gif',save_all=True,
append_images=motion[1:],duration=100,loop=0,disposal=2)
metadata.append({'id':identity,'description':DESCRIPTIONS[identity],**info})
print('Packed shared character',identity,flush=True)
roster.save(ART/'roster-v1.png')
(ART/'cast-v1.json').write_text(json.dumps({'dimensions':[W,H],'characters':metadata,
'stride':STRIDE,'actions':ACTIONS,'generation':'built-in imagegen; one identity-locked full sheet per person'},indent=2)+'\n')
if __name__ == '__main__':
main()

View file

@ -0,0 +1,240 @@
.data
bwTestEntrance db "build\hall-expansion\bowling-entrance.bmp",0
bwTestOverview db "build\hall-expansion\bowling-overview.bmp",0
bwTestRestaurant db "build\hall-expansion\bowling-restaurant.bmp",0
bwTestLanes db "build\hall-expansion\bowling-lanes.bmp",0
bwTestBar db "build\hall-expansion\bowling-bar.bmp",0
bwTestExit db "build\hall-expansion\bowling-exit.bmp",0
bwTestThrow0 db "build\hall-expansion\bowling-throw-0.bmp",0
bwTestThrow1 db "build\hall-expansion\bowling-throw-1.bmp",0
bwTestThrow2 db "build\hall-expansion\bowling-throw-2.bmp",0
bwTestThrow3 db "build\hall-expansion\bowling-throw-3.bmp",0
bwTestThrowPaths dd OFFSET bwTestThrow0,OFFSET bwTestThrow1,OFFSET bwTestThrow2,OFFSET bwTestThrow3
.code
BowlingNativeTests PROC uses ebx esi edi
local bank:DWORD
mov expansionStage,120
mov eax,bankroll
mov bank,eax
call InitBowling3D
call LoadBowling3D
cmp bwArt3D.frames,14
jne bw_test_fail
mov expansionStage,131
cmp characterCast3D.frames,CAST_COUNT*CAST_STRIDE
jne bw_test_fail
mov expansionStage,132
; Standard ten-frame scoring vectors, including tenth-frame bonuses.
lea edi,bwRolls3D
mov eax,10
mov ecx,12
rep stosd
mov bwRollCount3D,12
invoke BowlingScore3D,0
cmp eax,300
jne bw_test_fail
lea edi,bwRolls3D
mov eax,5
mov ecx,21
rep stosd
mov bwRollCount3D,21
invoke BowlingScore3D,0
cmp eax,150
jne bw_test_fail
lea edi,bwRolls3D
mov eax,1
mov ecx,20
rep stosd
mov bwRollCount3D,20
invoke BowlingScore3D,0
cmp eax,20
jne bw_test_fail
call InitBowling3D
invoke BowlingRegisterRoll3D,0,5
cmp bwBall3D,1
jne bw_test_fail
cmp bwFrames3D,0
jne bw_test_fail
invoke BowlingRegisterRoll3D,0,5
cmp bwFrames3D,1
jne bw_test_fail
cmp bwFrameDone3D,1
jne bw_test_fail
call InitBowling3D
mov bwFrames3D,9
invoke BowlingRegisterRoll3D,0,10
cmp bwBall3D,1
jne bw_test_fail
invoke BowlingRegisterRoll3D,0,10
cmp bwBall3D,2
jne bw_test_fail
invoke BowlingRegisterRoll3D,0,10
cmp bwFrames3D,10
jne bw_test_fail
mov expansionStage,121
call InitBowling3D
mov hallLevel3D,0
mov engineMode,MODE_WALK
mov worldMap3D,OFFSET casinoMap
mov playerX,5*FP_1
mov playerY,5*FP_1
mov playerAngle,384
call UpdateInteriorEye3D
mov esi,5*FP_1
bw_test_entrance_walk:
invoke CanWalk3D,5*FP_1,esi
test eax,eax
jz bw_test_fail
sub esi,FP_1/10
cmp esi,FP_1*14/10
jae bw_test_entrance_walk
call ExpansionFrame
invoke ExpansionSnapshot,OFFSET bwTestEntrance
cmp portalPixelsWritten3D,1000
jb bw_test_fail
mov expansionStage,125
mov playerY,FP_1*14/10
call BowlingTravel3D
cmp hallLevel3D,HALL_BOWLING
jne bw_test_fail
cmp playerX,17*FP_1
jne bw_test_fail
cmp playerY,FP_1*304/10
jne bw_test_fail
cmp viewCeiling3D,6*FP_1
jne bw_test_fail
mov expansionStage,126
invoke CanWalk3D,18*FP_1,25*FP_1
test eax,eax
jz bw_test_fail
invoke CanWalk3D,8*FP_1,10*FP_1
test eax,eax
jnz bw_test_fail
invoke CanWalk3D,22*FP_1,12*FP_1
test eax,eax
jnz bw_test_fail
mov expansionStage,122
mov esi,12000
bw_test_simulate:
mov deltaTime3D,50
add animationClock3D,50
call UpdateBowling3D
xor edi,edi
bw_test_bounds:
cmp bwRollCount3D[edi*4],21
ja bw_test_fail
cmp bwFrames3D[edi*4],10
ja bw_test_fail
cmp bwScores3D[edi*4],300
ja bw_test_fail
inc edi
cmp edi,16
jb bw_test_bounds
dec esi
jnz bw_test_simulate
mov expansionStage,123
mov playerX,17*FP_1
mov playerY,FP_1*301/10
mov playerAngle,350
call UpdateInteriorEye3D
call UpdateCamera3D
call RenderBowling3D
invoke ExpansionSnapshot,OFFSET bwTestOverview
mov playerX,18*FP_1
mov playerY,24*FP_1
mov playerAngle,330
call UpdateCamera3D
call RenderBowling3D
invoke ExpansionSnapshot,OFFSET bwTestLanes
mov playerX,21*FP_1
mov playerY,26*FP_1
mov playerAngle,420
call UpdateCamera3D
call RenderBowling3D
invoke ExpansionSnapshot,OFFSET bwTestRestaurant
mov playerX,23*FP_1
mov playerY,8*FP_1
mov playerAngle,420
call UpdateCamera3D
call RenderBowling3D
invoke ExpansionSnapshot,OFFSET bwTestBar
mov expansionStage,124
mov playerX,6*FP_1
mov playerY,FP_1*277/10
mov playerAngle,350
mov bwState3D,2
xor esi,esi
bw_test_throw_frames:
imul eax,esi,650
mov bwTime3D,eax
call UpdateCamera3D
call RenderBowling3D
invoke ExpansionSnapshot,bwTestThrowPaths[esi*4]
inc esi
cmp esi,4
jb bw_test_throw_frames
mov bwSeatTarget3D,0
mov interactTargetId,INTERACT_BOWL_SEAT
call TryBowlingInteraction3D
cmp bwSeat3D,1
jne bw_test_fail
cmp viewEye3D,FP_1*12/10
jne bw_test_fail
mov interactTargetId,INTERACT_BOWL_SEAT
call TryBowlingInteraction3D
cmp bwSeat3D,0
jne bw_test_fail
cmp playerX,6*FP_1
jne bw_test_fail
mov expansionStage,127
mov edi,4
bw_test_bar_visits:
mov engineMode,MODE_BAR
mov barPhase3D,0
invoke BuyBarDrink3D,2
call RenderBar3D
cmp playerX,6*FP_1
jne bw_test_fail
cmp playerY,FP_1*277/10
jne bw_test_fail
cmp playerAngle,350
jne bw_test_fail
mov deltaTime3D,3000
call HandleBar3D
call RenderBar3D
mov deltaTime3D,2400
call HandleBar3D
mov engineMode,MODE_WALK
call UpdateInteriorEye3D
cmp viewEye3D,HALL_STANDING_EYE
jne bw_test_fail
cmp viewCeiling3D,6*FP_1
jne bw_test_fail
dec edi
jnz bw_test_bar_visits
mov expansionStage,124
mov playerX,17*FP_1
mov playerY,29*FP_1
mov playerAngle,128
call UpdateCamera3D
call RenderBowling3D
invoke ExpansionSnapshot,OFFSET bwTestExit
mov playerY,FP_1*306/10
call BowlingTravel3D
cmp hallLevel3D,0
jne bw_test_fail
cmp playerX,5*FP_1
jne bw_test_fail
cmp playerY,FP_1*16/10
jne bw_test_fail
mov eax,bankroll
cmp eax,bank
jne bw_test_fail
mov engineMode,MODE_WALK
call ClearKeys3D
call CharacterCastNativeTests
ret
bw_test_fail:
invoke ExitProcess,expansionStage
ret
BowlingNativeTests ENDP

View file

@ -0,0 +1,267 @@
.data
castTestObject CityProp3D <16*FP_1,16*FP_1,CAST_WIDTH,CAST_HEIGHT,0,OFFSET characterCast3D>
castTestActions dd CAST_IDLE,CAST_WALK,CAST_WALK,CAST_SEAT_DRINK,CAST_PICK_UP,CAST_THROW
castTestClocks dd 0,FP_1*9/32,FP_1*9/32,360,700,1800
castPath0_0 db "build\hall-expansion\cast-0-0.bmp",0
castPath0_1 db "build\hall-expansion\cast-0-1.bmp",0
castPath0_2 db "build\hall-expansion\cast-0-2.bmp",0
castPath0_3 db "build\hall-expansion\cast-0-3.bmp",0
castPath0_4 db "build\hall-expansion\cast-0-4.bmp",0
castPath0_5 db "build\hall-expansion\cast-0-5.bmp",0
castPath1_0 db "build\hall-expansion\cast-1-0.bmp",0
castPath1_1 db "build\hall-expansion\cast-1-1.bmp",0
castPath1_2 db "build\hall-expansion\cast-1-2.bmp",0
castPath1_3 db "build\hall-expansion\cast-1-3.bmp",0
castPath1_4 db "build\hall-expansion\cast-1-4.bmp",0
castPath1_5 db "build\hall-expansion\cast-1-5.bmp",0
castPath2_0 db "build\hall-expansion\cast-2-0.bmp",0
castPath2_1 db "build\hall-expansion\cast-2-1.bmp",0
castPath2_2 db "build\hall-expansion\cast-2-2.bmp",0
castPath2_3 db "build\hall-expansion\cast-2-3.bmp",0
castPath2_4 db "build\hall-expansion\cast-2-4.bmp",0
castPath2_5 db "build\hall-expansion\cast-2-5.bmp",0
castPath3_0 db "build\hall-expansion\cast-3-0.bmp",0
castPath3_1 db "build\hall-expansion\cast-3-1.bmp",0
castPath3_2 db "build\hall-expansion\cast-3-2.bmp",0
castPath3_3 db "build\hall-expansion\cast-3-3.bmp",0
castPath3_4 db "build\hall-expansion\cast-3-4.bmp",0
castPath3_5 db "build\hall-expansion\cast-3-5.bmp",0
castPath4_0 db "build\hall-expansion\cast-4-0.bmp",0
castPath4_1 db "build\hall-expansion\cast-4-1.bmp",0
castPath4_2 db "build\hall-expansion\cast-4-2.bmp",0
castPath4_3 db "build\hall-expansion\cast-4-3.bmp",0
castPath4_4 db "build\hall-expansion\cast-4-4.bmp",0
castPath4_5 db "build\hall-expansion\cast-4-5.bmp",0
castPath5_0 db "build\hall-expansion\cast-5-0.bmp",0
castPath5_1 db "build\hall-expansion\cast-5-1.bmp",0
castPath5_2 db "build\hall-expansion\cast-5-2.bmp",0
castPath5_3 db "build\hall-expansion\cast-5-3.bmp",0
castPath5_4 db "build\hall-expansion\cast-5-4.bmp",0
castPath5_5 db "build\hall-expansion\cast-5-5.bmp",0
castPath6_0 db "build\hall-expansion\cast-6-0.bmp",0
castPath6_1 db "build\hall-expansion\cast-6-1.bmp",0
castPath6_2 db "build\hall-expansion\cast-6-2.bmp",0
castPath6_3 db "build\hall-expansion\cast-6-3.bmp",0
castPath6_4 db "build\hall-expansion\cast-6-4.bmp",0
castPath6_5 db "build\hall-expansion\cast-6-5.bmp",0
castPath7_0 db "build\hall-expansion\cast-7-0.bmp",0
castPath7_1 db "build\hall-expansion\cast-7-1.bmp",0
castPath7_2 db "build\hall-expansion\cast-7-2.bmp",0
castPath7_3 db "build\hall-expansion\cast-7-3.bmp",0
castPath7_4 db "build\hall-expansion\cast-7-4.bmp",0
castPath7_5 db "build\hall-expansion\cast-7-5.bmp",0
castPath8_0 db "build\hall-expansion\cast-8-0.bmp",0
castPath8_1 db "build\hall-expansion\cast-8-1.bmp",0
castPath8_2 db "build\hall-expansion\cast-8-2.bmp",0
castPath8_3 db "build\hall-expansion\cast-8-3.bmp",0
castPath8_4 db "build\hall-expansion\cast-8-4.bmp",0
castPath8_5 db "build\hall-expansion\cast-8-5.bmp",0
castPath9_0 db "build\hall-expansion\cast-9-0.bmp",0
castPath9_1 db "build\hall-expansion\cast-9-1.bmp",0
castPath9_2 db "build\hall-expansion\cast-9-2.bmp",0
castPath9_3 db "build\hall-expansion\cast-9-3.bmp",0
castPath9_4 db "build\hall-expansion\cast-9-4.bmp",0
castPath9_5 db "build\hall-expansion\cast-9-5.bmp",0
castTestPaths LABEL DWORD
dd OFFSET castPath0_0,OFFSET castPath0_1,OFFSET castPath0_2,OFFSET castPath0_3,OFFSET castPath0_4,OFFSET castPath0_5
dd OFFSET castPath1_0,OFFSET castPath1_1,OFFSET castPath1_2,OFFSET castPath1_3,OFFSET castPath1_4,OFFSET castPath1_5
dd OFFSET castPath2_0,OFFSET castPath2_1,OFFSET castPath2_2,OFFSET castPath2_3,OFFSET castPath2_4,OFFSET castPath2_5
dd OFFSET castPath3_0,OFFSET castPath3_1,OFFSET castPath3_2,OFFSET castPath3_3,OFFSET castPath3_4,OFFSET castPath3_5
dd OFFSET castPath4_0,OFFSET castPath4_1,OFFSET castPath4_2,OFFSET castPath4_3,OFFSET castPath4_4,OFFSET castPath4_5
dd OFFSET castPath5_0,OFFSET castPath5_1,OFFSET castPath5_2,OFFSET castPath5_3,OFFSET castPath5_4,OFFSET castPath5_5
dd OFFSET castPath6_0,OFFSET castPath6_1,OFFSET castPath6_2,OFFSET castPath6_3,OFFSET castPath6_4,OFFSET castPath6_5
dd OFFSET castPath7_0,OFFSET castPath7_1,OFFSET castPath7_2,OFFSET castPath7_3,OFFSET castPath7_4,OFFSET castPath7_5
dd OFFSET castPath8_0,OFFSET castPath8_1,OFFSET castPath8_2,OFFSET castPath8_3,OFFSET castPath8_4,OFFSET castPath8_5
dd OFFSET castPath9_0,OFFSET castPath9_1,OFFSET castPath9_2,OFFSET castPath9_3,OFFSET castPath9_4,OFFSET castPath9_5
castWalk0_0 db "build\hall-expansion\cast-walk-0-0.bmp",0
castWalk0_1 db "build\hall-expansion\cast-walk-0-1.bmp",0
castWalk0_2 db "build\hall-expansion\cast-walk-0-2.bmp",0
castWalk0_3 db "build\hall-expansion\cast-walk-0-3.bmp",0
castWalk0_4 db "build\hall-expansion\cast-walk-0-4.bmp",0
castWalk0_5 db "build\hall-expansion\cast-walk-0-5.bmp",0
castWalk0_6 db "build\hall-expansion\cast-walk-0-6.bmp",0
castWalk0_7 db "build\hall-expansion\cast-walk-0-7.bmp",0
castWalk1_0 db "build\hall-expansion\cast-walk-1-0.bmp",0
castWalk1_1 db "build\hall-expansion\cast-walk-1-1.bmp",0
castWalk1_2 db "build\hall-expansion\cast-walk-1-2.bmp",0
castWalk1_3 db "build\hall-expansion\cast-walk-1-3.bmp",0
castWalk1_4 db "build\hall-expansion\cast-walk-1-4.bmp",0
castWalk1_5 db "build\hall-expansion\cast-walk-1-5.bmp",0
castWalk1_6 db "build\hall-expansion\cast-walk-1-6.bmp",0
castWalk1_7 db "build\hall-expansion\cast-walk-1-7.bmp",0
castWalk2_0 db "build\hall-expansion\cast-walk-2-0.bmp",0
castWalk2_1 db "build\hall-expansion\cast-walk-2-1.bmp",0
castWalk2_2 db "build\hall-expansion\cast-walk-2-2.bmp",0
castWalk2_3 db "build\hall-expansion\cast-walk-2-3.bmp",0
castWalk2_4 db "build\hall-expansion\cast-walk-2-4.bmp",0
castWalk2_5 db "build\hall-expansion\cast-walk-2-5.bmp",0
castWalk2_6 db "build\hall-expansion\cast-walk-2-6.bmp",0
castWalk2_7 db "build\hall-expansion\cast-walk-2-7.bmp",0
castWalk3_0 db "build\hall-expansion\cast-walk-3-0.bmp",0
castWalk3_1 db "build\hall-expansion\cast-walk-3-1.bmp",0
castWalk3_2 db "build\hall-expansion\cast-walk-3-2.bmp",0
castWalk3_3 db "build\hall-expansion\cast-walk-3-3.bmp",0
castWalk3_4 db "build\hall-expansion\cast-walk-3-4.bmp",0
castWalk3_5 db "build\hall-expansion\cast-walk-3-5.bmp",0
castWalk3_6 db "build\hall-expansion\cast-walk-3-6.bmp",0
castWalk3_7 db "build\hall-expansion\cast-walk-3-7.bmp",0
castWalk4_0 db "build\hall-expansion\cast-walk-4-0.bmp",0
castWalk4_1 db "build\hall-expansion\cast-walk-4-1.bmp",0
castWalk4_2 db "build\hall-expansion\cast-walk-4-2.bmp",0
castWalk4_3 db "build\hall-expansion\cast-walk-4-3.bmp",0
castWalk4_4 db "build\hall-expansion\cast-walk-4-4.bmp",0
castWalk4_5 db "build\hall-expansion\cast-walk-4-5.bmp",0
castWalk4_6 db "build\hall-expansion\cast-walk-4-6.bmp",0
castWalk4_7 db "build\hall-expansion\cast-walk-4-7.bmp",0
castWalk5_0 db "build\hall-expansion\cast-walk-5-0.bmp",0
castWalk5_1 db "build\hall-expansion\cast-walk-5-1.bmp",0
castWalk5_2 db "build\hall-expansion\cast-walk-5-2.bmp",0
castWalk5_3 db "build\hall-expansion\cast-walk-5-3.bmp",0
castWalk5_4 db "build\hall-expansion\cast-walk-5-4.bmp",0
castWalk5_5 db "build\hall-expansion\cast-walk-5-5.bmp",0
castWalk5_6 db "build\hall-expansion\cast-walk-5-6.bmp",0
castWalk5_7 db "build\hall-expansion\cast-walk-5-7.bmp",0
castWalk6_0 db "build\hall-expansion\cast-walk-6-0.bmp",0
castWalk6_1 db "build\hall-expansion\cast-walk-6-1.bmp",0
castWalk6_2 db "build\hall-expansion\cast-walk-6-2.bmp",0
castWalk6_3 db "build\hall-expansion\cast-walk-6-3.bmp",0
castWalk6_4 db "build\hall-expansion\cast-walk-6-4.bmp",0
castWalk6_5 db "build\hall-expansion\cast-walk-6-5.bmp",0
castWalk6_6 db "build\hall-expansion\cast-walk-6-6.bmp",0
castWalk6_7 db "build\hall-expansion\cast-walk-6-7.bmp",0
castWalk7_0 db "build\hall-expansion\cast-walk-7-0.bmp",0
castWalk7_1 db "build\hall-expansion\cast-walk-7-1.bmp",0
castWalk7_2 db "build\hall-expansion\cast-walk-7-2.bmp",0
castWalk7_3 db "build\hall-expansion\cast-walk-7-3.bmp",0
castWalk7_4 db "build\hall-expansion\cast-walk-7-4.bmp",0
castWalk7_5 db "build\hall-expansion\cast-walk-7-5.bmp",0
castWalk7_6 db "build\hall-expansion\cast-walk-7-6.bmp",0
castWalk7_7 db "build\hall-expansion\cast-walk-7-7.bmp",0
castWalk8_0 db "build\hall-expansion\cast-walk-8-0.bmp",0
castWalk8_1 db "build\hall-expansion\cast-walk-8-1.bmp",0
castWalk8_2 db "build\hall-expansion\cast-walk-8-2.bmp",0
castWalk8_3 db "build\hall-expansion\cast-walk-8-3.bmp",0
castWalk8_4 db "build\hall-expansion\cast-walk-8-4.bmp",0
castWalk8_5 db "build\hall-expansion\cast-walk-8-5.bmp",0
castWalk8_6 db "build\hall-expansion\cast-walk-8-6.bmp",0
castWalk8_7 db "build\hall-expansion\cast-walk-8-7.bmp",0
castWalk9_0 db "build\hall-expansion\cast-walk-9-0.bmp",0
castWalk9_1 db "build\hall-expansion\cast-walk-9-1.bmp",0
castWalk9_2 db "build\hall-expansion\cast-walk-9-2.bmp",0
castWalk9_3 db "build\hall-expansion\cast-walk-9-3.bmp",0
castWalk9_4 db "build\hall-expansion\cast-walk-9-4.bmp",0
castWalk9_5 db "build\hall-expansion\cast-walk-9-5.bmp",0
castWalk9_6 db "build\hall-expansion\cast-walk-9-6.bmp",0
castWalk9_7 db "build\hall-expansion\cast-walk-9-7.bmp",0
castWalkPaths LABEL DWORD
dd OFFSET castWalk0_0,OFFSET castWalk0_1,OFFSET castWalk0_2,OFFSET castWalk0_3,OFFSET castWalk0_4,OFFSET castWalk0_5,OFFSET castWalk0_6,OFFSET castWalk0_7
dd OFFSET castWalk1_0,OFFSET castWalk1_1,OFFSET castWalk1_2,OFFSET castWalk1_3,OFFSET castWalk1_4,OFFSET castWalk1_5,OFFSET castWalk1_6,OFFSET castWalk1_7
dd OFFSET castWalk2_0,OFFSET castWalk2_1,OFFSET castWalk2_2,OFFSET castWalk2_3,OFFSET castWalk2_4,OFFSET castWalk2_5,OFFSET castWalk2_6,OFFSET castWalk2_7
dd OFFSET castWalk3_0,OFFSET castWalk3_1,OFFSET castWalk3_2,OFFSET castWalk3_3,OFFSET castWalk3_4,OFFSET castWalk3_5,OFFSET castWalk3_6,OFFSET castWalk3_7
dd OFFSET castWalk4_0,OFFSET castWalk4_1,OFFSET castWalk4_2,OFFSET castWalk4_3,OFFSET castWalk4_4,OFFSET castWalk4_5,OFFSET castWalk4_6,OFFSET castWalk4_7
dd OFFSET castWalk5_0,OFFSET castWalk5_1,OFFSET castWalk5_2,OFFSET castWalk5_3,OFFSET castWalk5_4,OFFSET castWalk5_5,OFFSET castWalk5_6,OFFSET castWalk5_7
dd OFFSET castWalk6_0,OFFSET castWalk6_1,OFFSET castWalk6_2,OFFSET castWalk6_3,OFFSET castWalk6_4,OFFSET castWalk6_5,OFFSET castWalk6_6,OFFSET castWalk6_7
dd OFFSET castWalk7_0,OFFSET castWalk7_1,OFFSET castWalk7_2,OFFSET castWalk7_3,OFFSET castWalk7_4,OFFSET castWalk7_5,OFFSET castWalk7_6,OFFSET castWalk7_7
dd OFFSET castWalk8_0,OFFSET castWalk8_1,OFFSET castWalk8_2,OFFSET castWalk8_3,OFFSET castWalk8_4,OFFSET castWalk8_5,OFFSET castWalk8_6,OFFSET castWalk8_7
dd OFFSET castWalk9_0,OFFSET castWalk9_1,OFFSET castWalk9_2,OFFSET castWalk9_3,OFFSET castWalk9_4,OFFSET castWalk9_5,OFFSET castWalk9_6,OFFSET castWalk9_7
.code
CharacterCastNativeTests PROC uses ebx esi edi
local phase:DWORD,time:DWORD,vx:DWORD,vy:DWORD
mov expansionStage,130
call LoadCharacterCast3D
cmp characterCast3D.frames,CAST_COUNT*CAST_STRIDE
jne cast_test_fail
cmp npcGuestArt3D.pixels,0
jne cast_test_fail
cmp npcWalkArt3D.pixels,0
jne cast_test_fail
cmp npcWalkLeftArt3D.pixels,0
jne cast_test_fail
cmp bwMotionArt3D.pixels,0
jne cast_test_fail
cmp bwSeatedArt3D.pixels,0
jne cast_test_fail
mov playerX,16*FP_1
mov playerY,20*FP_1
mov playerAngle,384
mov viewEye3D,HALL_STANDING_EYE
mov viewHorizon3D,HALF_H
call UpdateCamera3D
xor esi,esi
mov phase,0
cast_test_identity:
xor edi,edi
cast_test_action:
invoke ConfigureCharacter3D,OFFSET castTestObject,esi,edi,99999,FP_1,0
mov eax,castTestObject.frame
xor edx,edx
mov ecx,CAST_STRIDE
div ecx
cmp eax,esi
jne cast_test_fail
cmp castTestObject.asset,OFFSET characterCast3D
jne cast_test_fail
cmp castTestObject.worldH,CAST_HEIGHT
jne cast_test_fail
inc edi
cmp edi,12
jb cast_test_action
xor edi,edi
cast_test_picture:
invoke DrawBox,0,0,SCREEN_W,SCREEN_H,001C2228h
lea edi,zBuffer
mov eax,7FFFFFFFh
mov ecx,SCREEN_W
rep stosd
lea edi,cityDepthBuffer3D
mov ecx,SCREEN_SIZE
rep stosd
mov edi,phase
mov eax,castTestActions[edi*4]
mov time,eax
mov vx,0
mov vy,0
cmp edi,1
jne @F
mov vy,-FP_1
@@:
cmp edi,2
jne @F
mov vx,FP_1
@@:
invoke ConfigureCharacter3D,OFFSET castTestObject,esi,time,castTestClocks[edi*4],vx,vy
invoke DrawCityObject3D,OFFSET castTestObject,0
imul ebx,esi,6
add ebx,phase
invoke ExpansionSnapshot,castTestPaths[ebx*4]
inc phase
cmp phase,6
jb cast_test_picture
mov phase,0
cast_test_walking:
invoke DrawBox,0,0,SCREEN_W,SCREEN_H,001C2228h
lea edi,zBuffer
mov eax,7FFFFFFFh
mov ecx,SCREEN_W
rep stosd
lea edi,cityDepthBuffer3D
mov ecx,SCREEN_SIZE
rep stosd
imul eax,phase,FP_1*3/32
invoke ConfigureCharacter3D,OFFSET castTestObject,esi,CAST_WALK,eax,FP_1,0
invoke DrawCityObject3D,OFFSET castTestObject,0
imul ebx,esi,8
add ebx,phase
invoke ExpansionSnapshot,castWalkPaths[ebx*4]
inc phase
cmp phase,8
jb cast_test_walking
inc esi
mov phase,0
cmp esi,CAST_COUNT
jb cast_test_identity
ret
cast_test_fail:
invoke ExitProcess,expansionStage
ret
CharacterCastNativeTests ENDP

View file

@ -0,0 +1,63 @@
"""Shared sprite invariants and real MASM framebuffer captures."""
from pathlib import Path
import json
import struct
from PIL import Image, ImageChops
ROOT = Path(__file__).resolve().parents[2]
OUT = ROOT/'build/hall-expansion'
def check():
manifest = json.loads((ROOT/'assets/sprites/characters/cast-v1.json').read_text())
raw = (ROOT/'assets/animations/characters/cast-v1.bin').read_bytes()
magic,w,h,count,size = struct.unpack_from('<4s4I',raw)
assert (magic,w,h,count) == (b'ANM1',192,224,1000)
assert size == len(raw)-20 == w*h*count*4
assert len(manifest['characters']) == 10 and manifest['stride'] == 100
def frame(n):
offset = 20+n*w*h*4
return Image.frombytes('RGBA',(w,h),raw[offset:offset+w*h*4],'raw','BGRA')
assert len({frame(i*100).tobytes() for i in range(10)}) == 10
roster = Image.new('RGB',(320*5,240*2),(28,34,40))
for identity in range(10):
start = identity*100
standing = frame(start).getbbox()
seated = frame(start+12).getbbox()
assert seated[3]-seated[1] < (standing[3]-standing[1])*.87,(identity,'seated body resized')
for offset in range(100):
box = frame(start+offset).getbbox()
assert box and box[0] > 0 and box[2] < w and box[3] <= 222,(identity,offset,box)
for direction in (24,32,40,48):
poses = [frame(start+direction+i) for i in range(8)]
assert len({p.crop((0,120,w,h)).tobytes() for p in poses}) == 8,(identity,direction,'frozen legs')
# Distinct hashes alone missed the old repeated wide-stride source poses.
spans = []
for index in range(8):
box = frame(start+40+index).getchannel('A').crop((0,180,w,h)).getbbox()
spans.append(box[2]-box[0])
assert max(spans)-min(spans) > 55,(identity,'missing passing pose',spans)
for i in range(8):
assert frame(start+40+i).transpose(Image.Transpose.FLIP_LEFT_RIGHT).tobytes() == frame(start+48+i).tobytes()
assert len({frame(start+72+i).tobytes() for i in range(24)}) >= 20
for action in range(6):
native = Image.open(OUT/f'cast-{identity}-{action}.bmp').convert('RGB')
bg = Image.new('RGB',native.size,(28,34,40))
assert ImageChops.difference(native,bg).getbbox(),(identity,action,'blank native pose')
if action == 0:
box = ImageChops.difference(native,bg).getbbox()
assert 100 <= box[3]-box[1] <= 170 and 215 <= box[1] <= 250,(identity,'visitor scale',box)
native.save(OUT/f'cast-{identity}-{action}.png')
if action == 0:
native = native.resize((320,240),Image.Resampling.LANCZOS)
roster.paste(native,((identity%5)*320,(identity//5)*240))
walking = [Image.open(OUT/f'cast-walk-{identity}-{phase}.bmp').convert('RGB') for phase in range(8)]
assert len({im.tobytes() for im in walking}) == 8,(identity,'native frozen gait')
walking[0].save(OUT/f'cast-walk-{identity}.gif',save_all=True,
append_images=walking[1:],duration=110,loop=0)
roster.save(OUT/'cast-native-roster.png')
print('PASS: ten identities, contact/passing gait, eye-level scale, 60 poses and 80 native walking frames')
if __name__ == '__main__':
check()

View file

@ -8,6 +8,7 @@ import subprocess
from PIL import Image, ImageStat, ImageChops
from check_card_exit_build import Coff
from check_terminals import signature
from check_character_cast import check as check_character_cast
ROOT = Path(__file__).resolve().parents[2]
OUT = ROOT / "build/hall-expansion"
@ -38,6 +39,9 @@ def prepare():
code += "\n" + read("src_graphics/fortune_room.inc")
code += "\n" + read("src_graphics/fortune_dialog.inc")
code += "\n" + expand("src_graphics/rooftop.inc")
code += "\n" + expand("src_graphics/bowling.inc")
code += "\n" + read("src_graphics/character_cast.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")
code += "\n" + expand("src_graphics/darts.inc").replace(":SDWORD", ":DWORD")
@ -65,7 +69,8 @@ def prepare():
data = "\n".join(expand(path) for path in ("src_graphics/hall_expansion_data.inc",
"src_graphics/cabaret_data.inc","src_graphics/hall_layout.inc",
"src_graphics/fortune_room_data.inc","src_graphics/fortune_dialog_data.inc",
"src_graphics/observer_data.inc","src_graphics/rooftop_data.inc")) + "\n.data\n" + npc
"src_graphics/observer_data.inc","src_graphics/rooftop_data.inc",
"src_graphics/bowling_data.inc","src_graphics/character_cast_data.inc")) + "\n.data\n" + npc
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)
@ -205,9 +210,48 @@ def check_rooftop_navigation():
print(f"PASS: dense rooftop, {count} seats, {len(reached)} connected native walk points, lift/bar return and camera restore")
def check_bowling_room():
for name, count in (("materials",14),):
raw = (ROOT/f"assets/animations/bowling/{name}-v1.bin").read_bytes()
magic,w,h,frames,payload = struct.unpack_from("<4s4I",raw)
assert (magic,w,h,frames) == (b"ANM1",256,256,count)
assert len(raw) == payload+20 and payload == w*h*frames*4
layout = json.loads((ROOT/"assets/sprites/bowling/packed-v1.json").read_text())
def clear(x,y):
if not (1.2 <= x <= 30.8 and 1.2 <= y <= 30.8):
return False
if 2 <= x <= 17.3 and y < 22.3:
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]
while pending:
x,y = pending.pop()
for a,b in ((x-1,y),(x+1,y),(x,y-1),(x,y+1)):
if (a,b) not in reached and clear(a/4,b/4):
reached.add((a,b))
pending.append((a,b))
for x,y in ((18,24),(27,6),(22,10.5),(27,16.5)):
assert (int(x*4),int(y*4)) in reached,(x,y,"disconnected room area")
for i in (0,1,2,3,9):
x,y,_ = layout["seats"][i]
assert any((a/4-x)**2+(b/4-y)**2 < 1.8**2 for a,b in reached),(i,"unreachable seat")
captures = []
for path in OUT.glob("bowling-*.bmp"):
im = Image.open(path).convert("RGB")
assert max(ImageStat.Stat(im).stddev) > 20,(path,"blank room capture")
im.save(path.with_suffix('.png'))
captures.append(path)
throws = [Image.open(OUT/f"bowling-throw-{i}.bmp").tobytes() for i in range(4)]
assert len(set(throws)) == 4,"whole-body throw must visibly progress"
assert len(captures) >= 10
print(f"PASS: eight bowling lanes, {len(reached)} connected walk points, reachable bar/dining/seats, native throw and room captures")
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--parity",action="store_true")
parser.add_argument("--bowling-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")
@ -231,6 +275,14 @@ def main():
assert "DrawCityObject3D" not in room, "tarot furniture must not face the camera"
assert room.count("DrawFortuneFixedProp3D") == 24
stem = "module"
if args.bowling_only:
stem = "module-bowling"
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 BowlingNativeTests\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")
@ -282,10 +334,17 @@ def main():
f"/Fobuild/hall-expansion/{stem}.obj",f"build/hall-expansion/{stem}.asm"],cwd=ROOT,check=True)
sdk = sorted(Path("C:/Program Files (x86)/Windows Kits/10/Lib").glob("*/um/x86"))[-1]
subprocess.run([str(masm/"bin/link.exe"),"/nologo","/SUBSYSTEM:CONSOLE","/ENTRY:ExpansionTestStart@0",
f"/MAP:build/hall-expansion/{stem}.map",
f"/LIBPATH:{sdk}",f"/LIBPATH:{masm/'lib'}",f"/OUT:build/hall-expansion/{stem}.exe",
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.bowling_only:
assert result.returncode == 0,result.returncode
check_bowling_room()
check_character_cast()
print("PASS: native bowling entrance, collisions, seating, scoring, lane lifecycles and captured room/throw frames")
return
if args.prove_fate_anchor_regression:
assert result.returncode == 81,result.returncode
print("PASS: restoring fixed screen coordinates fails the native crystal-ball tracking assertions")
@ -305,6 +364,8 @@ def main():
assert result.returncode == 0,f"native case {result.returncode}"
if not args.prove_seam_regression:
check_rooftop_navigation()
check_bowling_room()
check_character_cast()
closed = Image.open(OUT/"fortune-closed.bmp").tobytes()
opened = Image.open(OUT/"fortune-open.bmp").tobytes()
assert closed != opened, "secret panel must visibly reveal the stairwell"
@ -457,10 +518,7 @@ def main():
pull = Image.open(OUT/"lever-mid-pull.bmp").crop((200,330,250,480)).tobytes()
release = Image.open(OUT/"lever-mid-release.bmp").crop((200,330,250,480)).tobytes()
assert pull != release, "left handle must visibly return while right remains enabled"
for name,expected in (("guest-cast-v1.bin",(128,224,36)),
("guest-walk-v2.bin",(128,224,48)),
("guest-walk-left-v2.bin",(128,224,48)),
("basement-door-v2.bin",(256,512,18)),
for name,expected in (("basement-door-v2.bin",(256,512,18)),
("cabaret-ensemble-v1.bin",(160,256,12)),
("cabaret-piano-v1.bin",(384,256,4)),
("cabaret-guitar-v1.bin",(160,256,4)),
@ -477,17 +535,6 @@ def main():
assert magic == b"ANM1" and (w,h,count) == expected
assert size == w*h*count*4 == len(raw)-20
frames = [raw[20+i*w*h*4:20+(i+1)*w*h*4] for i in range(count)]
if name == "guest-cast-v1.bin":
assert len(set(frames[::6])) == 6, "all six guest appearances must differ"
if name == "guest-walk-v2.bin":
for appearance in range(6):
poses = [Image.frombytes("RGBA",(w,h),frame,"raw","BGRA")
for frame in frames[appearance*8:appearance*8+8]]
assert len({pose.crop((0,120,128,224)).tobytes() for pose in poses}) == 8
assert all(pose.getbbox()[3] == 224 for pose in poses), "supporting foot must touch baseline"
mirrored = (ROOT / "assets/animations/hall/guest-walk-left-v2.bin").read_bytes()[20:]
assert all(Image.frombytes("RGBA",(w,h),frames[i],"raw","BGRA").transpose(
Image.Transpose.FLIP_LEFT_RIGHT).tobytes("raw","BGRA") == mirrored[i*w*h*4:(i+1)*w*h*4] for i in range(48))
if name == "basement-door-v2.bin":
assert len(set(frames[:17])) == 17, "separate rotating handwheel phases"
assert all(frame[:200*256*4] == frames[0][:200*256*4] for frame in frames[:17]), "leaf stays rigid while unlocking"

View file

@ -94,6 +94,8 @@ ExpansionFrame PROC
call UpdateCamera3D
cmp hallLevel3D,HALL_ROOF
je expansion_world_ready
cmp hallLevel3D,HALL_BOWLING
je expansion_world_ready
call RenderCeilingAndFloor
call RenderWalls
call PrepareCityDepth3D
@ -140,11 +142,9 @@ ExpansionTestStart PROC
call InitializeCasinoSprites3D
call InitHall3D
call LoadHallAssets3D
cmp npcGuestArt3D.pixels,0
cmp characterCast3D.pixels,0
je expansion_fail
cmp npcWalkArt3D.frames,48
jne expansion_fail
cmp npcWalkLeftArt3D.frames,48
cmp characterCast3D.frames,CAST_COUNT*CAST_STRIDE
jne expansion_fail
cmp basementDoorArt3D.frames,18
jne expansion_fail
@ -898,6 +898,7 @@ expansion_gait_hide:
jb expansion_gait_hide
mov npcLife3D.state,NPC_WALK
mov npcLife3D.identity,0
mov npcRise3D,0
mov casinoNpcs3D.posX,FP_1*165/10
mov casinoNpcs3D.posY,FP_1*175/10
mov npcWalkVelocityX3D,FP_1
@ -916,6 +917,7 @@ expansion_gait_frame:
call PrepareCityDepth3D
call RenderNpcLife3D
mov eax,npcSprite3D.frame
sub eax,40
cmp eax,esi
jne expansion_fail
invoke ExpansionSnapshot,expansionGaitPaths[esi*4]
@ -925,7 +927,9 @@ expansion_gait_frame:
mov npcWalkVelocityX3D,-FP_1
call PrepareCityDepth3D
call RenderNpcLife3D
cmp npcSprite3D.asset,OFFSET npcWalkLeftArt3D
cmp npcSprite3D.asset,OFFSET characterCast3D
jne expansion_fail
cmp npcSprite3D.frame,55
jne expansion_fail
mov expansionStage,32
mov engineMode,MODE_STAIRS
@ -1170,6 +1174,7 @@ expansion_leaf_shut:
call FortuneDialogNativeTests
call ExpansionDartsTests
call RoofNativeTests
call BowlingNativeTests
invoke ExitProcess,0
expansion_fail:
invoke ExitProcess,expansionStage
@ -1180,3 +1185,5 @@ include tools/tests/bar_camera_test.inc
include tools/tests/fortune_dialog_test.inc
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