Merge branch 'main' of https://git.silverstone.crazedns.ru/Billi_Bob/KAZIK
# Conflicts: # build/graphics/poker.exe # poker.exe
BIN
assets/animations/darts/board-v2.bin
Normal file
BIN
assets/animations/fortune/orb-numerals-v1.bin
Normal file
BIN
assets/animations/rooftop/city-night-v1.bin
Normal file
BIN
assets/animations/rooftop/city-v1.bin
Normal file
BIN
assets/animations/rooftop/details-v1.bin
Normal file
BIN
assets/animations/rooftop/lighting-v1.bin
Normal file
BIN
assets/animations/rooftop/materials-v1.bin
Normal file
BIN
assets/animations/rooftop/mesh-boat-v1.bin
Normal file
BIN
assets/animations/rooftop/mesh-car-v1.bin
Normal file
BIN
assets/animations/rooftop/mesh-city-v1.bin
Normal file
BIN
assets/animations/rooftop/night-v1.bin
Normal file
BIN
assets/animations/rooftop/panorama-v1.bin
Normal file
BIN
assets/animations/rooftop/rail-hands-v1.bin
Normal file
BIN
assets/sprites/darts/board-v2.png
Normal file
|
After Width: | Height: | Size: 247 KiB |
|
|
@ -78,3 +78,7 @@ outside the image.
|
|||
|
||||
Deterministically generated exact 20-sector sports geometry, not AI artwork.
|
||||
The sector order and scoring ring radii are shared with native scoring tests.
|
||||
|
||||
`board-v2.png` is the deterministic readability revision: larger, brighter
|
||||
numerals and cleaner wires, drawn at 2x then reduced to the same 512px canvas.
|
||||
It uses no image-generation model and does not change the board's world size.
|
||||
|
|
|
|||
BIN
assets/sprites/fortune/orb-numerals-v1.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
105
assets/sprites/fortune/orb-tracking-v1.json
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
{
|
||||
"canvas": [
|
||||
192,
|
||||
192
|
||||
],
|
||||
"font": "Cambria Regular, rasterized at 4x; no font needed at runtime",
|
||||
"frames": [
|
||||
{
|
||||
"cx": 107.0,
|
||||
"cy": 85.60757294157031,
|
||||
"rx": 72.68997989989911,
|
||||
"ry": 74.69516621989159
|
||||
},
|
||||
{
|
||||
"cx": 95.5,
|
||||
"cy": 85.76519319889574,
|
||||
"rx": 72.51196201438557,
|
||||
"ry": 74.89806065214609
|
||||
},
|
||||
{
|
||||
"cx": 95.5,
|
||||
"cy": 85.71241583054908,
|
||||
"rx": 72.69942242784161,
|
||||
"ry": 74.83168393851604
|
||||
},
|
||||
{
|
||||
"cx": 84.0,
|
||||
"cy": 85.72005975738783,
|
||||
"rx": 72.63601648302654,
|
||||
"ry": 74.68751926061978
|
||||
},
|
||||
{
|
||||
"cx": 106.5,
|
||||
"cy": 82.20860810943547,
|
||||
"rx": 72.87681495588859,
|
||||
"ry": 74.48180234847793
|
||||
},
|
||||
{
|
||||
"cx": 95.0,
|
||||
"cy": 82.2684885845598,
|
||||
"rx": 73.25393481375772,
|
||||
"ry": 74.62519501114646
|
||||
},
|
||||
{
|
||||
"cx": 95.0,
|
||||
"cy": 82.35337687892542,
|
||||
"rx": 73.02811219141157,
|
||||
"ry": 74.66203440932597
|
||||
},
|
||||
{
|
||||
"cx": 84.0,
|
||||
"cy": 82.42039157417511,
|
||||
"rx": 72.80858646484103,
|
||||
"ry": 74.62208876727115
|
||||
},
|
||||
{
|
||||
"cx": 106.5,
|
||||
"cy": 81.72901899211435,
|
||||
"rx": 74.34475334746573,
|
||||
"ry": 73.78037632992753
|
||||
},
|
||||
{
|
||||
"cx": 95.5,
|
||||
"cy": 81.63668560751485,
|
||||
"rx": 75.06807641776177,
|
||||
"ry": 73.48706340594615
|
||||
},
|
||||
{
|
||||
"cx": 95.5,
|
||||
"cy": 81.67982777738753,
|
||||
"rx": 75.12648691865994,
|
||||
"ry": 73.49674962974022
|
||||
},
|
||||
{
|
||||
"cx": 84.5,
|
||||
"cy": 81.57452731727243,
|
||||
"rx": 74.48750227516855,
|
||||
"ry": 73.69652904057105
|
||||
},
|
||||
{
|
||||
"cx": 106.0,
|
||||
"cy": 72.23134647099609,
|
||||
"rx": 75.09606781800167,
|
||||
"ry": 75.52834001598418
|
||||
},
|
||||
{
|
||||
"cx": 96.0,
|
||||
"cy": 72.31864303902333,
|
||||
"rx": 74.79155274675983,
|
||||
"ry": 75.30939853900956
|
||||
},
|
||||
{
|
||||
"cx": 94.5,
|
||||
"cy": 72.10115208001082,
|
||||
"rx": 74.92026723097523,
|
||||
"ry": 75.46485386910284
|
||||
},
|
||||
{
|
||||
"cx": 85.0,
|
||||
"cy": 72.21051327527425,
|
||||
"rx": 74.61593591942703,
|
||||
"ry": 75.5076844695196
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
assets/sprites/rooftop/chair-views-v1.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
assets/sprites/rooftop/city-facades-v1.png
Normal file
|
After Width: | Height: | Size: 3 MiB |
BIN
assets/sprites/rooftop/cutouts-v1.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
9547
assets/sprites/rooftop/district-map-v1.json
Normal file
BIN
assets/sprites/rooftop/materials-v1.png
Normal file
|
After Width: | Height: | Size: 3.5 MiB |
6665
assets/sprites/rooftop/mesh-v1.json
Normal file
BIN
assets/sprites/rooftop/modern-facades-v1.png
Normal file
|
After Width: | Height: | Size: 2.8 MiB |
BIN
assets/sprites/rooftop/night-sky-v1.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
45
assets/sprites/rooftop/packed-v1.json
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"panorama": [
|
||||
512,
|
||||
512,
|
||||
4
|
||||
],
|
||||
"materials": [
|
||||
256,
|
||||
256,
|
||||
8
|
||||
],
|
||||
"details": [
|
||||
256,
|
||||
256,
|
||||
15
|
||||
],
|
||||
"city": [
|
||||
512,
|
||||
512,
|
||||
22
|
||||
],
|
||||
"city-night": [
|
||||
512,
|
||||
512,
|
||||
22
|
||||
],
|
||||
"night": [
|
||||
512,
|
||||
512,
|
||||
4
|
||||
],
|
||||
"rail-hands": [
|
||||
640,
|
||||
320,
|
||||
32
|
||||
],
|
||||
"lighting": [
|
||||
512,
|
||||
512,
|
||||
3
|
||||
],
|
||||
"city_renderer": "world-space MASM geometry; sky only is raster",
|
||||
"source": "Built-in image generation; runtime geometry and interaction are assembly-only",
|
||||
"reference": "https://www.loftprojectetagi.ru/visitors/krysha/"
|
||||
}
|
||||
BIN
assets/sprites/rooftop/panels-v1.png
Normal file
|
After Width: | Height: | Size: 3.1 MiB |
BIN
assets/sprites/rooftop/panorama-v1.png
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
BIN
assets/sprites/rooftop/panorama-v2.png
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
27
assets/sprites/rooftop/prompts-v2.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# Rooftop City Source Art
|
||||
|
||||
Generated with the built-in image generation tool. The selected PNG originals
|
||||
are preserved in this directory; the offline packer prepares runtime atlases.
|
||||
The city itself is MASM world-space geometry, not a generated city background.
|
||||
|
||||
## Prompt Set
|
||||
|
||||
- `rail-hands-v1.png`: transparent eight-pose sprite sheet, two anatomical hands
|
||||
and complete forearms reaching forward and resting naturally on a low rooftop
|
||||
handrail, first-person view, dark sleeves, consistent camera and anatomy.
|
||||
- `city-facades-v1.png`: eight detailed, straight-on Petersburg-inspired
|
||||
classical building facades, varied pastel masonry, carved window surrounds,
|
||||
cornices and realistic windows; no perspective, sky or ground.
|
||||
- `modern-facades-v1.png`: varied straight-on contemporary glass, brick,
|
||||
townhouse and civic building facades, suitable for wrapping real geometry.
|
||||
- `roof-zinc-v1.png`: detailed weathered blue-grey standing-seam zinc roof
|
||||
surface, evenly lit, seamless material texture, no building silhouette.
|
||||
- `sunset-sky-v1.png`: panoramic sunset sky with fine pink and warm cloud
|
||||
streaks fading into cool blue; no buildings, landscape or city silhouette.
|
||||
- `night-sky-v1.png`: panoramic deep night sky with subtle clouds and stars;
|
||||
no buildings, landscape or city silhouette.
|
||||
|
||||
These record the asset specifications. The generated images are originals,
|
||||
not an exact reconstruction of any real venue or skyline. Alpha cleanup,
|
||||
optical-flow in-betweens, window masks, light maps and mesh generation are
|
||||
deterministic offline processing; no image service is used by the game.
|
||||
BIN
assets/sprites/rooftop/rail-hands-v1.png
Normal file
|
After Width: | Height: | Size: 946 KiB |
BIN
assets/sprites/rooftop/roof-zinc-v1.png
Normal file
|
After Width: | Height: | Size: 3.2 MiB |
BIN
assets/sprites/rooftop/sofa-views-v1.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
assets/sprites/rooftop/sunset-sky-v1.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
bin/poker.exe
|
|
@ -8,7 +8,7 @@ namespace AssemblerCasinoLauncher
|
|||
{
|
||||
static class Program
|
||||
{
|
||||
const string ExpectedPokerSha256 = "07D474A0223390E7AC4F4F779818D77CC2DAE5123F4BDB0F72DD975473267310";
|
||||
const string ExpectedPokerSha256 = "25A3ED73CF72140CEE3DB0637E857191F6885C2BC5C9A0D0D2835CAE5F134CEB";
|
||||
|
||||
[STAThread]
|
||||
static void Main()
|
||||
|
|
|
|||
|
|
@ -5350,10 +5350,14 @@ check_walk_mode:
|
|||
call UpdateInteriorTravel3D
|
||||
call UpdateInteriorEye3D
|
||||
call UpdateCamera3D
|
||||
; The roof owns all planes/depth; an indoor prepass would be discarded.
|
||||
cmp hallLevel3D,HALL_ROOF
|
||||
je walk_world_ready
|
||||
call RenderCeilingAndFloor
|
||||
call RenderWalls
|
||||
call PrepareCityDepth3D
|
||||
call RenderSprites
|
||||
walk_world_ready:
|
||||
call RenderHall3D
|
||||
call RenderHUD
|
||||
call RenderMinimap
|
||||
|
|
|
|||
BIN
dist/Assembler Casino.exe
vendored
|
|
@ -31,8 +31,12 @@ required by the game.
|
|||
|
||||
The throwing line is 2700 board millimetres from the target. The initial lens
|
||||
is adjusted so the board is only about 7 percent smaller on screen than the
|
||||
previous 2370mm/900px view. Its original 512px texture and scoring geometry are
|
||||
unchanged. Max watches the same moving aim point for a short release window;
|
||||
previous 2370mm/900px view. Texture resolution and scoring geometry are preserved.
|
||||
The v2 board keeps the same physical bounds and 512px pack dimensions,
|
||||
but uses larger high-contrast numerals, supersampled wires and lower grain.
|
||||
The reticle is a 7x7 footprint containing only nine one-pixel marks, with no thick
|
||||
outline. Individual marks adapt to bright/dark sectors for readability.
|
||||
Max watches the same moving aim point for a short release window;
|
||||
he does not reroll a hit or bypass the sway.
|
||||
|
||||
The independent
|
||||
|
|
|
|||
|
|
@ -25,22 +25,41 @@ steps after stalls. No neural worker or algorithmic reading generator is called.
|
|||
|
||||
## Crystal Ball
|
||||
|
||||
The second selection raises a crystal ball and animates gloved hands with swirling
|
||||
blue mist. It ends with an explicit unavailable-result line. There is no real
|
||||
student grading, inference of merit, identity form or automated academic decision.
|
||||
Again replays the visual sequence. This limitation is deliberate, not a hidden
|
||||
random grade presented as a judgement.
|
||||
The second selection is a fictional, theatrical student-themed lottery, separate
|
||||
from tarot and not an assessment of actual academic merit. The form asks for a
|
||||
first name, surname, birth date (DD.MM.YYYY), group and inclusive score bounds
|
||||
from 0 to 100. Tab cycles fields, clicking focuses one, Backspace edits, and
|
||||
Enter or the summon button submits. Empty fields, invalid calendar dates
|
||||
(including leap years), reversed bounds and scores above 100 are rejected.
|
||||
|
||||
Identity fields only dress the scene: they never seed or weight the draw. One
|
||||
native random draw is committed on valid submission. The ball rises for 1.4 s,
|
||||
hands circle through 32 poses with 16 mist frames for 5 s, then a glowing number
|
||||
appears inside the ball. Short typewriter speech accompanies the stages. The
|
||||
result fades into view and stays readable while the identity and chosen bounds
|
||||
remain at the bottom. Renders, duplicate submits and clicks during the ritual
|
||||
cannot reroll. After the reveal, Edit returns to the filled form for another
|
||||
session. No network, inference worker, profile or bankroll changes are involved.
|
||||
Fields are kept only in session memory and cleared on exit; nothing is saved.
|
||||
|
||||
The number uses baked, antialiased Cambria numeral sprites with translucent blue
|
||||
glow, not enlarged 3x5 block digits. Each of the 16 mist frames has its own measured
|
||||
glass-centre anchor, excluding the pedestal. One assembly resolver chooses both
|
||||
the ball frame and the matching number position, including the rising movement.
|
||||
Numerals stay at a constant size and the fog remains visible through their glow.
|
||||
The random draw and its probabilities are unchanged; identity never biases it.
|
||||
|
||||
## Assets
|
||||
|
||||
Generated source art and production prompts: `assets/sprites/fortune/`.
|
||||
Offline packer: `tools/generators/pack_fortune_session.py`.
|
||||
Offline packers: `tools/generators/pack_fortune_session.py` and then
|
||||
`tools/generators/pack_fortune_orb.py` (tracking metadata and numeral sprites).
|
||||
Runtime packs: `assets/animations/fortune/` (ANM1 format).
|
||||
|
||||
Forty authored poses are arranged on one shared anchored 8x5 sheet. Shared-canvas
|
||||
in-betweens yield 64 shuffle, 32 deal, 32 gesture and 32 orb-hand frames. Sixteen
|
||||
mist frames, 44 oriented card faces, a back and a 640x480 room complete the scene.
|
||||
Art loads only when seated and all seven packs are freed on exit/shutdown. The
|
||||
Art loads only when seated and all eight packs are freed on exit/shutdown. The
|
||||
seated view is independent of the walking room's existing asset packs and map.
|
||||
|
||||
## Verification
|
||||
|
|
@ -48,11 +67,17 @@ seated view is independent of the walking room's existing asset packs and map.
|
|||
Run `C:/Python314/python.exe tools/tests/check_hall_expansion.py` after packing.
|
||||
The fresh native x86 harness exercises real WM_CHAR input, length/backspace,
|
||||
empty/double submission, all stage transitions, card uniqueness, speech growth,
|
||||
question persistence, crystal-ball stages, exit during animation, asset cleanup
|
||||
question persistence, crystal-ball form validation, inclusive draws, fixed bounds,
|
||||
input locking, duplicate submission, crystal-ball stages, exit during animation, asset cleanup
|
||||
and unchanged player position/bank. It writes actual framebuffer BMP/PNG captures
|
||||
and a 64-frame shuffle GIF under `build/hall-expansion/`. Python additionally checks
|
||||
native-frame differences, question pixels, pack integrity and reversed artwork.
|
||||
`--parity` checks these tested procedures against the shipping game object.
|
||||
The orb regression captures each of the 16 native frames twice, with and without
|
||||
the number. Pixel differences must stay centred on that frame's glass sphere;
|
||||
this also checks that opacity is restored and the numeral pack is freed on exit.
|
||||
`--prove-fate-anchor-regression` deliberately restores the old fixed coordinates
|
||||
in a test-only module and must fail the native tracking assertions.
|
||||
|
||||
The old multi-gigabyte neural ZIP is not rebuilt by this feature. Distributions
|
||||
must include the new `assets/animations/fortune/` directory with the updated game.
|
||||
|
|
|
|||
138
docs/ROOFTOP.md
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
# Rooftop Lounge "Vysota"
|
||||
|
||||
The terrace is an assembly-only walkable hall level (`HALL_ROOF = 4`), reached
|
||||
by a real elevator cabin near the north wall to the right of the cabaret.
|
||||
Existing games, basement stairs, scratch tickets and fortune room stay in place.
|
||||
|
||||
## Travel
|
||||
|
||||
Use E at the exterior call panel, then walk into the open cabin using the normal
|
||||
movement keys. The inside panel offers the roof or the main hall depending on
|
||||
the current landing. E starts a trip only when the player is fully inside.
|
||||
Doors physically slide apart into projecting pockets. Movement is blocked during
|
||||
closing/travel, while looking around remains possible. After one second of
|
||||
closing and 3.2 seconds of travel the destination changes, doors open, and the
|
||||
player walks out. Position, angle and standing height do not drift during travel.
|
||||
|
||||
An idle open door closes after 2.8 seconds away from the elevator. It reverses
|
||||
if the player re-enters its approach, rather than shutting through them. Arrival
|
||||
keeps the cabin coordinates and opens the other landing; there is no forced
|
||||
camera path or automatic walking. The illuminated physical indicator changes
|
||||
during travel and shows `1` or `R` at the landings.
|
||||
|
||||
## Terrace
|
||||
|
||||
- A fully volumetric Petersburg-inspired city, seen from a sixty-metre tower.
|
||||
Only the sky is a painted texture; buildings, streets, roofs, parks, canal,
|
||||
bridges, monuments, cars and boats are real world-space geometry.
|
||||
- Finite HD decking, low railings and an open sky, rather than an indoor ceiling
|
||||
or a floor extending to infinity beyond the roof edge.
|
||||
- Three three-seat sofas, four armchairs, three bar stools and three stools at
|
||||
a rail-side standing counter: 13 selectable furniture positions, 19 seat places.
|
||||
- Seven coffee tables, eight potted trees, flowering troughs, eight varied guests,
|
||||
two supported strings of lanterns, and a slatted pergola over a working bar.
|
||||
- Sofas and chairs use closed native volumes with separate padded cushions.
|
||||
Planter boxes contain soil and multiple foliage clusters at different depths.
|
||||
Whole-object photographs are not pasted onto every furniture face.
|
||||
- E at a railing eases into a leaning pose, with thirty-two hand/forearm frames
|
||||
anchored to the actual rail height. E or movement releases the rail.
|
||||
- While leaning, mouse, arrows and A/D can turn up to 73 degrees either way. The
|
||||
rail contact is projected in world space so the hands do not follow head yaw.
|
||||
- Guests walk on the terrace with collision checks, pauses and walking frames.
|
||||
- In the open first-floor elevator, 1 selects sunset and 2 selects night.
|
||||
- E sits/stands; walking forward/backward also stands and restores the approach
|
||||
position. Stool views use bar height and rail-side stools face the city.
|
||||
- The bar reuses the existing mixing/drinking system and prices (100/200/free
|
||||
water). Its camera is temporarily positioned at the new counter and completely
|
||||
restored after rendering. Drinks are the only new-location actions that spend
|
||||
or save bankroll, through the existing purchase routine.
|
||||
|
||||
## Assets And Build
|
||||
|
||||
Generated sources are under `assets/sprites/rooftop/`; runtime ANM1 packs are under
|
||||
`assets/animations/rooftop/`. Run `tools/generators/pack_rooftop.py` offline to pack
|
||||
the sky, materials, semantic night-window textures, ground lighting, hands,
|
||||
district layout and collision map. Run `pack_rooftop_gl.py` afterwards to build
|
||||
the static city and moving vehicle triangle meshes. Python, Pillow, NumPy and
|
||||
OpenCV are offline developer tools, not game dependencies.
|
||||
|
||||
The native MASM HD city renderer uses the Windows system OpenGL library, a
|
||||
private hidden rendering context and an offscreen framebuffer up to 1280x960. It uses
|
||||
textured mesh display lists, mipmaps, normals, directional dusk lighting,
|
||||
distance fog and a separate emissive-window pass at night. The existing terrace,
|
||||
hands and HUD are composited by direct native pixel copying into one DIB. Other games retain
|
||||
their existing renderer. If the required GL capabilities are unavailable, the
|
||||
assembly software city renderer remains the fallback. No C/C++ renderer, model
|
||||
engine, browser runtime, cloud service or runtime downloader is introduced.
|
||||
|
||||
The eastern avenue is now a broad river with raised embankments, promenades,
|
||||
trees, piers, three full-span bridges and further riverfront quarters. Water
|
||||
texture flow and six moving boats keep it from being a static blue plane.
|
||||
Street rectangles are clipped into disjoint junctions rather than coplanar,
|
||||
overlapping strips. The terrace deck uses mipmapped HD geometry rather than
|
||||
the unfiltered software floor when HD rendering is available. City, foreground
|
||||
and HUD are composed into one offscreen DIB and copied to the window once;
|
||||
there is no intermediate bare-city frame. glReadPixels supplies the required
|
||||
readback synchronisation, without a redundant preceding glFinish.
|
||||
|
||||
## Rooftop Performance
|
||||
|
||||
- F8 cycles automatic, eco (640x480) and detailed (1280x960) city rendering.
|
||||
Automatic starts at 960x720 and drops to eco after sustained frames over 34 ms.
|
||||
Switching back to automatic retries 960x720. The scene, view distance, collisions
|
||||
and gameplay do not change with the render profile; other rooms are unaffected.
|
||||
- The offline mesh packer discards only triangles that face away from every
|
||||
possible rooftop eye position. Runtime back-face culling handles head turns.
|
||||
The static mesh, including driver-safe ground and canal frontage, stays under 48,000
|
||||
triangles. Never-visible faces are pruned before shipping.
|
||||
- Terrain and light-map triangles crossing the near district are at most 64 m
|
||||
wide, with finer cells near the terrace. This avoids relying on per-fragment
|
||||
fog for multi-kilometre polygons on integrated OpenGL drivers. Water animation
|
||||
uses its own display list from the same tessellated mesh, not two giant quads.
|
||||
GL_NICEST requests accurate fog, but the geometry remains safe if ignored.
|
||||
- CPU foreground surfaces use projected screen bounds and camera-facing box
|
||||
sides. The final frame avoids an extra GDI scaling/alpha-blending pass.
|
||||
- Mipmap generation is optional on older OpenGL drivers. If hardware rendering
|
||||
cannot initialise, the software ground renderer still draws roads and water,
|
||||
including solid-colour fallback surfaces when a texture pack is absent.
|
||||
- `runtime/roof-renderer.txt` records the local GL vendor, renderer, version,
|
||||
initialisation stage, city-pack availability and render width. It contains no
|
||||
personal data and is not sent anywhere. Stage 0 means successful initialisation.
|
||||
|
||||
Integrated Intel graphics use shared system memory; the small dedicated-memory
|
||||
figure alone is not a useful rendering budget. Actual laptop performance must be
|
||||
checked on that machine, not inferred from the desktop benchmark.
|
||||
|
||||
The western canal has two rows of varied waterfront facades, raised stone quays,
|
||||
bridge openings, trees and benches. These replace the empty cleared block rows;
|
||||
they use the same world records in the GL and software renderers. The offline
|
||||
`pack_rooftop.py --district-only` option rebuilds layout and lighting without
|
||||
repacking unchanged sky, hand-animation or material assets.
|
||||
|
||||
The art direction takes atmospheric inspiration from the rooftop reference:
|
||||
https://www.loftprojectetagi.ru/visitors/krysha/ . It is an original game backdrop,
|
||||
not an exact survey or reproduction of that venue. Generated source images are
|
||||
preserved alongside the packer metadata. Older city panoramas and furniture
|
||||
reference photographs remain source art, not the active city/furniture renderer.
|
||||
|
||||
Runtime sources: `src_graphics/rooftop*.inc` and the shared MASM city primitives.
|
||||
The normal MASM build and root/bin publisher ship the feature. Rebuilding the
|
||||
existing installer is separate from updating the ordinary game executable.
|
||||
|
||||
## Verification
|
||||
|
||||
`tools/tests/check_hall_expansion.py` compiles the real current MASM routines and
|
||||
tests closed/open door collision, manual cabin entry/exit, both trips, timing,
|
||||
camera/position/bankroll preservation, furniture/rail bounds, seating, automatic
|
||||
closing, thirty repeated bar renders, asset loading/freeing and native screenshots.
|
||||
|
||||
It exports a 70x78 walk grid from actual `CanWalk3D` calls. Reachability checks
|
||||
verify a connected route from the cabin to the bar, both sides of the terrace,
|
||||
and every seating approach after the denser layout. Framebuffer captures are
|
||||
written under `build/hall-expansion/roof-*.png`. `--parity` verifies tested
|
||||
procedures against the full shipping object before publishing.
|
||||
The native harness also checks all three resolution profiles, automatic downgrade,
|
||||
F8 switching, and software roads/water with both present and missing city textures.
|
||||
It also captures GL_FASTEST fog, checks ground-cell bounds and water tessellation.
|
||||
These are compatibility checks on the test machine, not a substitute for confirming
|
||||
the result on the laptop's actual Intel driver.
|
||||
BIN
poker.exe
BIN
poker.obj
|
|
@ -5,12 +5,19 @@ 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,161
|
||||
cmp eax,MATERIAL_JACKBOX_FRONT
|
||||
jb @F
|
||||
cmp eax,163
|
||||
cmp eax,MATERIAL_JACKBOX_BACK
|
||||
ja @F
|
||||
invoke ResolveJackboxSurface3D,material
|
||||
ret
|
||||
@@:
|
||||
cmp eax,ROOF_DECK
|
||||
jb @F
|
||||
cmp eax,ROOF_LAST_MATERIAL
|
||||
ja @F
|
||||
invoke ResolveRoofSurface3D,material
|
||||
ret
|
||||
@@:
|
||||
cmp eax,160
|
||||
jne @F
|
||||
|
|
@ -116,6 +123,7 @@ city_prepare_ray:
|
|||
PrepareCityRays3D ENDP
|
||||
|
||||
CityRayPlane3D PROC coordinate:DWORD,origin:DWORD,direction:DWORD
|
||||
local farDelta:DWORD
|
||||
mov ecx, direction
|
||||
mov eax, ecx
|
||||
test eax, eax
|
||||
|
|
@ -124,6 +132,17 @@ CityRayPlane3D PROC coordinate:DWORD,origin:DWORD,direction:DWORD
|
|||
@@:
|
||||
cmp eax, 256
|
||||
jb city_ray_parallel
|
||||
; A long city ray can overflow IDIV before its depth is clipped. Reject
|
||||
; intersections beyond the exterior far plane before forming the dividend.
|
||||
shl eax,10
|
||||
mov farDelta,eax
|
||||
mov eax,coordinate
|
||||
sub eax,origin
|
||||
cdq
|
||||
xor eax,edx
|
||||
sub eax,edx
|
||||
cmp eax,farDelta
|
||||
jae city_ray_parallel
|
||||
mov eax, coordinate
|
||||
sub eax, origin
|
||||
cdq
|
||||
|
|
@ -140,14 +159,78 @@ CityRayPlane3D ENDP
|
|||
DrawCityVertical3D PROC uses ebx esi edi axis:DWORD,coordinate:DWORD,loBound:DWORD,hiBound:DWORD,zLow:DWORD,zHigh:DWORD,material:DWORD
|
||||
local depth:DWORD,top:DWORD,bottom:DWORD,span:DWORD,column:DWORD
|
||||
local sourceX:DWORD,stepY:DWORD,sourceY:DWORD,destination:DWORD,row:DWORD,texture:DWORD
|
||||
local textureW:DWORD,textureH:DWORD
|
||||
local textureW:DWORD,textureH:DWORD,faceLight:DWORD
|
||||
local endColumn:DWORD,firstX:DWORD,firstDepth:DWORD,clipX:DWORD,clipY:DWORD
|
||||
invoke ResolveCitySurface3D,material
|
||||
test eax, eax
|
||||
jz city_vertical_done
|
||||
mov texture, eax
|
||||
mov textureW, ecx
|
||||
mov textureH, edx
|
||||
mov faceLight,230
|
||||
cmp roofCityPass3D,0
|
||||
je city_vertical_setup
|
||||
cmp axis,0
|
||||
jne city_vertical_light_y
|
||||
mov eax,playerX
|
||||
cmp eax,coordinate
|
||||
jl city_vertical_setup
|
||||
mov faceLight,142
|
||||
jmp city_vertical_setup
|
||||
city_vertical_light_y:
|
||||
mov faceLight,165
|
||||
mov eax,playerY
|
||||
cmp eax,coordinate
|
||||
jl city_vertical_setup
|
||||
mov faceLight,218
|
||||
city_vertical_setup:
|
||||
mov column, 0
|
||||
mov endColumn,SCREEN_W
|
||||
cmp hallLevel3D,HALL_ROOF
|
||||
jne city_vertical_column
|
||||
mov eax,coordinate
|
||||
mov edx,loBound
|
||||
cmp axis,0
|
||||
je @F
|
||||
xchg eax,edx
|
||||
@@:
|
||||
invoke ProjectCityPoint3D,eax,edx
|
||||
mov firstX,eax
|
||||
mov firstDepth,edx
|
||||
mov eax,coordinate
|
||||
mov edx,hiBound
|
||||
cmp axis,0
|
||||
je @F
|
||||
xchg eax,edx
|
||||
@@:
|
||||
invoke ProjectCityPoint3D,eax,edx
|
||||
cmp edx,4096
|
||||
jg city_vertical_clip_second
|
||||
cmp firstDepth,4096
|
||||
jle city_vertical_done
|
||||
jmp city_vertical_column
|
||||
city_vertical_clip_second:
|
||||
cmp firstDepth,4096
|
||||
jle city_vertical_column
|
||||
mov edx,firstX
|
||||
cmp eax,edx
|
||||
jge @F
|
||||
xchg eax,edx
|
||||
@@:
|
||||
sub edx,2
|
||||
add eax,3
|
||||
test edx,edx
|
||||
jns @F
|
||||
xor edx,edx
|
||||
@@:
|
||||
cmp eax,SCREEN_W
|
||||
jle @F
|
||||
mov eax,SCREEN_W
|
||||
@@:
|
||||
cmp edx,eax
|
||||
jge city_vertical_done
|
||||
mov column,edx
|
||||
mov endColumn,eax
|
||||
city_vertical_column:
|
||||
mov ebx, column
|
||||
cmp axis, 0
|
||||
|
|
@ -200,15 +283,15 @@ city_vertical_along:
|
|||
mov sourceX, eax
|
||||
mov eax, viewEye3D
|
||||
sub eax, zHigh
|
||||
imul eax, SCREEN_H
|
||||
cdq
|
||||
mov ecx,SCREEN_H
|
||||
imul ecx
|
||||
idiv depth
|
||||
add eax, viewHorizon3D
|
||||
mov top, eax
|
||||
mov eax, viewEye3D
|
||||
sub eax, zLow
|
||||
imul eax, SCREEN_H
|
||||
cdq
|
||||
mov ecx,SCREEN_H
|
||||
imul ecx
|
||||
idiv depth
|
||||
add eax, viewHorizon3D
|
||||
mov bottom, eax
|
||||
|
|
@ -274,7 +357,10 @@ city_vertical_pixel:
|
|||
@@:
|
||||
mov edx, depth
|
||||
mov cityDepthBuffer3D[edi], edx
|
||||
cmp roofCityPass3D,0
|
||||
jne city_vertical_scene_color
|
||||
and eax, 00FFFFFFh
|
||||
city_vertical_scene_color:
|
||||
cmp cityBackdrop3D, 0
|
||||
je @F
|
||||
push eax
|
||||
|
|
@ -288,7 +374,21 @@ city_vertical_pixel:
|
|||
shr eax, 1
|
||||
add eax, 00101828h
|
||||
@@:
|
||||
cmp roofCityPass3D,0
|
||||
je city_vertical_color_ready
|
||||
invoke ShadeRoofCityPixel3D,eax,depth,faceLight,material
|
||||
mov ebx,edi
|
||||
shr ebx,2
|
||||
mov ecx,roofPixelGlow3D
|
||||
mov roofGlowMask3D[ebx],cl
|
||||
city_vertical_color_ready:
|
||||
mov screenBuffer[edi], eax
|
||||
cmp hallLevel3D,HALL_ROOF
|
||||
jne city_vertical_next_pixel
|
||||
mov ebx,edi
|
||||
shr ebx,2
|
||||
mov ecx,roofCityPass3D
|
||||
mov roofCityMask3D[ebx],cl
|
||||
city_vertical_next_pixel:
|
||||
mov eax, stepY
|
||||
add sourceY, eax
|
||||
|
|
@ -299,7 +399,8 @@ city_vertical_next_pixel:
|
|||
jb city_vertical_pixel
|
||||
city_vertical_next_column:
|
||||
inc column
|
||||
cmp column, SCREEN_W
|
||||
mov eax,endColumn
|
||||
cmp column,eax
|
||||
jb city_vertical_column
|
||||
city_vertical_done:
|
||||
ret
|
||||
|
|
@ -308,7 +409,7 @@ DrawCityVertical3D ENDP
|
|||
DrawCityHorizontal3D PROC uses ebx esi edi xMin:DWORD,xMax:DWORD,yMin:DWORD,yMax:DWORD,z:DWORD,material:DWORD
|
||||
local left:DWORD,right:DWORD,top:DWORD,bottom:DWORD,corner:DWORD,pointX:DWORD,pointY:DWORD
|
||||
local depth:DWORD,row:DWORD,column:DWORD,stepX:DWORD,stepY:DWORD,scaleX:DWORD,scaleY:DWORD,texture:DWORD
|
||||
local textureW:DWORD,textureH:DWORD,nearClipped:DWORD
|
||||
local textureW:DWORD,textureH:DWORD,nearClipped:DWORD,visibleCorners:DWORD
|
||||
invoke ResolveCitySurface3D,material
|
||||
test eax, eax
|
||||
jz city_horizontal_done
|
||||
|
|
@ -316,6 +417,7 @@ DrawCityHorizontal3D PROC uses ebx esi edi xMin:DWORD,xMax:DWORD,yMin:DWORD,yMax
|
|||
mov textureW, ecx
|
||||
mov textureH, edx
|
||||
mov nearClipped, 0
|
||||
mov visibleCorners,0
|
||||
mov left, SCREEN_W
|
||||
mov right, 0
|
||||
mov top, SCREEN_H
|
||||
|
|
@ -338,6 +440,7 @@ city_horizontal_corner:
|
|||
mov nearClipped, 1
|
||||
jmp city_horizontal_next_corner
|
||||
city_horizontal_visible_corner:
|
||||
inc visibleCorners
|
||||
mov esi, edx
|
||||
cmp eax, left
|
||||
jge @F
|
||||
|
|
@ -349,8 +452,8 @@ city_horizontal_visible_corner:
|
|||
@@:
|
||||
mov eax, viewEye3D
|
||||
sub eax, z
|
||||
imul eax, SCREEN_H
|
||||
cdq
|
||||
mov ecx,SCREEN_H
|
||||
imul ecx
|
||||
idiv esi
|
||||
add eax, viewHorizon3D
|
||||
cmp eax, top
|
||||
|
|
@ -365,6 +468,11 @@ city_horizontal_next_corner:
|
|||
inc corner
|
||||
cmp corner, 4
|
||||
jb city_horizontal_corner
|
||||
cmp hallLevel3D,HALL_ROOF
|
||||
jne @F
|
||||
cmp visibleCorners,0
|
||||
je city_horizontal_done
|
||||
@@:
|
||||
cmp nearClipped, 0
|
||||
je @F
|
||||
; Ray bounds/depth below clip the polygon; a behind-camera corner cannot discard it.
|
||||
|
|
@ -414,8 +522,8 @@ city_horizontal_row:
|
|||
jz city_horizontal_next_row
|
||||
mov eax, viewEye3D
|
||||
sub eax, z
|
||||
imul eax, SCREEN_H
|
||||
cdq
|
||||
mov ebx,SCREEN_H
|
||||
imul ebx
|
||||
idiv ecx
|
||||
cmp eax, 4096
|
||||
jle city_horizontal_next_row
|
||||
|
|
@ -508,7 +616,25 @@ city_horizontal_alpha:
|
|||
mov ecx, depth
|
||||
mov cityDepthBuffer3D[edi], ecx
|
||||
and eax, 00FFFFFFh
|
||||
cmp roofCityPass3D,0
|
||||
je city_horizontal_color_ready
|
||||
cmp material,ROOF_CITY_ROAD
|
||||
jne @F
|
||||
invoke SampleRoofGroundLight3D,eax,pointX,pointY
|
||||
@@:
|
||||
invoke ShadeRoofCityPixel3D,eax,depth,244,material
|
||||
mov ebx,edi
|
||||
shr ebx,2
|
||||
mov ecx,roofPixelGlow3D
|
||||
mov roofGlowMask3D[ebx],cl
|
||||
city_horizontal_color_ready:
|
||||
mov screenBuffer[edi], eax
|
||||
cmp hallLevel3D,HALL_ROOF
|
||||
jne city_horizontal_advance
|
||||
mov ebx,edi
|
||||
shr ebx,2
|
||||
mov ecx,roofCityPass3D
|
||||
mov roofCityMask3D[ebx],cl
|
||||
city_horizontal_advance:
|
||||
mov eax, stepX
|
||||
add pointX, eax
|
||||
|
|
@ -529,6 +655,30 @@ city_horizontal_done:
|
|||
DrawCityHorizontal3D ENDP
|
||||
|
||||
DrawCityBoxSides3D PROC xMin:DWORD,xMax:DWORD,yMin:DWORD,yMax:DWORD,zLow:DWORD,zHigh:DWORD,material:DWORD
|
||||
cmp hallLevel3D,HALL_ROOF
|
||||
jne city_box_all_sides
|
||||
mov eax,playerX
|
||||
cmp eax,xMin
|
||||
jge @F
|
||||
invoke DrawCityVertical3D,0,xMin,yMin,yMax,zLow,zHigh,material
|
||||
@@:
|
||||
mov eax,playerX
|
||||
cmp eax,xMax
|
||||
jle @F
|
||||
invoke DrawCityVertical3D,0,xMax,yMin,yMax,zLow,zHigh,material
|
||||
@@:
|
||||
mov eax,playerY
|
||||
cmp eax,yMin
|
||||
jge @F
|
||||
invoke DrawCityVertical3D,1,yMin,xMin,xMax,zLow,zHigh,material
|
||||
@@:
|
||||
mov eax,playerY
|
||||
cmp eax,yMax
|
||||
jle @F
|
||||
invoke DrawCityVertical3D,1,yMax,xMin,xMax,zLow,zHigh,material
|
||||
@@:
|
||||
ret
|
||||
city_box_all_sides:
|
||||
invoke DrawCityVertical3D, 0, xMin, yMin, yMax, zLow, zHigh, material
|
||||
invoke DrawCityVertical3D, 0, xMax, yMin, yMax, zLow, zHigh, material
|
||||
invoke DrawCityVertical3D, 1, yMin, xMin, xMax, zLow, zHigh, material
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ DartsWallText3D PROTO :DWORD,:DWORD,:DWORD
|
|||
DrawDartsScoreboard3D PROTO
|
||||
DrawDartsProps3D PROTO
|
||||
DrawDartsArm3D PROTO :DWORD,:SDWORD,:SDWORD
|
||||
DrawDartsReticle3D PROTO :SDWORD,:SDWORD
|
||||
DartsScore3D PROTO :SDWORD,:SDWORD
|
||||
SettleDarts3D PROTO
|
||||
DrawDartsRoom3D PROTO
|
||||
|
|
@ -45,7 +46,7 @@ dtWall3D AnimPack3D <>
|
|||
dtChair3D AnimPack3D <>
|
||||
dtTable3D AnimPack3D <>
|
||||
dtRoom3D AnimPack3D <>
|
||||
dtBoardPath db "assets\animations\darts\board-v1.bin",0
|
||||
dtBoardPath db "assets\animations\darts\board-v2.bin",0
|
||||
dtHandsPath db "assets\animations\darts\arm-throw-v3.bin",0
|
||||
dtDartPath db "assets\animations\darts\dart-views-v1.bin",0
|
||||
dtWallPath db "assets\animations\darts\lounge-wall-v1.bin",0
|
||||
|
|
@ -134,6 +135,7 @@ dtDouble db 68,0
|
|||
dtTriple db 84,0
|
||||
dtSingle db 83,0
|
||||
dtTarget db 214,197,203,220,0
|
||||
dtReticleOffsets3D dd -3,0,-2,0,2,0,3,0,0,-3,0,-2,0,2,0,3,0,0
|
||||
.data?
|
||||
dtRayX3D dd 640 dup(?)
|
||||
dtRayZ3D dd 640 dup(?)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,39 @@
|
|||
; Nine physical pixels, with contrast chosen from the surface beneath each one.
|
||||
DrawDartsReticle3D PROC uses ebx esi edi targetX:SDWORD,targetY:SDWORD
|
||||
lea esi,dtReticleOffsets3D
|
||||
mov ebx,9
|
||||
dt_reticle_point:
|
||||
mov eax,targetX
|
||||
add eax,[esi]
|
||||
cmp eax,640
|
||||
jae dt_reticle_next
|
||||
mov edx,targetY
|
||||
add edx,[esi+4]
|
||||
cmp edx,32
|
||||
jl dt_reticle_next
|
||||
cmp edx,400
|
||||
jge dt_reticle_next
|
||||
imul edx,640
|
||||
add edx,eax
|
||||
lea edi,screenBuffer[edx*4]
|
||||
movzx eax,byte ptr [edi]
|
||||
movzx ecx,byte ptr [edi+1]
|
||||
lea eax,[eax+ecx*2]
|
||||
movzx ecx,byte ptr [edi+2]
|
||||
add eax,ecx
|
||||
mov ecx,00F6E6ADh
|
||||
cmp eax,560
|
||||
jb @F
|
||||
mov ecx,00152021h
|
||||
@@:
|
||||
mov [edi],ecx
|
||||
dt_reticle_next:
|
||||
add esi,8
|
||||
dec ebx
|
||||
jnz dt_reticle_point
|
||||
ret
|
||||
DrawDartsReticle3D ENDP
|
||||
|
||||
; A single rigid sprite transform aligns the dart axis with the live target.
|
||||
DrawDartsArm3D PROC uses ebx esi edi pose:DWORD,targetX:SDWORD,targetY:SDWORD
|
||||
local originX:DWORD,originY:DWORD,left:DWORD,right:DWORD,bottom:DWORD,row:DWORD,column:DWORD
|
||||
|
|
|
|||
|
|
@ -545,22 +545,7 @@ dt_render_pose:
|
|||
invoke DrawDartsArm3D,pose,bx0,by0
|
||||
cmp dtState3D,DT_AIM
|
||||
jne dt_render_hud
|
||||
mov eax,bx0
|
||||
sub eax,7
|
||||
mov edx,by0
|
||||
sub edx,2
|
||||
invoke DrawBuckshotPanel3D,eax,edx,15,5,00101112h
|
||||
mov eax,by0
|
||||
sub eax,7
|
||||
mov edx,bx0
|
||||
sub edx,2
|
||||
invoke DrawBuckshotPanel3D,edx,eax,5,15,00101112h
|
||||
mov eax,bx0
|
||||
sub eax,6
|
||||
invoke DrawBuckshotPanel3D,eax,by0,13,2,00F6E6ADh
|
||||
mov eax,by0
|
||||
sub eax,6
|
||||
invoke DrawBuckshotPanel3D,bx0,eax,2,13,00F6E6ADh
|
||||
invoke DrawDartsReticle3D,bx0,by0
|
||||
dt_render_hud:
|
||||
invoke DrawBox,0,0,320,16,00151D20h
|
||||
invoke DrawString8x8,8,4,OFFSET dtTitle,00E8D7ADh
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ fortune_session_load:
|
|||
invoke LoadAnimPack3D,esi,fortuneSessionPaths3D[ebx*4]
|
||||
add esi,SIZEOF AnimPack3D
|
||||
inc ebx
|
||||
cmp ebx,7
|
||||
cmp ebx,FORTUNE_SESSION_PACK_COUNT
|
||||
jb fortune_session_load
|
||||
mov engineMode,MODE_FORTUNE
|
||||
mov paused3D,0
|
||||
|
|
@ -24,6 +24,7 @@ fortune_session_load:
|
|||
mov fortuneQuestionLength3D,0
|
||||
mov fortuneQuestion3D,0
|
||||
mov fortuneWorkerState3D,0
|
||||
call ResetFortuneFate3D
|
||||
mov eax,animationClock3D
|
||||
mov fortuneSessionClock3D,eax
|
||||
invoke SetFortuneSpeech3D,OFFSET fortuneWelcome3D
|
||||
|
|
@ -35,13 +36,14 @@ BeginFortuneDialog3D ENDP
|
|||
; Kept as the public exit/shutdown hook. No inference process is launched.
|
||||
StopFortuneWorker3D PROC uses ebx esi
|
||||
lea esi,fortuneSessionPacks3D
|
||||
mov ebx,7
|
||||
mov ebx,FORTUNE_SESSION_PACK_COUNT
|
||||
fortune_session_free:
|
||||
invoke FreeAnimPack3D,esi
|
||||
add esi,SIZEOF AnimPack3D
|
||||
dec ebx
|
||||
jnz fortune_session_free
|
||||
mov fortuneWorkerState3D,0
|
||||
call ResetFortuneFate3D
|
||||
ret
|
||||
StopFortuneWorker3D ENDP
|
||||
|
||||
|
|
@ -129,11 +131,17 @@ fortune_char_tarot:
|
|||
ret
|
||||
fortune_char_orb:
|
||||
mov fortuneSubMode3D,FORTUNE_SUBMODE_ORB
|
||||
mov fortunePhase3D,1
|
||||
call ResetFortuneFate3D
|
||||
mov fortunePhase3D,0
|
||||
mov fortunePhaseTime3D,0
|
||||
invoke SetFortuneSpeech3D,OFFSET fortuneOrbStatus3D
|
||||
invoke SetFortuneSpeech3D,OFFSET fortuneFateReady3D
|
||||
ret
|
||||
fortune_char_question:
|
||||
cmp fortuneSubMode3D,FORTUNE_SUBMODE_ORB
|
||||
jne @F
|
||||
invoke FortuneFateChar3D,character
|
||||
ret
|
||||
@@:
|
||||
cmp fortuneSubMode3D,FORTUNE_SUBMODE_TAROT
|
||||
jne fortune_char_done
|
||||
cmp fortunePhase3D,0
|
||||
|
|
@ -277,6 +285,7 @@ fortune_update_orb:
|
|||
jb fortune_update_done
|
||||
mov fortunePhase3D,2
|
||||
mov fortunePhaseTime3D,0
|
||||
invoke SetFortuneSpeech3D,OFFSET fortuneFateSwirl3D
|
||||
jmp fortune_update_done
|
||||
fortune_orb_swirl:
|
||||
cmp fortunePhase3D,2
|
||||
|
|
@ -285,7 +294,7 @@ fortune_orb_swirl:
|
|||
jb fortune_update_done
|
||||
mov fortunePhase3D,3
|
||||
mov fortunePhaseTime3D,0
|
||||
invoke SetFortuneSpeech3D,OFFSET fortuneOrbText3D
|
||||
invoke SetFortuneSpeech3D,OFFSET fortuneFateReveal3D
|
||||
fortune_update_done:
|
||||
ret
|
||||
UpdateFortuneSession3D ENDP
|
||||
|
|
@ -315,6 +324,37 @@ HandleFortuneDialog3D PROC
|
|||
jae fortune_handle_reset
|
||||
jmp fortune_handle_action
|
||||
fortune_handle_menu_click:
|
||||
cmp fortuneSubMode3D,FORTUNE_SUBMODE_ORB
|
||||
jne fortune_handle_selection_click
|
||||
cmp fortunePhase3D,0
|
||||
jne fortune_handle_done
|
||||
; Form uses physical pixels; mouse input here uses half-resolution units.
|
||||
mov eax,mouseY3D
|
||||
sub eax,162
|
||||
jb fortune_handle_done
|
||||
xor edx,edx
|
||||
mov ecx,21
|
||||
div ecx
|
||||
cmp eax,2
|
||||
ja fortune_handle_done
|
||||
cmp edx,11
|
||||
ja fortune_handle_done
|
||||
shl eax,1
|
||||
cmp mouseX3D,156
|
||||
jb fortune_handle_form_left
|
||||
cmp mouseX3D,168
|
||||
jb fortune_handle_done
|
||||
cmp mouseX3D,312
|
||||
ja fortune_handle_done
|
||||
inc eax
|
||||
jmp fortune_handle_form_focus
|
||||
fortune_handle_form_left:
|
||||
cmp mouseX3D,8
|
||||
jb fortune_handle_done
|
||||
fortune_handle_form_focus:
|
||||
mov fortuneFateField3D,eax
|
||||
ret
|
||||
fortune_handle_selection_click:
|
||||
cmp fortuneSubMode3D,FORTUNE_SUBMODE_MENU
|
||||
jne fortune_handle_done
|
||||
cmp mouseY3D,196
|
||||
|
|
@ -332,6 +372,11 @@ fortune_handle_orb:
|
|||
fortune_handle_action:
|
||||
cmp fortuneSubMode3D,FORTUNE_SUBMODE_MENU
|
||||
je fortune_handle_tarot
|
||||
cmp fortuneSubMode3D,FORTUNE_SUBMODE_ORB
|
||||
jne @F
|
||||
call SubmitFortuneFate3D
|
||||
ret
|
||||
@@:
|
||||
cmp fortuneSubMode3D,FORTUNE_SUBMODE_TAROT
|
||||
jne fortune_handle_done
|
||||
cmp fortunePhase3D,0
|
||||
|
|
@ -367,9 +412,14 @@ fortune_handle_menu:
|
|||
fortune_handle_reset:
|
||||
cmp fortuneSubMode3D,FORTUNE_SUBMODE_ORB
|
||||
jne @F
|
||||
mov fortunePhase3D,1
|
||||
cmp fortunePhase3D,3
|
||||
jne fortune_handle_done
|
||||
cmp fortunePhaseTime3D,1200
|
||||
jb fortune_handle_done
|
||||
mov fortunePhase3D,0
|
||||
mov fortunePhaseTime3D,0
|
||||
invoke SetFortuneSpeech3D,OFFSET fortuneOrbStatus3D
|
||||
mov fortuneFateResult3D,-1
|
||||
invoke SetFortuneSpeech3D,OFFSET fortuneFateReady3D
|
||||
ret
|
||||
@@:
|
||||
cmp fortunePhase3D,12
|
||||
|
|
@ -692,6 +742,8 @@ fortune_render_hand_gesture:
|
|||
mov esi,eax
|
||||
jmp fortune_render_hands
|
||||
fortune_render_orb_hands:
|
||||
cmp fortunePhase3D,0
|
||||
je fortune_render_hands
|
||||
mov ebx,OFFSET fortuneSessionOrbHands3D
|
||||
mov eax,fortunePhaseTime3D
|
||||
shr eax,5
|
||||
|
|
@ -737,28 +789,24 @@ fortune_render_menu:
|
|||
invoke DrawBuckshotText3D,364,404,OFFSET fortuneFate3D,00D2E8F2h
|
||||
jmp fortune_render_footer
|
||||
fortune_render_orb:
|
||||
mov eax,fortunePhaseTime3D
|
||||
shr eax,6
|
||||
and eax,15
|
||||
mov esi,eax
|
||||
mov ebx,195
|
||||
cmp fortunePhase3D,1
|
||||
cmp fortunePhase3D,0
|
||||
jne fortune_render_orb_active
|
||||
call DrawFortuneFateForm3D
|
||||
jmp fortune_render_footer
|
||||
fortune_render_orb_active:
|
||||
call ResolveFortuneOrb3D
|
||||
invoke DrawAnimSprite3D,OFFSET fortuneSessionMist3D,fortuneOrbFrame3D,fortuneOrbX3D,fortuneOrbY3D,128,128
|
||||
cmp fortunePhase3D,3
|
||||
jne @F
|
||||
mov eax,fortunePhaseTime3D
|
||||
imul eax,135
|
||||
xor edx,edx
|
||||
mov ecx,1400
|
||||
div ecx
|
||||
mov ebx,330
|
||||
sub ebx,eax
|
||||
call DrawFortuneFateResult3D
|
||||
@@:
|
||||
invoke DrawAnimSprite3D,OFFSET fortuneSessionMist3D,esi,214,ebx,128,128
|
||||
call DrawFortuneFateIdentity3D
|
||||
fortune_render_footer:
|
||||
call DrawFortuneSpeech3D
|
||||
cmp fortuneSubMode3D,FORTUNE_SUBMODE_MENU
|
||||
je fortune_render_exit_button
|
||||
cmp fortuneSubMode3D,FORTUNE_SUBMODE_TAROT
|
||||
jne fortune_render_again_button
|
||||
jne fortune_render_fate_button
|
||||
invoke DrawBoxBorder,8,227,76,12,000B1E30h,00445F75h,1
|
||||
mov esi,OFFSET fortuneAsk3D
|
||||
cmp fortunePhase3D,0
|
||||
|
|
@ -766,9 +814,20 @@ fortune_render_footer:
|
|||
mov esi,OFFSET fortuneContinue3D
|
||||
@@:
|
||||
invoke DrawBuckshotText3D,24,458,esi,00CCE4F4h
|
||||
jmp fortune_render_again_button
|
||||
fortune_render_fate_button:
|
||||
cmp fortunePhase3D,0
|
||||
jne fortune_render_again_button
|
||||
invoke DrawBoxBorder,8,227,76,12,000B1E30h,00445F75h,1
|
||||
invoke DrawBuckshotText3D,24,458,OFFSET fortuneFateStart3D,00CCE4F4h
|
||||
fortune_render_again_button:
|
||||
invoke DrawBoxBorder,88,227,76,12,000B1E30h,00445F75h,1
|
||||
invoke DrawBuckshotText3D,192,458,OFFSET fortuneAgain3D,0091A8BCh
|
||||
mov esi,OFFSET fortuneAgain3D
|
||||
cmp fortuneSubMode3D,FORTUNE_SUBMODE_ORB
|
||||
jne @F
|
||||
mov esi,OFFSET fortuneFateEdit3D
|
||||
@@:
|
||||
invoke DrawBuckshotText3D,192,458,esi,0091A8BCh
|
||||
invoke DrawBoxBorder,172,227,64,12,000B1E30h,00445F75h,1
|
||||
invoke DrawBuckshotText3D,368,458,OFFSET fortuneMenu3D,00CCE4F4h
|
||||
fortune_render_exit_button:
|
||||
|
|
@ -776,3 +835,425 @@ fortune_render_exit_button:
|
|||
invoke DrawBuckshotText3D,516,458,OFFSET fortuneExit3D,00CCE4F4h
|
||||
ret
|
||||
RenderFortuneDialog3D ENDP
|
||||
|
||||
ResetFortuneFate3D PROC uses edi
|
||||
lea edi,fortuneFateLengths3D
|
||||
xor eax,eax
|
||||
mov ecx,6
|
||||
rep stosd
|
||||
lea edi,fortuneFateFields3D
|
||||
mov ecx,96
|
||||
rep stosd
|
||||
mov fortuneFateField3D,0
|
||||
mov fortuneFateResult3D,-1
|
||||
mov fortuneFateFields3D[256],'9'
|
||||
mov fortuneFateFields3D[320],'1'
|
||||
mov fortuneFateFields3D[321],'0'
|
||||
mov fortuneFateLengths3D[16],1
|
||||
mov fortuneFateLengths3D[20],2
|
||||
ret
|
||||
ResetFortuneFate3D ENDP
|
||||
|
||||
FortuneFateChar3D PROC uses ebx esi edi character:DWORD
|
||||
cmp fortunePhase3D,0
|
||||
jne fate_char_done
|
||||
mov ebx,fortuneFateField3D
|
||||
cmp ebx,6
|
||||
jae fate_char_done
|
||||
mov eax,character
|
||||
cmp eax,9
|
||||
je fate_char_next
|
||||
mov esi,ebx
|
||||
shl esi,6
|
||||
lea esi,fortuneFateFields3D[esi]
|
||||
mov ecx,fortuneFateLengths3D[ebx*4]
|
||||
cmp eax,8
|
||||
je fate_char_backspace
|
||||
cmp eax,32
|
||||
jb fate_char_done
|
||||
cmp eax,255
|
||||
ja fate_char_done
|
||||
cmp eax,127
|
||||
je fate_char_done
|
||||
cmp ecx,fortuneFateLimits3D[ebx*4]
|
||||
jae fate_char_done
|
||||
cmp ebx,2
|
||||
je fate_char_date
|
||||
cmp ebx,4
|
||||
jae fate_char_digit
|
||||
cmp eax,32
|
||||
jne fate_char_store
|
||||
test ecx,ecx
|
||||
jz fate_char_done
|
||||
cmp BYTE PTR [esi+ecx-1],32
|
||||
je fate_char_done
|
||||
jmp fate_char_store
|
||||
fate_char_date:
|
||||
cmp eax,'/'
|
||||
jne @F
|
||||
mov eax,'.'
|
||||
@@:
|
||||
cmp eax,'.'
|
||||
jne fate_char_date_digit
|
||||
cmp ecx,2
|
||||
je fate_char_store
|
||||
cmp ecx,5
|
||||
je fate_char_store
|
||||
jmp fate_char_done
|
||||
fate_char_date_digit:
|
||||
cmp eax,'0'
|
||||
jb fate_char_done
|
||||
cmp eax,'9'
|
||||
ja fate_char_done
|
||||
cmp ecx,2
|
||||
je fate_char_date_dot
|
||||
cmp ecx,5
|
||||
jne fate_char_store
|
||||
fate_char_date_dot:
|
||||
mov BYTE PTR [esi+ecx],'.'
|
||||
inc ecx
|
||||
jmp fate_char_store
|
||||
fate_char_digit:
|
||||
cmp eax,'0'
|
||||
jb fate_char_done
|
||||
cmp eax,'9'
|
||||
ja fate_char_done
|
||||
fate_char_store:
|
||||
mov [esi+ecx],al
|
||||
inc ecx
|
||||
mov fortuneFateLengths3D[ebx*4],ecx
|
||||
mov BYTE PTR [esi+ecx],0
|
||||
ret
|
||||
fate_char_backspace:
|
||||
test ecx,ecx
|
||||
jz fate_char_done
|
||||
dec ecx
|
||||
mov fortuneFateLengths3D[ebx*4],ecx
|
||||
mov BYTE PTR [esi+ecx],0
|
||||
ret
|
||||
fate_char_next:
|
||||
inc ebx
|
||||
cmp ebx,6
|
||||
jb @F
|
||||
xor ebx,ebx
|
||||
@@:
|
||||
mov fortuneFateField3D,ebx
|
||||
fate_char_done:
|
||||
ret
|
||||
FortuneFateChar3D ENDP
|
||||
|
||||
; Validation is calendar-based, including leap years, not a birth-date heuristic.
|
||||
ValidateFortuneBirth3D PROC uses ebx esi edi
|
||||
local dayValue:DWORD,monthValue:DWORD
|
||||
cmp fortuneFateLengths3D[8],10
|
||||
jne fate_birth_bad
|
||||
lea esi,fortuneFateFields3D[128]
|
||||
cmp BYTE PTR [esi+2],'.'
|
||||
jne fate_birth_bad
|
||||
cmp BYTE PTR [esi+5],'.'
|
||||
jne fate_birth_bad
|
||||
xor ebx,ebx
|
||||
xor edi,edi
|
||||
fate_birth_parse:
|
||||
movzx eax,BYTE PTR [esi+edi]
|
||||
cmp edi,2
|
||||
je fate_birth_day
|
||||
cmp edi,5
|
||||
je fate_birth_month
|
||||
sub eax,'0'
|
||||
cmp eax,9
|
||||
ja fate_birth_bad
|
||||
imul ebx,10
|
||||
add ebx,eax
|
||||
jmp fate_birth_next
|
||||
fate_birth_day:
|
||||
mov dayValue,ebx
|
||||
xor ebx,ebx
|
||||
jmp fate_birth_next
|
||||
fate_birth_month:
|
||||
mov monthValue,ebx
|
||||
xor ebx,ebx
|
||||
fate_birth_next:
|
||||
inc edi
|
||||
cmp edi,10
|
||||
jb fate_birth_parse
|
||||
cmp ebx,1900
|
||||
jb fate_birth_bad
|
||||
cmp ebx,2099
|
||||
ja fate_birth_bad
|
||||
mov eax,monthValue
|
||||
dec eax
|
||||
cmp eax,11
|
||||
ja fate_birth_bad
|
||||
movzx ecx,fortuneFateMonthDays3D[eax]
|
||||
cmp eax,1
|
||||
jne fate_birth_check_day
|
||||
test ebx,3
|
||||
jnz fate_birth_check_day
|
||||
cmp ebx,1900
|
||||
je fate_birth_check_day
|
||||
inc ecx
|
||||
fate_birth_check_day:
|
||||
cmp dayValue,1
|
||||
jb fate_birth_bad
|
||||
cmp dayValue,ecx
|
||||
ja fate_birth_bad
|
||||
mov eax,1
|
||||
ret
|
||||
fate_birth_bad:
|
||||
xor eax,eax
|
||||
ret
|
||||
ValidateFortuneBirth3D ENDP
|
||||
|
||||
ParseFortuneFateNumber3D PROC uses ebx esi slot:DWORD
|
||||
mov eax,slot
|
||||
mov ecx,fortuneFateLengths3D[eax*4]
|
||||
test ecx,ecx
|
||||
jz fate_number_bad
|
||||
cmp ecx,3
|
||||
ja fate_number_bad
|
||||
shl eax,6
|
||||
lea esi,fortuneFateFields3D[eax]
|
||||
xor eax,eax
|
||||
fate_number_parse:
|
||||
movzx ebx,BYTE PTR [esi]
|
||||
sub ebx,'0'
|
||||
cmp ebx,9
|
||||
ja fate_number_bad
|
||||
imul eax,10
|
||||
add eax,ebx
|
||||
inc esi
|
||||
loop fate_number_parse
|
||||
cmp eax,100
|
||||
ja fate_number_bad
|
||||
ret
|
||||
fate_number_bad:
|
||||
mov eax,-1
|
||||
ret
|
||||
ParseFortuneFateNumber3D ENDP
|
||||
|
||||
SubmitFortuneFate3D PROC uses ebx
|
||||
cmp fortuneSubMode3D,FORTUNE_SUBMODE_ORB
|
||||
jne fate_submit_done
|
||||
cmp fortunePhase3D,0
|
||||
jne fate_submit_done
|
||||
xor ebx,ebx
|
||||
fate_submit_required:
|
||||
cmp fortuneFateLengths3D[ebx*4],0
|
||||
je fate_submit_missing
|
||||
inc ebx
|
||||
cmp ebx,6
|
||||
jb fate_submit_required
|
||||
call ValidateFortuneBirth3D
|
||||
test eax,eax
|
||||
jz fate_submit_date
|
||||
invoke ParseFortuneFateNumber3D,4
|
||||
cmp eax,-1
|
||||
je fate_submit_low
|
||||
mov fortuneFateLowValue3D,eax
|
||||
invoke ParseFortuneFateNumber3D,5
|
||||
cmp eax,-1
|
||||
je fate_submit_high
|
||||
cmp eax,fortuneFateLowValue3D
|
||||
jb fate_submit_high
|
||||
mov fortuneFateHighValue3D,eax
|
||||
sub eax,fortuneFateLowValue3D
|
||||
inc eax
|
||||
; Exactly one draw, independent of identity. Renders and double clicks never reroll.
|
||||
invoke RandomRange3D,eax
|
||||
add eax,fortuneFateLowValue3D
|
||||
mov fortuneFateResult3D,eax
|
||||
mov fortunePhase3D,1
|
||||
mov fortunePhaseTime3D,0
|
||||
mov eax,animationClock3D
|
||||
mov fortuneSessionClock3D,eax
|
||||
invoke SetFortuneSpeech3D,OFFSET fortuneFateRising3D
|
||||
fate_submit_done:
|
||||
ret
|
||||
fate_submit_missing:
|
||||
mov fortuneFateField3D,ebx
|
||||
invoke SetFortuneSpeech3D,OFFSET fortuneFateRequired3D
|
||||
ret
|
||||
fate_submit_date:
|
||||
mov fortuneFateField3D,2
|
||||
invoke SetFortuneSpeech3D,OFFSET fortuneFateDateError3D
|
||||
ret
|
||||
fate_submit_low:
|
||||
mov fortuneFateField3D,4
|
||||
jmp fate_submit_range
|
||||
fate_submit_high:
|
||||
mov fortuneFateField3D,5
|
||||
fate_submit_range:
|
||||
invoke SetFortuneSpeech3D,OFFSET fortuneFateRangeError3D
|
||||
ret
|
||||
SubmitFortuneFate3D ENDP
|
||||
|
||||
DrawFortuneFateForm3D PROC uses ebx esi edi
|
||||
local px:DWORD,py:DWORD,edgeInk:DWORD
|
||||
invoke DrawBoxBorder,8,149,304,74,00081322h,0046637Fh,1
|
||||
invoke DrawBuckshotText3D,24,304,OFFSET fortuneFatePlay3D,0086ABBCh
|
||||
xor esi,esi
|
||||
fate_form_field:
|
||||
mov eax,fortuneFateXs3D[esi*4]
|
||||
mov px,eax
|
||||
shr eax,1
|
||||
mov ebx,fortuneFateYs3D[esi*4]
|
||||
mov py,ebx
|
||||
shr ebx,1
|
||||
mov edgeInk,00314D65h
|
||||
cmp esi,fortuneFateField3D
|
||||
jne @F
|
||||
mov edgeInk,0088DDEAh
|
||||
@@:
|
||||
invoke DrawBoxBorder,eax,ebx,144,11,000B1D30h,edgeInk,1
|
||||
mov eax,px
|
||||
add eax,8
|
||||
mov ebx,py
|
||||
sub ebx,9
|
||||
invoke DrawBuckshotText3D,eax,ebx,fortuneFateLabels3D[esi*4],00A9C8D8h
|
||||
mov edi,esi
|
||||
shl edi,6
|
||||
add edi,OFFSET fortuneFateFields3D
|
||||
mov eax,px
|
||||
add eax,8
|
||||
mov ebx,py
|
||||
add ebx,7
|
||||
cmp esi,2
|
||||
jne fate_form_value
|
||||
cmp fortuneFateLengths3D[8],0
|
||||
jne fate_form_value
|
||||
invoke DrawBuckshotText3D,eax,ebx,OFFSET fortuneFateHint3D,005A7A95h
|
||||
jmp fate_form_caret
|
||||
fate_form_value:
|
||||
invoke DrawBuckshotText3D,eax,ebx,edi,00E8EFF4h
|
||||
fate_form_caret:
|
||||
cmp esi,fortuneFateField3D
|
||||
jne fate_form_next
|
||||
test animationClock3D,200h
|
||||
jnz fate_form_next
|
||||
mov eax,fortuneFateLengths3D[esi*4]
|
||||
shl eax,3
|
||||
add eax,px
|
||||
add eax,8
|
||||
mov ebx,py
|
||||
add ebx,7
|
||||
invoke DrawBuckshotText3D,eax,ebx,OFFSET fortuneCaret3D,00B8F3FFh
|
||||
fate_form_next:
|
||||
inc esi
|
||||
cmp esi,6
|
||||
jb fate_form_field
|
||||
ret
|
||||
DrawFortuneFateForm3D ENDP
|
||||
|
||||
DrawFortuneFateIdentity3D PROC uses ebx esi edi
|
||||
invoke DrawBoxBorder,8,190,304,33,00081322h,0046637Fh,1
|
||||
xor esi,esi
|
||||
fate_identity_field:
|
||||
mov eax,fortuneFateXs3D[esi*4]
|
||||
add eax,8
|
||||
mov ebx,esi
|
||||
shr ebx,1
|
||||
imul ebx,20
|
||||
add ebx,388
|
||||
mov edi,esi
|
||||
shl edi,6
|
||||
add edi,OFFSET fortuneFateFields3D
|
||||
cmp esi,4
|
||||
jb @F
|
||||
invoke DrawBuckshotText3D,eax,ebx,fortuneFateLabels3D[esi*4],0086ABBCh
|
||||
mov eax,fortuneFateXs3D[esi*4]
|
||||
add eax,40
|
||||
@@:
|
||||
invoke DrawBuckshotText3D,eax,ebx,edi,00E8EFF4h
|
||||
inc esi
|
||||
cmp esi,6
|
||||
jb fate_identity_field
|
||||
ret
|
||||
DrawFortuneFateIdentity3D ENDP
|
||||
|
||||
; One resolver supplies the frame, sprite placement and its measured glass centre.
|
||||
ResolveFortuneOrb3D PROC uses ebx
|
||||
mov eax,fortunePhaseTime3D
|
||||
shr eax,6
|
||||
and eax,15
|
||||
mov fortuneOrbFrame3D,eax
|
||||
mov ebx,eax
|
||||
mov fortuneOrbX3D,214
|
||||
mov fortuneOrbY3D,195
|
||||
cmp fortunePhase3D,1
|
||||
jne fate_orb_anchor
|
||||
mov eax,fortunePhaseTime3D
|
||||
cmp eax,1400
|
||||
jbe @F
|
||||
mov eax,1400
|
||||
@@:
|
||||
imul eax,135
|
||||
xor edx,edx
|
||||
mov ecx,1400
|
||||
div ecx
|
||||
mov ecx,330
|
||||
sub ecx,eax
|
||||
mov fortuneOrbY3D,ecx
|
||||
fate_orb_anchor:
|
||||
mov eax,fortuneOrbAnchorX3D[ebx*4]
|
||||
add eax,128
|
||||
shr eax,8
|
||||
add eax,fortuneOrbX3D
|
||||
mov fortuneOrbCenterX3D,eax
|
||||
mov eax,fortuneOrbAnchorY3D[ebx*4]
|
||||
add eax,128
|
||||
shr eax,8
|
||||
add eax,fortuneOrbY3D
|
||||
mov fortuneOrbCenterY3D,eax
|
||||
ret
|
||||
ResolveFortuneOrb3D ENDP
|
||||
|
||||
DrawFortuneFateResult3D PROC uses ebx esi edi
|
||||
local numberX:DWORD,numberY:DWORD
|
||||
cmp fortuneFateResult3D,100
|
||||
ja fate_result_done
|
||||
invoke FormatNumber,fortuneFateResult3D,OFFSET fortuneFateNumber3D
|
||||
lea edi,fortuneFateNumber3D
|
||||
xor ebx,ebx
|
||||
fate_result_length:
|
||||
cmp BYTE PTR [edi+ebx],0
|
||||
je fate_result_place
|
||||
inc ebx
|
||||
jmp fate_result_length
|
||||
fate_result_place:
|
||||
dec ebx
|
||||
imul ebx,20
|
||||
add ebx,32
|
||||
shr ebx,1
|
||||
neg ebx
|
||||
add ebx,fortuneOrbCenterX3D
|
||||
mov numberX,ebx
|
||||
mov eax,fortuneOrbCenterY3D
|
||||
sub eax,24
|
||||
mov numberY,eax
|
||||
; Fade into the mist at a constant size; preserve opacity for later draws.
|
||||
push animOpacity3D
|
||||
mov eax,fortunePhaseTime3D
|
||||
cmp eax,900
|
||||
jbe @F
|
||||
mov eax,900
|
||||
@@:
|
||||
imul eax,255
|
||||
xor edx,edx
|
||||
mov ecx,900
|
||||
div ecx
|
||||
mov animOpacity3D,eax
|
||||
fate_result_digit:
|
||||
movzx esi,BYTE PTR [edi]
|
||||
test esi,esi
|
||||
jz fate_result_restore
|
||||
sub esi,'0'
|
||||
invoke DrawAnimSprite3D,OFFSET fortuneSessionNumerals3D,esi,numberX,numberY,32,48
|
||||
add numberX,20
|
||||
inc edi
|
||||
jmp fate_result_digit
|
||||
fate_result_restore:
|
||||
pop animOpacity3D
|
||||
fate_result_done:
|
||||
ret
|
||||
DrawFortuneFateResult3D ENDP
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ MODE_FORTUNE equ 25
|
|||
FORTUNE_SUBMODE_MENU equ 0
|
||||
FORTUNE_SUBMODE_TAROT equ 1
|
||||
FORTUNE_SUBMODE_ORB equ 2
|
||||
FORTUNE_SESSION_PACK_COUNT equ 8
|
||||
BeginFortuneDialog3D PROTO
|
||||
HandleFortuneDialog3D PROTO
|
||||
RenderFortuneDialog3D PROTO
|
||||
|
|
@ -15,6 +16,15 @@ SetFortuneSpeech3D PROTO :DWORD
|
|||
FortuneCardSpeech3D PROTO :DWORD
|
||||
DrawFortuneSpeech3D PROTO
|
||||
DrawFortuneSessionCard3D PROTO :DWORD
|
||||
ResetFortuneFate3D PROTO
|
||||
FortuneFateChar3D PROTO :DWORD
|
||||
SubmitFortuneFate3D PROTO
|
||||
ValidateFortuneBirth3D PROTO
|
||||
ParseFortuneFateNumber3D PROTO :DWORD
|
||||
DrawFortuneFateForm3D PROTO
|
||||
DrawFortuneFateIdentity3D PROTO
|
||||
DrawFortuneFateResult3D PROTO
|
||||
ResolveFortuneOrb3D PROTO
|
||||
.data
|
||||
fortuneSessionPacks3D LABEL BYTE
|
||||
fortuneSessionRoom3D AnimPack3D <>
|
||||
|
|
@ -24,6 +34,7 @@ fortuneSessionGesture3D AnimPack3D <>
|
|||
fortuneSessionOrbHands3D AnimPack3D <>
|
||||
fortuneSessionMist3D AnimPack3D <>
|
||||
fortuneSessionCards3D AnimPack3D <>
|
||||
fortuneSessionNumerals3D AnimPack3D <>
|
||||
fortuneSessionPath0 db "assets\animations\fortune\room-v1.bin",0
|
||||
fortuneSessionPath1 db "assets\animations\fortune\shuffle-v1.bin",0
|
||||
fortuneSessionPath2 db "assets\animations\fortune\deal-v1.bin",0
|
||||
|
|
@ -31,8 +42,10 @@ fortuneSessionPath3 db "assets\animations\fortune\gesture-v1.bin",0
|
|||
fortuneSessionPath4 db "assets\animations\fortune\orb-hands-v1.bin",0
|
||||
fortuneSessionPath5 db "assets\animations\fortune\orb-mist-v1.bin",0
|
||||
fortuneSessionPath6 db "assets\animations\fortune\cards-v1.bin",0
|
||||
fortuneSessionPath7 db "assets\animations\fortune\orb-numerals-v1.bin",0
|
||||
fortuneSessionPaths3D dd OFFSET fortuneSessionPath0,OFFSET fortuneSessionPath1,OFFSET fortuneSessionPath2,OFFSET fortuneSessionPath3
|
||||
dd OFFSET fortuneSessionPath4,OFFSET fortuneSessionPath5,OFFSET fortuneSessionPath6
|
||||
dd OFFSET fortuneSessionPath7
|
||||
fortuneSubMode3D dd 0
|
||||
fortunePhase3D dd 0
|
||||
fortunePhaseTime3D dd 0
|
||||
|
|
@ -120,6 +133,46 @@ fortuneCardNames3D dd OFFSET fortuneCardName0,OFFSET fortuneCardName1,OFFSET for
|
|||
dd OFFSET fortuneCardName12,OFFSET fortuneCardName13,OFFSET fortuneCardName14,OFFSET fortuneCardName15
|
||||
dd OFFSET fortuneCardName16,OFFSET fortuneCardName17,OFFSET fortuneCardName18,OFFSET fortuneCardName19
|
||||
dd OFFSET fortuneCardName20,OFFSET fortuneCardName21
|
||||
; Local, ephemeral identity fields dress a fictional crystal-ball lottery.
|
||||
fortuneFateField3D dd 0
|
||||
fortuneFateLengths3D dd 6 dup(0)
|
||||
fortuneFateFields3D db 384 dup(0)
|
||||
fortuneFateLowValue3D dd 9
|
||||
fortuneFateHighValue3D dd 10
|
||||
fortuneFateResult3D dd -1
|
||||
fortuneFateLimits3D dd 30,30,10,20,3,3
|
||||
fortuneFateXs3D dd 16,336,16,336,16,336
|
||||
fortuneFateYs3D dd 324,324,366,366,408,408
|
||||
fortuneFateLabels3D dd OFFSET fortuneFateName3D,OFFSET fortuneFateSurname3D,OFFSET fortuneFateBirth3D,OFFSET fortuneFateGroup3D
|
||||
dd OFFSET fortuneFateLow3D,OFFSET fortuneFateHigh3D
|
||||
fortuneFateMonthDays3D db 31,28,31,30,31,30,31,31,30,31,30,31
|
||||
fortuneFateNumber3D db 12 dup(0)
|
||||
fortuneOrbFrame3D dd 0
|
||||
fortuneOrbX3D dd 214
|
||||
fortuneOrbY3D dd 195
|
||||
fortuneOrbCenterX3D dd 0
|
||||
fortuneOrbCenterY3D dd 0
|
||||
include src_graphics\fortune_orb_art.inc
|
||||
fortuneFateName3D db 200,204,223,0
|
||||
fortuneFateSurname3D db 212,192,204,200,203,200,223,0
|
||||
fortuneFateBirth3D db 196,192,210,192,32,208,206,198,196,197,205,200,223,0
|
||||
fortuneFateGroup3D db 195,208,211,207,207,192,0
|
||||
fortuneFateLow3D db 206,210,0
|
||||
fortuneFateHigh3D db 196,206,0
|
||||
fortuneFateStart3D db 207,208,200,199,194,192,210,220,0
|
||||
fortuneFateEdit3D db 200,199,204,197,205,200,210,220,0
|
||||
fortuneFateHint3D db 196,196,46,204,204,46,195,195,195,195,0
|
||||
fortuneFateReady3D db 205,224,231,238,226,232,32,242,238,227,238,44,10,247,252,254,32,241,243,228,252,225,243,32,240,229,248,232,236,46,0
|
||||
fortuneFateRising3D db 210,232,248,229,46,10,216,224,240,32,239,240,238,225,243,230,228,224,229,242,241,255,46,0
|
||||
fortuneFateSwirl3D db 210,243,236,224,237,32,241,238,225,232,240,224,229,242,241,255,46,46,46,10,207,243,241,242,252,32,241,243,228,252,225,224,10,237,224,231,238,226,184,242,32,247,232,241,235,238,46,0
|
||||
fortuneFateReveal3D db 208,229,248,229,237,232,229,32,239,240,232,237,255,242,238,46,10,216,224,240,32,237,224,231,226,224,235,32,247,232,241,235,238,46,0
|
||||
fortuneFateRequired3D db 205,229,32,226,241,229,32,232,236,229,237,224,10,237,224,231,226,224,237,251,46,0
|
||||
fortuneFateDateError3D db 210,224,234,238,233,32,228,224,242,251,32,237,229,242,46,10,205,224,231,238,226,232,32,228,229,237,252,32,240,238,230,228,229,237,232,255,46,0
|
||||
fortuneFateRangeError3D db 205,224,231,238,226,232,32,227,240,224,237,232,246,251,58,10
|
||||
db 238,242,32,236,229,237,252,248,229,227,238,32,234,32,225,238,235,252,248,229,236,243,44,10
|
||||
db 238,242,32,48,32,228,238,32,49,48,48,46,0
|
||||
fortuneFateCaption3D db 208,197,216,197,205,200,197,32,209,211,196,220,193,219,0
|
||||
fortuneFatePlay3D db 200,195,208,206,194,206,201,32,208,200,210,211,192,203,0
|
||||
.data?
|
||||
fortuneBackground3D dd SCREEN_SIZE dup(?)
|
||||
.data
|
||||
|
|
|
|||
3
src_graphics/fortune_orb_art.inc
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
; Generated by tools/generators/pack_fortune_orb.py; glass only, pedestal excluded.
|
||||
fortuneOrbAnchorX3D dd 18261,16299,16299,14336,18176,16213,16213,14336,18176,16299,16299,14421,18091,16384,16128,14507
|
||||
fortuneOrbAnchorY3D dd 14610,14637,14628,14630,14030,14040,14055,14066,13948,13933,13940,13922,12327,12342,12305,12324
|
||||
|
|
@ -169,4 +169,5 @@ include src_graphics\hall_expansion_data.inc
|
|||
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
|
||||
.code
|
||||
|
|
|
|||
|
|
@ -194,6 +194,9 @@ interior_begin_finish:
|
|||
BeginWalkableStairs3D ENDP
|
||||
|
||||
UpdateInteriorTravel3D PROC
|
||||
call UpdateRoof3D
|
||||
cmp hallLevel3D,HALL_ROOF
|
||||
je interior_travel_done
|
||||
call UpdateUpperEffects3D
|
||||
call UpdateBasementDoor3D
|
||||
call UpdateFortunePassage3D
|
||||
|
|
@ -410,6 +413,11 @@ UpdateInteriorEye3D PROC
|
|||
je UpdateStairHeight3D
|
||||
cmp engineMode,MODE_WALK
|
||||
jne interior_eye_done
|
||||
cmp hallLevel3D,HALL_ROOF
|
||||
jne @F
|
||||
call UpdateRoofEye3D
|
||||
ret
|
||||
@@:
|
||||
mov viewHorizon3D,HALF_H
|
||||
mov viewEye3D,HALL_STANDING_EYE
|
||||
mov viewCeiling3D,FP_1*5/2
|
||||
|
|
|
|||
|
|
@ -2,11 +2,17 @@
|
|||
RenderCeilingAndFloor PROC uses ebx esi edi
|
||||
local row:DWORD,distance:DWORD,sx:DWORD,sy:DWORD,wx:DWORD,wy:DWORD
|
||||
local material:DWORD,shade:DWORD
|
||||
cmp hallLevel3D,HALL_ROOF
|
||||
jne @F
|
||||
call DrawRoofSky3D
|
||||
@@:
|
||||
mov row, 0
|
||||
hall_plane_row:
|
||||
mov eax, row
|
||||
cmp eax, viewHorizon3D
|
||||
jge hall_plane_floor
|
||||
cmp hallLevel3D,HALL_ROOF
|
||||
je hall_plane_next_row
|
||||
cmp outdoorRender3D, 0
|
||||
jne hall_plane_next_row
|
||||
mov ecx, viewHorizon3D
|
||||
|
|
@ -96,7 +102,18 @@ hall_plane_distance:
|
|||
imul eax, SCREEN_W*4
|
||||
lea edi, screenBuffer[eax]
|
||||
mov esi, SCREEN_W
|
||||
hall_plane_pixel:
|
||||
hall_plane_pixel:
|
||||
cmp hallLevel3D,HALL_ROOF
|
||||
jne roof_floor_inside
|
||||
cmp wx,FP_1*109/10
|
||||
jl roof_floor_outside
|
||||
cmp wx,FP_1*291/10
|
||||
jg roof_floor_outside
|
||||
cmp wy,FP_1*9/10
|
||||
jl roof_floor_outside
|
||||
cmp wy,FP_1*211/10
|
||||
jg roof_floor_outside
|
||||
roof_floor_inside:
|
||||
mov ebx, material
|
||||
cmp outdoorRender3D, 0
|
||||
je hall_plane_sample
|
||||
|
|
@ -129,7 +146,13 @@ hall_plane_sample:
|
|||
jl hall_plane_choose_hd
|
||||
hall_plane_wood:
|
||||
mov ebx, 85
|
||||
hall_plane_choose_hd:
|
||||
hall_plane_choose_hd:
|
||||
cmp ebx,ROOF_DECK
|
||||
jne hall_plane_not_roof
|
||||
mov ecx,roofMaterialArt3D.pixels
|
||||
test ecx,ecx
|
||||
jnz hall_plane_hd_coordinates
|
||||
hall_plane_not_roof:
|
||||
cmp ebx,132
|
||||
jb hall_plane_other_hd
|
||||
cmp ebx,135
|
||||
|
|
@ -237,6 +260,9 @@ hall_plane_next_row:
|
|||
cmp row, SCREEN_H
|
||||
jb hall_plane_row
|
||||
ret
|
||||
roof_floor_outside:
|
||||
mov eax,[edi]
|
||||
jmp hall_plane_store
|
||||
RenderCeilingAndFloor ENDP
|
||||
|
||||
RenderSprites PROC uses ebx esi edi
|
||||
|
|
@ -330,7 +356,8 @@ hall_sprites_done:
|
|||
ret
|
||||
RenderSprites ENDP
|
||||
|
||||
InitHall3D PROC uses esi edi
|
||||
InitHall3D PROC uses esi edi
|
||||
call InitRoof3D
|
||||
lea esi, hallLayout3D
|
||||
lea edi, casinoMap
|
||||
mov ecx, 256
|
||||
|
|
@ -370,7 +397,8 @@ init_plane_done:
|
|||
ret
|
||||
InitHall3D ENDP
|
||||
|
||||
LoadHallAssets3D PROC
|
||||
LoadHallAssets3D PROC
|
||||
call LoadRoofAssets3D
|
||||
call InitUpperSurfaces3D
|
||||
call LoadFortuneArt3D
|
||||
invoke LoadAnimPack3D, OFFSET upperMaterialArt3D, OFFSET upperMaterialPath3D
|
||||
|
|
@ -469,7 +497,8 @@ hall_load_sign:
|
|||
ret
|
||||
LoadHallAssets3D ENDP
|
||||
|
||||
FreeHallAssets3D PROC
|
||||
FreeHallAssets3D PROC
|
||||
call FreeRoofAssets3D
|
||||
call FreeFortuneArt3D
|
||||
invoke FreeAnimPack3D, OFFSET upperMaterialArt3D
|
||||
invoke FreeAnimPack3D,OFFSET upperGamesArt3D
|
||||
|
|
@ -574,6 +603,11 @@ surface_missing:
|
|||
ResolveWorldSurface3D ENDP
|
||||
|
||||
RenderHall3D PROC uses ebx esi
|
||||
cmp hallLevel3D,HALL_ROOF
|
||||
jne @F
|
||||
call RenderRoof3D
|
||||
ret
|
||||
@@:
|
||||
cmp engineMode,MODE_STAIRS
|
||||
je fortune_hall_stairs
|
||||
cmp hallLevel3D,3
|
||||
|
|
@ -694,6 +728,7 @@ hall_render_lounge:
|
|||
call RenderHallDressing3D
|
||||
call RenderHallPortals3D
|
||||
call RenderFortuneEntrance3D
|
||||
call RenderLift3D
|
||||
call ApplyHallLighting3D
|
||||
call RenderInteriorViews3D
|
||||
call DrawBasementLeaf3D
|
||||
|
|
@ -759,7 +794,12 @@ hall_target_done:
|
|||
ret
|
||||
HallTarget3D ENDP
|
||||
|
||||
CheckHallInteractions3D PROC
|
||||
CheckHallInteractions3D PROC
|
||||
cmp hallLevel3D,HALL_ROOF
|
||||
jne @F
|
||||
call CheckRoofInteractions3D
|
||||
ret
|
||||
@@:
|
||||
cmp hallLevel3D,3
|
||||
jne @F
|
||||
call CheckFortuneInteractions3D
|
||||
|
|
@ -790,6 +830,7 @@ hall_check_other:
|
|||
invoke HallTarget3D,FP_1*305/10,21*FP_1,0,OFFSET upperStairPrompt3D
|
||||
call CheckNpcInteractions3D
|
||||
call CheckFortuneInteractions3D
|
||||
call CheckRoofInteractions3D
|
||||
ret
|
||||
hall_check_bunker:
|
||||
mov interactTargetId, 0
|
||||
|
|
@ -805,6 +846,9 @@ TriggerHallInteraction3D PROC
|
|||
mov eax,1
|
||||
ret
|
||||
@@:
|
||||
call TryRoofInteraction3D
|
||||
test eax,eax
|
||||
jnz hall_trigger_handled
|
||||
cmp interactTargetId,INTERACT_DARTS
|
||||
jne hall_not_darts
|
||||
call BeginDarts3D
|
||||
|
|
@ -978,6 +1022,11 @@ RenderBar3D PROC uses ebx
|
|||
jne @F
|
||||
mov playerX,FP_1*25/2
|
||||
mov playerY,16*FP_1+FP_1/3
|
||||
@@:
|
||||
cmp hallLevel3D,HALL_ROOF
|
||||
jne @F
|
||||
mov playerX,15*FP_1
|
||||
mov playerY,FP_1*63/10
|
||||
@@:
|
||||
mov viewHorizon3D, HALF_H
|
||||
mov viewEye3D, HALL_STANDING_EYE
|
||||
|
|
@ -1007,6 +1056,10 @@ bar_draw_title:
|
|||
cmp hallLevel3D,2
|
||||
jne @F
|
||||
mov eax,OFFSET upperBarName3D
|
||||
@@:
|
||||
cmp hallLevel3D,HALL_ROOF
|
||||
jne @F
|
||||
mov eax,OFFSET roofBarName3D
|
||||
@@:
|
||||
invoke DrawString8x8, 18,15,eax,00E7CA88h
|
||||
cmp barPhase3D, 2
|
||||
|
|
@ -1094,7 +1147,15 @@ city_world_guard_right:
|
|||
ret
|
||||
RenderCityGuards3D ENDP
|
||||
|
||||
CanWalkHall3D PROC uses esi ebx px:DWORD,py:DWORD
|
||||
CanWalkHall3D PROC uses esi ebx px:DWORD,py:DWORD
|
||||
cmp hallLevel3D,HALL_ROOF
|
||||
jne @F
|
||||
invoke RoofCanWalk3D,px,py
|
||||
ret
|
||||
@@:
|
||||
invoke LiftCanWalk3D,px,py
|
||||
test eax,eax
|
||||
jz hall_collision_blocked
|
||||
cmp engineMode,MODE_STAIRS
|
||||
je fortune_collision_stairs
|
||||
cmp hallLevel3D,3
|
||||
|
|
@ -1404,6 +1465,7 @@ RenderPatrons3D ENDP
|
|||
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\steel_door.inc
|
||||
include src_graphics\interior_portals.inc
|
||||
include src_graphics\fortune_room.inc
|
||||
|
|
|
|||
|
|
@ -3,15 +3,15 @@
|
|||
; ==============================================================================
|
||||
|
||||
MODE_JACKBOX equ 28
|
||||
INTERACT_JACKBOX equ 48
|
||||
INTERACT_JACKBOX equ 52
|
||||
|
||||
JACKBOX_X equ FP_1*306/10
|
||||
JACKBOX_Y equ FP_1*235/10
|
||||
JACKBOX_TRACK_COUNT equ 3
|
||||
|
||||
MATERIAL_JACKBOX_FRONT equ 161
|
||||
MATERIAL_JACKBOX_FRONT equ 211
|
||||
MATERIAL_JACKBOX_SIDE equ 121
|
||||
MATERIAL_JACKBOX_BACK equ 163
|
||||
MATERIAL_JACKBOX_BACK equ 213
|
||||
|
||||
LoadJackboxArt3D PROTO
|
||||
FreeJackboxArt3D PROTO
|
||||
|
|
|
|||
|
|
@ -431,6 +431,9 @@ present_geometry_ready:
|
|||
jz @F
|
||||
invoke PatBlt, h3DDC, eax, 0, ecx, edi, 42h
|
||||
@@:
|
||||
invoke PresentRoofHD3D,h3DDC,offsetX,offsetY,viewW,viewH
|
||||
test eax,eax
|
||||
jnz present_done
|
||||
invoke StretchDIBits, h3DDC, offsetX, offsetY, viewW, viewH, 0, 0, SCREEN_W, SCREEN_H, OFFSET screenBuffer, OFFSET bmi3D, 0, SRCCOPY
|
||||
present_done:
|
||||
ret
|
||||
|
|
|
|||
1178
src_graphics/rooftop.inc
Normal file
382
src_graphics/rooftop_atmosphere.inc
Normal file
|
|
@ -0,0 +1,382 @@
|
|||
; Directional dusk lighting and aerial perspective are applied to actual geometry.
|
||||
ShadeRoofCityPixel3D PROC uses ebx esi edi color:DWORD,depth:DWORD,light:DWORD,material:DWORD
|
||||
local fog:DWORD,inverseFog:DWORD,red:DWORD,green:DWORD,blue:DWORD,level:DWORD
|
||||
local targetR:DWORD,targetG:DWORD,targetB:DWORD
|
||||
mov roofPixelGlow3D,0
|
||||
mov eax,depth
|
||||
sar eax,16
|
||||
test eax,eax
|
||||
jns @F
|
||||
xor eax,eax
|
||||
@@:
|
||||
cmp eax,1023
|
||||
jbe @F
|
||||
mov eax,1023
|
||||
@@:
|
||||
movzx eax,roofFog3D[eax]
|
||||
mov fog,eax
|
||||
neg eax
|
||||
add eax,256
|
||||
mov inverseFog,eax
|
||||
mov eax,color
|
||||
and eax,255
|
||||
mov blue,eax
|
||||
mov eax,color
|
||||
shr eax,8
|
||||
and eax,255
|
||||
mov green,eax
|
||||
mov eax,color
|
||||
shr eax,16
|
||||
and eax,255
|
||||
mov red,eax
|
||||
mov targetR,171
|
||||
mov targetG,157
|
||||
mov targetB,177
|
||||
mov eax,light
|
||||
mov level,eax
|
||||
cmp roofNight3D,0
|
||||
je roof_lighting_day
|
||||
mov targetR,13
|
||||
mov targetG,24
|
||||
mov targetB,44
|
||||
mov level,72
|
||||
cmp material,ROOF_CITY_ROAD
|
||||
jne @F
|
||||
mov level,256
|
||||
@@:
|
||||
cmp material,ROOF_LIGHT
|
||||
jne @F
|
||||
mov level,256
|
||||
mov roofPixelGlow3D,96
|
||||
@@:
|
||||
cmp roofLandmarkLight3D,0
|
||||
je @F
|
||||
mov level,148
|
||||
@@:
|
||||
cmp material,ROOF_BRASS
|
||||
jne @F
|
||||
mov level,228
|
||||
cmp roofLandmarkLight3D,0
|
||||
je @F
|
||||
mov roofPixelGlow3D,12
|
||||
@@:
|
||||
; Emission is tagged in the actual window pixels, never inferred from gold trim.
|
||||
cmp material,ROOF_FACADE
|
||||
jb roof_lighting_apply
|
||||
cmp material,ROOF_FACADE+7
|
||||
jbe roof_lighting_window
|
||||
cmp material,ROOF_MODERN_FACADE
|
||||
jb roof_lighting_apply
|
||||
cmp material,ROOF_MODERN_FACADE+7
|
||||
ja roof_lighting_apply
|
||||
roof_lighting_window:
|
||||
mov eax,color
|
||||
shr eax,24
|
||||
cmp eax,255
|
||||
jne roof_lighting_apply
|
||||
mov level,310
|
||||
mov roofPixelGlow3D,64
|
||||
shr fog,1
|
||||
mov eax,fog
|
||||
neg eax
|
||||
add eax,256
|
||||
mov inverseFog,eax
|
||||
jmp roof_lighting_apply
|
||||
roof_lighting_day:
|
||||
cmp material,ROOF_CITY_ROOF
|
||||
jne @F
|
||||
imul eax,red,180
|
||||
shr eax,8
|
||||
mov red,eax
|
||||
imul eax,green,220
|
||||
shr eax,8
|
||||
mov green,eax
|
||||
@@:
|
||||
mov eax,red
|
||||
imul eax,263
|
||||
shr eax,8
|
||||
cmp eax,255
|
||||
jbe @F
|
||||
mov eax,255
|
||||
@@:
|
||||
mov red,eax
|
||||
imul eax,blue,235
|
||||
shr eax,8
|
||||
mov blue,eax
|
||||
roof_lighting_apply:
|
||||
mov eax,red
|
||||
imul eax,level
|
||||
shr eax,8
|
||||
cmp eax,255
|
||||
jbe @F
|
||||
mov eax,255
|
||||
@@:
|
||||
imul eax,inverseFog
|
||||
mov ecx,targetR
|
||||
imul ecx,fog
|
||||
add eax,ecx
|
||||
shr eax,8
|
||||
mov ebx,eax
|
||||
shl ebx,16
|
||||
mov eax,green
|
||||
imul eax,level
|
||||
shr eax,8
|
||||
cmp eax,255
|
||||
jbe @F
|
||||
mov eax,255
|
||||
@@:
|
||||
imul eax,inverseFog
|
||||
mov ecx,targetG
|
||||
imul ecx,fog
|
||||
add eax,ecx
|
||||
shr eax,8
|
||||
shl eax,8
|
||||
or ebx,eax
|
||||
mov eax,blue
|
||||
imul eax,level
|
||||
shr eax,8
|
||||
cmp eax,255
|
||||
jbe @F
|
||||
mov eax,255
|
||||
@@:
|
||||
imul eax,inverseFog
|
||||
mov ecx,targetB
|
||||
imul ecx,fog
|
||||
add eax,ecx
|
||||
shr eax,8
|
||||
or eax,ebx
|
||||
ret
|
||||
ShadeRoofCityPixel3D ENDP
|
||||
|
||||
SampleRoofGroundLight3D PROC uses ebx esi edi color:DWORD,wx:DWORD,wy:DWORD
|
||||
mov eax,color
|
||||
cmp roofLightingArt3D.pixels,0
|
||||
je roof_ground_light_done
|
||||
mov eax,wx
|
||||
sar eax,17
|
||||
add eax,256
|
||||
cmp eax,511
|
||||
ja roof_ground_light_original
|
||||
mov ebx,eax
|
||||
mov eax,wy
|
||||
sar eax,17
|
||||
add eax,256
|
||||
cmp eax,511
|
||||
ja roof_ground_light_original
|
||||
shl eax,9
|
||||
add eax,ebx
|
||||
mov esi,roofLightingArt3D.pixels
|
||||
cmp roofNight3D,0
|
||||
je @F
|
||||
add esi,512*512*4
|
||||
@@:
|
||||
mov edx,[esi+eax*4]
|
||||
mov edi,edx
|
||||
and edx,255
|
||||
mov eax,color
|
||||
and eax,255
|
||||
imul eax,edx
|
||||
shr eax,8
|
||||
mov ebx,eax
|
||||
mov edx,edi
|
||||
shr edx,8
|
||||
and edx,255
|
||||
mov eax,color
|
||||
shr eax,8
|
||||
and eax,255
|
||||
imul eax,edx
|
||||
and eax,0FF00h
|
||||
or ebx,eax
|
||||
mov edx,edi
|
||||
shr edx,16
|
||||
and edx,255
|
||||
mov eax,color
|
||||
shr eax,16
|
||||
and eax,255
|
||||
imul eax,edx
|
||||
and eax,0FF00h
|
||||
shl eax,8
|
||||
or eax,ebx
|
||||
ret
|
||||
roof_ground_light_original:
|
||||
mov eax,color
|
||||
roof_ground_light_done:
|
||||
ret
|
||||
SampleRoofGroundLight3D ENDP
|
||||
|
||||
PrepareRoofCityAA3D PROC uses ebx esi edi
|
||||
mov roofAAStill3D,0
|
||||
cmp roofAAValid3D,0
|
||||
je roof_aa_moving
|
||||
mov eax,playerX
|
||||
cmp eax,roofAAX3D
|
||||
jne roof_aa_moving
|
||||
mov eax,playerY
|
||||
cmp eax,roofAAY3D
|
||||
jne roof_aa_moving
|
||||
mov eax,playerAngle
|
||||
cmp eax,roofAAAngle3D
|
||||
jne roof_aa_moving
|
||||
mov eax,viewEye3D
|
||||
cmp eax,roofAAEye3D
|
||||
jne roof_aa_moving
|
||||
mov eax,viewHorizon3D
|
||||
cmp eax,roofAAHorizon3D
|
||||
jne roof_aa_moving
|
||||
mov eax,roofNight3D
|
||||
cmp eax,roofAATheme3D
|
||||
jne roof_aa_moving
|
||||
mov roofAAStill3D,1
|
||||
inc roofAAFrame3D
|
||||
and roofAAFrame3D,3
|
||||
jmp roof_aa_save_view
|
||||
roof_aa_moving:
|
||||
mov roofAAFrame3D,0
|
||||
roof_aa_save_view:
|
||||
mov eax,playerX
|
||||
mov roofAAX3D,eax
|
||||
mov eax,playerY
|
||||
mov roofAAY3D,eax
|
||||
mov eax,playerAngle
|
||||
mov roofAAAngle3D,eax
|
||||
mov eax,viewEye3D
|
||||
mov roofAAEye3D,eax
|
||||
mov eax,viewHorizon3D
|
||||
mov roofAAHorizon3D,eax
|
||||
mov eax,roofNight3D
|
||||
mov roofAATheme3D,eax
|
||||
mov eax,roofAAFrame3D
|
||||
shl eax,1
|
||||
sub eax,3
|
||||
mov ebx,eax
|
||||
mov eax,playerPlaneX
|
||||
imul ebx
|
||||
cdq
|
||||
mov ecx,SCREEN_W*4
|
||||
idiv ecx
|
||||
mov esi,eax
|
||||
mov eax,playerPlaneY
|
||||
imul ebx
|
||||
cdq
|
||||
idiv ecx
|
||||
mov edi,eax
|
||||
xor ebx,ebx
|
||||
roof_aa_shift_ray:
|
||||
add cityRayX3D[ebx*4],esi
|
||||
add cityRayY3D[ebx*4],edi
|
||||
inc ebx
|
||||
cmp ebx,SCREEN_W
|
||||
jb roof_aa_shift_ray
|
||||
ret
|
||||
PrepareRoofCityAA3D ENDP
|
||||
|
||||
FinishRoofCityAA3D PROC uses ebx esi edi
|
||||
xor edi,edi
|
||||
roof_aa_pixel:
|
||||
mov eax,screenBuffer[edi*4]
|
||||
cmp roofAAStill3D,0
|
||||
je roof_aa_store
|
||||
cmp roofCityMask3D[edi],1
|
||||
jne roof_aa_store
|
||||
and eax,00FCFCFCh
|
||||
shr eax,2
|
||||
mov edx,roofAAHistory3D[edi*4]
|
||||
and edx,00FCFCFCh
|
||||
shr edx,2
|
||||
imul edx,3
|
||||
add eax,edx
|
||||
mov screenBuffer[edi*4],eax
|
||||
roof_aa_store:
|
||||
mov roofAAHistory3D[edi*4],eax
|
||||
inc edi
|
||||
cmp edi,SCREEN_SIZE
|
||||
jb roof_aa_pixel
|
||||
mov roofAAValid3D,1
|
||||
ret
|
||||
FinishRoofCityAA3D ENDP
|
||||
|
||||
RenderRoofCityBloom3D PROC uses ebx esi edi
|
||||
local pixel:DWORD,color:DWORD,amount:DWORD,rowOffset:DWORD,columnOffset:DWORD
|
||||
local target:DWORD,red:DWORD,green:DWORD,blue:DWORD
|
||||
cmp roofNight3D,0
|
||||
je roof_bloom_done
|
||||
mov pixel,SCREEN_W+1
|
||||
roof_bloom_source:
|
||||
mov esi,pixel
|
||||
movzx eax,roofGlowMask3D[esi]
|
||||
test eax,eax
|
||||
jz roof_bloom_next_source
|
||||
shr eax,2
|
||||
mov amount,eax
|
||||
mov eax,screenBuffer[esi*4]
|
||||
mov color,eax
|
||||
mov rowOffset,-1
|
||||
roof_bloom_row:
|
||||
mov columnOffset,-1
|
||||
roof_bloom_column:
|
||||
mov eax,rowOffset
|
||||
imul eax,SCREEN_W
|
||||
add eax,columnOffset
|
||||
add eax,pixel
|
||||
cmp eax,SCREEN_SIZE
|
||||
jae roof_bloom_next_column
|
||||
mov target,eax
|
||||
lea edi,screenBuffer[eax*4]
|
||||
mov ebx,[edi]
|
||||
mov eax,color
|
||||
and eax,255
|
||||
imul eax,amount
|
||||
shr eax,8
|
||||
movzx ecx,bl
|
||||
add eax,ecx
|
||||
cmp eax,255
|
||||
jbe @F
|
||||
mov eax,255
|
||||
@@:
|
||||
mov blue,eax
|
||||
mov eax,color
|
||||
shr eax,8
|
||||
and eax,255
|
||||
imul eax,amount
|
||||
shr eax,8
|
||||
mov ecx,ebx
|
||||
shr ecx,8
|
||||
and ecx,255
|
||||
add eax,ecx
|
||||
cmp eax,255
|
||||
jbe @F
|
||||
mov eax,255
|
||||
@@:
|
||||
mov green,eax
|
||||
mov eax,color
|
||||
shr eax,16
|
||||
and eax,255
|
||||
imul eax,amount
|
||||
shr eax,8
|
||||
shr ebx,16
|
||||
and ebx,255
|
||||
add eax,ebx
|
||||
cmp eax,255
|
||||
jbe @F
|
||||
mov eax,255
|
||||
@@:
|
||||
shl eax,16
|
||||
mov ecx,green
|
||||
shl ecx,8
|
||||
or eax,ecx
|
||||
or eax,blue
|
||||
mov [edi],eax
|
||||
roof_bloom_next_column:
|
||||
inc columnOffset
|
||||
cmp columnOffset,1
|
||||
jle roof_bloom_column
|
||||
inc rowOffset
|
||||
cmp rowOffset,1
|
||||
jle roof_bloom_row
|
||||
roof_bloom_next_source:
|
||||
inc pixel
|
||||
cmp pixel,SCREEN_SIZE-SCREEN_W-1
|
||||
jb roof_bloom_source
|
||||
roof_bloom_done:
|
||||
ret
|
||||
RenderRoofCityBloom3D ENDP
|
||||
156
src_graphics/rooftop_city_life.inc
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
RenderRoofCityLamps3D PROC uses ebx esi edi
|
||||
local px:DWORD,py:DWORD,x1:DWORD,y1:DWORD
|
||||
xor esi,esi
|
||||
roof_city_lamp:
|
||||
mov eax,roofCityLamps3D[esi*8]
|
||||
mov px,eax
|
||||
mov edx,roofCityLamps3D[esi*8+4]
|
||||
mov py,edx
|
||||
invoke ProjectCityPoint3D,eax,edx
|
||||
cmp edx,FP_1
|
||||
jle roof_city_lamp_next
|
||||
cmp edx,180*FP_1
|
||||
ja roof_city_lamp_next
|
||||
cmp eax,-8
|
||||
jl roof_city_lamp_next
|
||||
cmp eax,SCREEN_W+8
|
||||
jg roof_city_lamp_next
|
||||
mov eax,px
|
||||
add eax,FP_1/12
|
||||
mov x1,eax
|
||||
mov eax,py
|
||||
add eax,FP_1/12
|
||||
mov y1,eax
|
||||
invoke DrawRoofSolid3D,px,x1,py,y1,ROOF_CITY_STREET_Z,ROOF_CITY_STREET_Z+5*FP_1,ROOF_METAL
|
||||
mov eax,px
|
||||
sub eax,FP_1/5
|
||||
mov edx,px
|
||||
add edx,FP_1/5
|
||||
mov ecx,py
|
||||
sub ecx,FP_1/5
|
||||
mov edi,py
|
||||
add edi,FP_1/5
|
||||
invoke DrawRoofSolid3D,eax,edx,ecx,edi,ROOF_CITY_STREET_Z+FP_1*49/10,ROOF_CITY_STREET_Z+FP_1*51/10,ROOF_LIGHT
|
||||
roof_city_lamp_next:
|
||||
inc esi
|
||||
cmp esi,ROOF_CITY_LAMP_COUNT
|
||||
jb roof_city_lamp
|
||||
ret
|
||||
RenderRoofCityLamps3D ENDP
|
||||
|
||||
DrawRoofCar3D PROC uses ebx esi edi centreX:DWORD,centreY:DWORD,axis:DWORD,style:DWORD
|
||||
local x0:DWORD,x1:DWORD,y0:DWORD,y1:DWORD,bodyMat:DWORD,cabinX0:DWORD,cabinX1:DWORD
|
||||
local cabinY0:DWORD,cabinY1:DWORD
|
||||
invoke ProjectCityPoint3D,centreX,centreY
|
||||
cmp edx,FP_1
|
||||
jle roof_car_done
|
||||
cmp eax,-30
|
||||
jl roof_car_done
|
||||
cmp eax,SCREEN_W+30
|
||||
jg roof_car_done
|
||||
mov ebx,FP_1*19/10
|
||||
mov ecx,FP_1*8/10
|
||||
cmp axis,0
|
||||
je @F
|
||||
xchg ebx,ecx
|
||||
@@:
|
||||
mov eax,centreX
|
||||
sub eax,ebx
|
||||
mov x0,eax
|
||||
mov eax,centreX
|
||||
add eax,ebx
|
||||
mov x1,eax
|
||||
mov eax,centreY
|
||||
sub eax,ecx
|
||||
mov y0,eax
|
||||
mov eax,centreY
|
||||
add eax,ecx
|
||||
mov y1,eax
|
||||
mov bodyMat,ROOF_CITY_ROOF
|
||||
test style,1
|
||||
jz @F
|
||||
mov bodyMat,ROOF_BRASS
|
||||
@@:
|
||||
invoke DrawRoofSolid3D,x0,x1,y0,y1,ROOF_CITY_STREET_Z+FP_1/4,ROOF_CITY_STREET_Z+FP_1*85/100,bodyMat
|
||||
mov ebx,FP_1*9/10
|
||||
mov ecx,FP_1*65/100
|
||||
cmp axis,0
|
||||
je @F
|
||||
xchg ebx,ecx
|
||||
@@:
|
||||
mov eax,centreX
|
||||
sub eax,ebx
|
||||
mov cabinX0,eax
|
||||
mov eax,centreX
|
||||
add eax,ebx
|
||||
mov cabinX1,eax
|
||||
mov eax,centreY
|
||||
sub eax,ecx
|
||||
mov cabinY0,eax
|
||||
mov eax,centreY
|
||||
add eax,ecx
|
||||
mov cabinY1,eax
|
||||
invoke DrawRoofSolid3D,cabinX0,cabinX1,cabinY0,cabinY1,ROOF_CITY_STREET_Z+FP_1*85/100,ROOF_CITY_STREET_Z+FP_1*135/100,ROOF_MODERN_FACADE+1
|
||||
roof_car_done:
|
||||
ret
|
||||
DrawRoofCar3D ENDP
|
||||
|
||||
RenderRoofTraffic3D PROC uses ebx esi edi
|
||||
local px:DWORD,py:DWORD,material:DWORD
|
||||
xor esi,esi
|
||||
roof_city_car:
|
||||
mov eax,esi
|
||||
imul eax,17000
|
||||
add eax,animationClock3D
|
||||
mov ecx,393
|
||||
mul ecx
|
||||
mov ecx,340*FP_1
|
||||
div ecx
|
||||
sub edx,160*FP_1
|
||||
mov px,edx
|
||||
mov py,-FP_1*59/10
|
||||
test esi,1
|
||||
jz @F
|
||||
neg edx
|
||||
mov px,edx
|
||||
mov py,-FP_1*81/10
|
||||
@@:
|
||||
cmp esi,4
|
||||
jb roof_city_car_x
|
||||
mov eax,px
|
||||
mov py,eax
|
||||
mov px,FP_1*391/10
|
||||
test esi,1
|
||||
jz @F
|
||||
mov px,FP_1*369/10
|
||||
@@:
|
||||
invoke DrawRoofCar3D,px,py,1,esi
|
||||
jmp roof_city_car_next
|
||||
roof_city_car_x:
|
||||
invoke DrawRoofCar3D,px,py,0,esi
|
||||
roof_city_car_next:
|
||||
inc esi
|
||||
cmp esi,8
|
||||
jb roof_city_car
|
||||
xor esi,esi
|
||||
roof_city_boat:
|
||||
imul eax,esi,47000
|
||||
add eax,animationClock3D
|
||||
mov ecx,98
|
||||
mul ecx
|
||||
mov ecx,340*FP_1
|
||||
div ecx
|
||||
sub edx,160*FP_1
|
||||
mov px,edx
|
||||
mov py,68*FP_1
|
||||
invoke DrawRoofEllipsoid3D,px,py,ROOF_CITY_STREET_Z-FP_1*16/10,3*FP_1,FP_1,FP_1/2,ROOF_CITY_ROOF
|
||||
mov eax,px
|
||||
sub eax,FP_1
|
||||
mov ebx,px
|
||||
add ebx,FP_1
|
||||
invoke DrawRoofSolid3D,eax,ebx,FP_1*674/10,FP_1*686/10,ROOF_CITY_STREET_Z-FP_1*14/10,ROOF_CITY_STREET_Z-FP_1*7/10,ROOF_PLASTER
|
||||
inc esi
|
||||
cmp esi,3
|
||||
jb roof_city_boat
|
||||
ret
|
||||
RenderRoofTraffic3D ENDP
|
||||
202
src_graphics/rooftop_data.inc
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
; Native walkable rooftop and elevator share the existing hall engine.
|
||||
HALL_ROOF equ 4
|
||||
INTERACT_LIFT_CALL equ 48
|
||||
INTERACT_LIFT_TRAVEL equ 49
|
||||
INTERACT_ROOF_SEAT equ 50
|
||||
INTERACT_ROOF_RAIL equ 51
|
||||
ROOF_STANDING_EYE equ FP_1*165/100
|
||||
ROOF_SEATED_EYE equ FP_1*118/100
|
||||
ROOF_RAIL_HEIGHT equ FP_1*102/100
|
||||
ROOF_FAR_DEPTH equ FP_1*1024
|
||||
ROOF_FACADE equ 186
|
||||
ROOF_FOLIAGE equ 194
|
||||
ROOF_EARTH equ 195
|
||||
ROOF_CITY_ROOF equ 196
|
||||
ROOF_CITY_ROAD equ 197
|
||||
ROOF_CITY_WATER equ 198
|
||||
ROOF_MODERN_FACADE equ 199
|
||||
ROOF_CITY_LAWN equ 207
|
||||
ROOF_LAST_MATERIAL equ 207
|
||||
ROOF_CITY_STREET_Z equ -60*FP_1
|
||||
ROOF_DECK equ 161
|
||||
ROOF_STONE equ 162
|
||||
ROOF_TEAL equ 163
|
||||
ROOF_IVORY equ 164
|
||||
ROOF_METAL equ 165
|
||||
ROOF_BRASS equ 166
|
||||
ROOF_MARBLE equ 167
|
||||
ROOF_PLASTER equ 168
|
||||
ROOF_DOOR equ 169
|
||||
ROOF_BUTTON equ 170
|
||||
ROOF_BAR_FRONT equ 171
|
||||
ROOF_BOTTLES equ 172
|
||||
ROOF_TREE equ 173
|
||||
ROOF_GRASS equ 174
|
||||
ROOF_LANTERN equ 175
|
||||
ROOF_TABLE equ 176
|
||||
ROOF_INDICATOR equ 177
|
||||
ROOF_LIGHT equ 178
|
||||
ROOF_SOFA_FRONT equ 179
|
||||
ROOF_SOFA_SIDE equ 180
|
||||
ROOF_SOFA_BACK equ 181
|
||||
ROOF_CHAIR_FRONT equ 182
|
||||
ROOF_CHAIR_SIDE equ 183
|
||||
ROOF_CHAIR_BACK equ 184
|
||||
ROOF_BAR_STOOL equ 185
|
||||
ROOF_SEAT_COUNT equ 13
|
||||
ROOF_TREE_COUNT equ 8
|
||||
ROOF_GUEST_COUNT equ 8
|
||||
LIFT_CENTRE_X equ FP_1*244/10
|
||||
LIFT_FRONT_Y equ FP_1*345/100
|
||||
InitRoof3D PROTO
|
||||
LoadRoofAssets3D PROTO
|
||||
FreeRoofAssets3D PROTO
|
||||
ResolveRoofSurface3D PROTO :DWORD
|
||||
DrawRoofSky3D PROTO
|
||||
RenderRoof3D PROTO
|
||||
RenderLift3D PROTO
|
||||
UpdateRoof3D PROTO
|
||||
CheckRoofInteractions3D PROTO
|
||||
TryRoofInteraction3D PROTO
|
||||
LiftCanWalk3D PROTO :DWORD,:DWORD
|
||||
RoofCanWalk3D PROTO :DWORD,:DWORD
|
||||
UpdateRoofEye3D PROTO
|
||||
UpdateRoofLook3D PROTO
|
||||
StandFromRoofSeat3D PROTO
|
||||
DrawRoofSofa3D PROTO :DWORD,:DWORD
|
||||
DrawRoofChair3D PROTO :DWORD,:DWORD,:DWORD
|
||||
DrawRoofCoffee3D PROTO :DWORD,:DWORD
|
||||
DrawRoofTree3D PROTO :DWORD,:DWORD,:DWORD
|
||||
DrawRoofBulb3D PROTO :DWORD,:DWORD,:DWORD
|
||||
UpdateRoofIndicator3D PROTO
|
||||
DrawRoofSolid3D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
|
||||
DrawRoofUpholstery3D PROTO :DWORD,:DWORD,:DWORD
|
||||
DrawRoofPlanter3D PROTO :DWORD,:DWORD,:DWORD,:DWORD
|
||||
RenderRoofDistrict3D PROTO
|
||||
UpdateRoofGuests3D PROTO :DWORD
|
||||
CheckRoofRail3D PROTO
|
||||
StartRoofLean3D PROTO
|
||||
RenderRoofLean3D PROTO
|
||||
DrawRoofHandsPlane3D PROTO :DWORD,:DWORD,:DWORD
|
||||
ShadeRoofNight3D PROTO
|
||||
PrepareRoofFloorDepth3D PROTO
|
||||
DrawRoofEllipsoid3D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
|
||||
DrawRoofPadding3D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
|
||||
RenderRoofCityGround3D PROTO
|
||||
RenderRoofLandmarks3D PROTO
|
||||
RenderRoofCityParks3D PROTO
|
||||
ShadeRoofCityPixel3D PROTO :DWORD,:DWORD,:DWORD,:DWORD
|
||||
DrawRoofSlope3D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
|
||||
DrawRoofPyramid3D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
|
||||
SampleRoofGroundLight3D PROTO :DWORD,:DWORD,:DWORD
|
||||
RenderRoofCityLamps3D PROTO
|
||||
RenderRoofTraffic3D PROTO
|
||||
DrawRoofCar3D PROTO :DWORD,:DWORD,:DWORD,:DWORD
|
||||
PrepareRoofCityAA3D PROTO
|
||||
FinishRoofCityAA3D PROTO
|
||||
RenderRoofCityBloom3D PROTO
|
||||
include src_graphics\rooftop_district_data.inc
|
||||
include src_graphics\rooftop_map.inc
|
||||
include src_graphics\rooftop_gl_data.inc
|
||||
.data
|
||||
roofSkyArt3D AnimPack3D <>
|
||||
roofMaterialArt3D AnimPack3D <>
|
||||
roofDetailArt3D AnimPack3D <>
|
||||
roofCityArt3D AnimPack3D <>
|
||||
roofCityNightArt3D AnimPack3D <>
|
||||
roofHandsArt3D AnimPack3D <>
|
||||
roofNightArt3D AnimPack3D <>
|
||||
roofLightingArt3D AnimPack3D <>
|
||||
roofLightingPath3D db "assets\animations\rooftop\lighting-v1.bin",0
|
||||
roofCityPath3D db "assets\animations\rooftop\city-v1.bin",0
|
||||
roofCityNightPath3D db "assets\animations\rooftop\city-night-v1.bin",0
|
||||
roofHandsPath3D db "assets\animations\rooftop\rail-hands-v1.bin",0
|
||||
roofNightPath3D db "assets\animations\rooftop\night-v1.bin",0
|
||||
roofNight3D dd 0
|
||||
roofCityPass3D dd 0
|
||||
roofLandmarkLight3D dd 0
|
||||
roofPixelGlow3D dd 0
|
||||
roofAAValid3D dd 0
|
||||
roofAAStill3D dd 0
|
||||
roofAAFrame3D dd 0
|
||||
roofAAX3D dd 0
|
||||
roofAAY3D dd 0
|
||||
roofAAAngle3D dd 0
|
||||
roofAAEye3D dd 0
|
||||
roofAAHorizon3D dd 0
|
||||
roofAATheme3D dd 0
|
||||
roofSlopeMask3D dd 0
|
||||
roofSlopeCentreX3D dd 0
|
||||
roofSlopeCentreY3D dd 0
|
||||
roofSlopeHalfX3D dd 0
|
||||
roofSlopeHalfY3D dd 0
|
||||
roofLean3D dd 0
|
||||
roofLeanTime3D dd 0
|
||||
roofLeanAngle3D dd 0
|
||||
roofLeanSide3D dd 0
|
||||
roofHandsContactX3D dd 320
|
||||
roofHandsWidth3D dd 640
|
||||
roofGuestClock3D dd 0
|
||||
roofGroundRoadPixels3D dd 0
|
||||
roofGroundWaterPixels3D dd 0
|
||||
roofGuestStep3D dd ROOF_GUEST_COUNT dup(0)
|
||||
roofGuestWait3D dd 1200,2400,3600,1800,3000,4200,2000,3300
|
||||
roofGuestMoving3D dd ROOF_GUEST_COUNT dup(0)
|
||||
roofGuestDistance3D dd ROOF_GUEST_COUNT dup(0)
|
||||
roofGuestDir3D dd ROOF_GUEST_COUNT dup(0)
|
||||
roofFloatFP3D REAL4 65536.0
|
||||
roofFloatHalfTex3D REAL4 127.5
|
||||
roofFloatShade3D REAL4 44.0
|
||||
roofFloatAmbient3D REAL4 205.0
|
||||
roofFloat4803D REAL4 480.0
|
||||
roofSkyPath3D db "assets\animations\rooftop\panorama-v1.bin",0
|
||||
roofMaterialPath3D db "assets\animations\rooftop\materials-v1.bin",0
|
||||
roofDetailPath3D db "assets\animations\rooftop\details-v1.bin",0
|
||||
liftPhase3D dd 0
|
||||
liftDoor3D dd 0
|
||||
liftTrip3D dd 0
|
||||
liftDestination3D dd 0
|
||||
liftRideTime3D dd 0
|
||||
liftAwayTime3D dd 0
|
||||
roofUpdateClock3D dd -1
|
||||
roofIndicatorPixel3D dd 0FFFFE9BDh
|
||||
roofSeat3D dd 0
|
||||
roofReturnX3D dd 0
|
||||
roofReturnY3D dd 0
|
||||
roofReturnAngle3D dd 0
|
||||
roofSeatX3D dd FP_1*145/10,FP_1*245/10,FP_1*195/10,FP_1*177/10,FP_1*206/10,FP_1*145/10,FP_1*245/10
|
||||
dd 13*FP_1,15*FP_1,17*FP_1,13*FP_1,13*FP_1,13*FP_1
|
||||
roofSeatY3D dd FP_1*95/10,FP_1*95/10,18*FP_1,FP_1*95/10,FP_1*95/10,15*FP_1,15*FP_1
|
||||
dd FP_1*61/10,FP_1*61/10,FP_1*61/10,FP_1*124/10,FP_1*139/10,FP_1*154/10
|
||||
roofSeatKind3D dd 0,0,0,1,1,1,1,2,2,2,3,3,3
|
||||
roofTargetSeat3D dd 0
|
||||
roofTreeX3D dd 12*FP_1,28*FP_1,12*FP_1,28*FP_1,FP_1*194/10,FP_1*215/10,FP_1*175/10,FP_1*278/10
|
||||
roofTreeY3D dd FP_1*68/10,8*FP_1,FP_1*189/10,16*FP_1,FP_1*134/10,FP_1*195/10,FP_1*195/10,FP_1*45/10
|
||||
roofPostX3D dd 12*FP_1,18*FP_1,12*FP_1,18*FP_1,FP_1*117/10,FP_1*283/10,FP_1*117/10,FP_1*283/10
|
||||
roofPostY3D dd FP_1*15/10,FP_1*15/10,FP_1*62/10,FP_1*62/10,7*FP_1,7*FP_1,15*FP_1,15*FP_1
|
||||
roofGuestX3D dd FP_1*188/10,FP_1*165/10,FP_1*265/10,FP_1*163/10,FP_1*221/10,FP_1*263/10,FP_1*155/10,FP_1*205/10
|
||||
roofGuestY3D dd FP_1*65/10,FP_1*75/10,13*FP_1,FP_1*119/10,FP_1*122/10,FP_1*188/10,FP_1*188/10,FP_1*16/10+15*FP_1
|
||||
roofGuestFrame3D dd 0,24,12,6,18,30,0,24
|
||||
roofActor3D CityProp3D <0,0,FP_1*75/100,FP_1*175/100,0,OFFSET npcGuestArt3D>
|
||||
roofTender3D CityProp3D <15*FP_1,FP_1*34/10,FP_1,FP_1,0,OFFSET hallBartender3D,FP_1*9/20>
|
||||
roofBarName3D db 203,192,211,205,198,45,193,192,208,32,34,194,219,209,206,210,192,34,0
|
||||
roofLiftCall3D db 69,58,32,194,219,199,194,192,210,220,32,203,200,212,210,0
|
||||
roofLiftUp3D db 49,58,32,199,192,202,192,210,32,47,32,50,58,32,205,206,215,220,32,47,32,69,58,32,197,213,192,210,220,0
|
||||
roofLeanPrompt3D db 69,58,32,206,193,203,206,202,206,210,200,210,220,209,223,0
|
||||
roofLeanLeave3D db 69,58,32,206,210,206,201,210,200,0
|
||||
roofSunsetName3D db 199,192,202,192,210,0
|
||||
roofNightName3D db 205,206,215,220,0
|
||||
roofLiftDown3D db 69,58,32,194,32,195,203,192,194,205,219,201,32,199,192,203,0
|
||||
roofLiftBusy3D db 203,200,212,210,32,207,206,196,205,200,204,192,197,210,209,223,0
|
||||
roofLiftBusyDown3D db 203,200,212,210,32,209,207,211,209,202,192,197,210,209,223,0
|
||||
roofSeatPrompt3D db 69,58,32,207,208,200,209,197,209,210,220,0
|
||||
roofStandPrompt3D db 69,58,32,194,209,210,192,210,220,0
|
||||
roofBarPrompt3D db 69,58,32,203,192,211,205,198,45,193,192,208,32,34,194,219,209,206,210,192,34,0
|
||||
roofArrival3D db 202,208,219,216,192,32,47,32,210,197,208,208,192,209,192,32,34,194,219,209,206,210,192,34,0
|
||||
.data?
|
||||
roofIndicatorPixels3D dd 4096 dup(?)
|
||||
roofCityMask3D db SCREEN_SIZE dup(?)
|
||||
roofGlowMask3D db SCREEN_SIZE dup(?)
|
||||
roofSlopeRay3D dd SCREEN_W dup(?)
|
||||
roofAAHistory3D dd SCREEN_SIZE dup(?)
|
||||
.code
|
||||
1496
src_graphics/rooftop_details.inc
Normal file
1037
src_graphics/rooftop_district_data.inc
Normal file
1122
src_graphics/rooftop_gl.inc
Normal file
92
src_graphics/rooftop_gl_api.inc
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
; Windows' system OpenGL entry points. All scene logic and calls are native MASM.
|
||||
includelib opengl32.lib
|
||||
includelib msimg32.lib
|
||||
glBegin PROTO STDCALL :DWORD
|
||||
glEnd PROTO STDCALL
|
||||
glVertex3f PROTO STDCALL :DWORD,:DWORD,:DWORD
|
||||
glNormal3f PROTO STDCALL :DWORD,:DWORD,:DWORD
|
||||
glTexCoord2f PROTO STDCALL :DWORD,:DWORD
|
||||
glColor4f PROTO STDCALL :DWORD,:DWORD,:DWORD,:DWORD
|
||||
glEnable PROTO STDCALL :DWORD
|
||||
glDisable PROTO STDCALL :DWORD
|
||||
glClear PROTO STDCALL :DWORD
|
||||
glClearColor PROTO STDCALL :DWORD,:DWORD,:DWORD,:DWORD
|
||||
glViewport PROTO STDCALL :DWORD,:DWORD,:DWORD,:DWORD
|
||||
glMatrixMode PROTO STDCALL :DWORD
|
||||
glLoadIdentity PROTO STDCALL
|
||||
glPushMatrix PROTO STDCALL
|
||||
glPopMatrix PROTO STDCALL
|
||||
glTranslatef PROTO STDCALL :DWORD,:DWORD,:DWORD
|
||||
glRotatef PROTO STDCALL :DWORD,:DWORD,:DWORD,:DWORD
|
||||
glFrustum PROTO STDCALL :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
|
||||
glOrtho PROTO STDCALL :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
|
||||
glGenTextures PROTO STDCALL :DWORD,:DWORD
|
||||
glDeleteTextures PROTO STDCALL :DWORD,:DWORD
|
||||
glBindTexture PROTO STDCALL :DWORD,:DWORD
|
||||
glTexParameteri PROTO STDCALL :DWORD,:DWORD,:DWORD
|
||||
glTexImage2D PROTO STDCALL :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
|
||||
glGenLists PROTO STDCALL :DWORD
|
||||
glNewList PROTO STDCALL :DWORD,:DWORD
|
||||
glEndList PROTO STDCALL
|
||||
glCallList PROTO STDCALL :DWORD
|
||||
glDeleteLists PROTO STDCALL :DWORD,:DWORD
|
||||
glLightfv PROTO STDCALL :DWORD,:DWORD,:DWORD
|
||||
glLightModelfv PROTO STDCALL :DWORD,:DWORD
|
||||
glMaterialfv PROTO STDCALL :DWORD,:DWORD,:DWORD
|
||||
glColorMaterial PROTO STDCALL :DWORD,:DWORD
|
||||
glFogfv PROTO STDCALL :DWORD,:DWORD
|
||||
glFogf PROTO STDCALL :DWORD,:DWORD
|
||||
glFogi PROTO STDCALL :DWORD,:DWORD
|
||||
glHint PROTO STDCALL :DWORD,:DWORD
|
||||
glShadeModel PROTO STDCALL :DWORD
|
||||
glBlendFunc PROTO STDCALL :DWORD,:DWORD
|
||||
glDepthMask PROTO STDCALL :DWORD
|
||||
glDepthFunc PROTO STDCALL :DWORD
|
||||
glPolygonOffset PROTO STDCALL :DWORD,:DWORD
|
||||
glReadPixels PROTO STDCALL :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
|
||||
glReadBuffer PROTO STDCALL :DWORD
|
||||
glDrawBuffer PROTO STDCALL :DWORD
|
||||
glGetError PROTO STDCALL
|
||||
glGetString PROTO STDCALL :DWORD
|
||||
glGetIntegerv PROTO STDCALL :DWORD,:DWORD
|
||||
glFlush PROTO STDCALL
|
||||
glFinish PROTO STDCALL
|
||||
glCullFace PROTO STDCALL :DWORD
|
||||
glFrontFace PROTO STDCALL :DWORD
|
||||
wglCreateContext PROTO STDCALL :DWORD
|
||||
wglDeleteContext PROTO STDCALL :DWORD
|
||||
wglMakeCurrent PROTO STDCALL :DWORD,:DWORD
|
||||
wglGetProcAddress PROTO STDCALL :DWORD
|
||||
IFNDEF RegisterClassA
|
||||
RegisterClassA PROTO STDCALL :DWORD
|
||||
ENDIF
|
||||
IFNDEF ChoosePixelFormat
|
||||
ChoosePixelFormat PROTO STDCALL :DWORD,:DWORD
|
||||
ENDIF
|
||||
IFNDEF SetPixelFormat
|
||||
SetPixelFormat PROTO STDCALL :DWORD,:DWORD,:DWORD
|
||||
ENDIF
|
||||
IFNDEF CreateDIBSection
|
||||
CreateDIBSection PROTO STDCALL :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
|
||||
ENDIF
|
||||
IFNDEF CreateCompatibleDC
|
||||
CreateCompatibleDC PROTO STDCALL :DWORD
|
||||
ENDIF
|
||||
IFNDEF DeleteDC
|
||||
DeleteDC PROTO STDCALL :DWORD
|
||||
ENDIF
|
||||
IFNDEF SelectObject
|
||||
SelectObject PROTO STDCALL :DWORD,:DWORD
|
||||
ENDIF
|
||||
IFNDEF DeleteObject
|
||||
DeleteObject PROTO STDCALL :DWORD
|
||||
ENDIF
|
||||
IFNDEF AlphaBlend
|
||||
AlphaBlend PROTO STDCALL :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
|
||||
ENDIF
|
||||
IFNDEF GdiFlush
|
||||
GdiFlush PROTO STDCALL
|
||||
ENDIF
|
||||
IFNDEF StretchBlt
|
||||
StretchBlt PROTO STDCALL :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
|
||||
ENDIF
|
||||
186
src_graphics/rooftop_gl_data.inc
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
include src_graphics\rooftop_gl_api.inc
|
||||
ROOF_HD_W equ 1280
|
||||
ROOF_HD_H equ 960
|
||||
ROOF_GL_TEXTURES equ 49
|
||||
ROOF_GL_LIST_COUNT equ 10
|
||||
ROOF_GL_WATER_LIST equ 9
|
||||
InitRoofHD3D PROTO
|
||||
FreeRoofHD3D PROTO
|
||||
RenderRoofHD3D PROTO
|
||||
PresentRoofHD3D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
|
||||
LoadRoofMesh3D PROTO :DWORD,:DWORD
|
||||
FreeRoofMesh3D PROTO :DWORD
|
||||
RoofGLProc3D PROTO :DWORD,:DWORD
|
||||
UploadRoofGLTexture3D PROTO :DWORD,:DWORD,:DWORD,:DWORD
|
||||
BuildRoofGLList3D PROTO :DWORD,:DWORD,:DWORD,:DWORD
|
||||
DrawRoofGLSky3D PROTO
|
||||
DrawRoofGLTraffic3D PROTO
|
||||
DrawRoofGLWater3D PROTO
|
||||
UploadRoofGLSky3D PROTO :DWORD,:DWORD
|
||||
UpdateRoofGLFog3D PROTO
|
||||
UpdateRoofQuality3D PROTO
|
||||
ComposeRoofFrame3D PROTO
|
||||
RenderRoofQualityToast3D PROTO
|
||||
WriteRoofRendererInfo3D PROTO
|
||||
WriteRoofLogString3D PROTO :DWORD,:DWORD
|
||||
.data
|
||||
roofHDReady3D dd 0
|
||||
roofHDStage3D dd 0
|
||||
roofHDWindow3D dd 0
|
||||
roofHDDC3D dd 0
|
||||
roofHDRC3D dd 0
|
||||
roofHDModule3D dd 0
|
||||
roofHDBuffer3D dd 0
|
||||
roofHDFBO3D dd 0
|
||||
roofHDColorTex3D dd 0
|
||||
roofHDDepth3D dd 0
|
||||
roofHDLists3D dd 0
|
||||
roofGLFogHint3D dd 1102h
|
||||
roofHDSkyTextures3D dd 2 dup(0)
|
||||
roofHDDayTextures3D dd ROOF_GL_TEXTURES dup(0)
|
||||
roofHDNightTextures3D dd ROOF_GL_TEXTURES dup(0)
|
||||
roofHDEmissionTextures3D dd ROOF_GL_TEXTURES dup(0)
|
||||
roofHDMeshScene3D dd 4 dup(0)
|
||||
roofHDMeshCar3D dd 4 dup(0)
|
||||
roofHDMeshBoat3D dd 4 dup(0)
|
||||
roofHDScenePath3D db "assets\animations\rooftop\mesh-city-v1.bin",0
|
||||
roofHDCarPath3D db "assets\animations\rooftop\mesh-car-v1.bin",0
|
||||
roofHDBoatPath3D db "assets\animations\rooftop\mesh-boat-v1.bin",0
|
||||
roofHDMeshPtrs3D dd OFFSET roofHDMeshScene3D,OFFSET roofHDMeshCar3D,OFFSET roofHDMeshBoat3D
|
||||
roofHDClassName3D db "AssemblerCasinoRoofOffscreen",0
|
||||
roofHDTitle3D db "Roof offscreen renderer",0
|
||||
roofHDClass3D dd 20h,OFFSET DefWindowProcA,0,0,0,0,0,0,0,OFFSET roofHDClassName3D
|
||||
; PIXELFORMATDESCRIPTOR: RGBA32, depth24, draw-to-window/OpenGL/double-buffer.
|
||||
roofHDPixelFormat3D dw 40,1
|
||||
dd 25h
|
||||
db 0,32,0,0,0,0,0,0,8,0,0,0,0,0,0,24,8,0,0,0
|
||||
dd 0,0,0
|
||||
roofHDOverlayDC3D dd 0
|
||||
roofHDOverlayBmp3D dd 0
|
||||
roofHDOverlayOld3D dd 0
|
||||
roofHDFinalDC3D dd 0
|
||||
roofHDFinalBmp3D dd 0
|
||||
roofHDFinalOld3D dd 0
|
||||
roofHDFinalPixels3D dd 0
|
||||
roofHDOverlayPixels3D dd 0
|
||||
roofHDOverlayBMI3D dd 40,640,-480
|
||||
dw 1,32
|
||||
dd 0,640*480*4,0,0,0,0
|
||||
roofHDBMI3D dd 40,ROOF_HD_W,ROOF_HD_H
|
||||
dw 1,32
|
||||
dd 0,ROOF_HD_W*ROOF_HD_H*4,0,0,0,0
|
||||
roofGLGenFBO3D dd 0
|
||||
roofGLBindFBO3D dd 0
|
||||
roofGLAttachTexture3D dd 0
|
||||
roofGLCheckFBO3D dd 0
|
||||
roofGLDeleteFBO3D dd 0
|
||||
roofGLGenRBO3D dd 0
|
||||
roofGLBindRBO3D dd 0
|
||||
roofGLStoreRBO3D dd 0
|
||||
roofGLAttachRBO3D dd 0
|
||||
roofGLDeleteRBO3D dd 0
|
||||
roofGLMipmap3D dd 0
|
||||
roofGLProcSlots3D dd OFFSET roofGLGenFBO3D,OFFSET roofGLBindFBO3D,OFFSET roofGLAttachTexture3D
|
||||
dd OFFSET roofGLCheckFBO3D,OFFSET roofGLDeleteFBO3D,OFFSET roofGLGenRBO3D
|
||||
dd OFFSET roofGLBindRBO3D,OFFSET roofGLStoreRBO3D,OFFSET roofGLAttachRBO3D
|
||||
dd OFFSET roofGLDeleteRBO3D,OFFSET roofGLMipmap3D
|
||||
roofGLProcNames3D dd OFFSET roofGLName0,OFFSET roofGLName1,OFFSET roofGLName2,OFFSET roofGLName3
|
||||
dd OFFSET roofGLName4,OFFSET roofGLName5,OFFSET roofGLName6,OFFSET roofGLName7
|
||||
dd OFFSET roofGLName8,OFFSET roofGLName9,OFFSET roofGLName10
|
||||
roofGLProcExtNames3D dd OFFSET roofGLExt0,OFFSET roofGLExt1,OFFSET roofGLExt2,OFFSET roofGLExt3
|
||||
dd OFFSET roofGLExt4,OFFSET roofGLExt5,OFFSET roofGLExt6,OFFSET roofGLExt7
|
||||
dd OFFSET roofGLExt8,OFFSET roofGLExt9,OFFSET roofGLExt10
|
||||
roofGLName0 db "glGenFramebuffers",0
|
||||
roofGLName1 db "glBindFramebuffer",0
|
||||
roofGLName2 db "glFramebufferTexture2D",0
|
||||
roofGLName3 db "glCheckFramebufferStatus",0
|
||||
roofGLName4 db "glDeleteFramebuffers",0
|
||||
roofGLName5 db "glGenRenderbuffers",0
|
||||
roofGLName6 db "glBindRenderbuffer",0
|
||||
roofGLName7 db "glRenderbufferStorage",0
|
||||
roofGLName8 db "glFramebufferRenderbuffer",0
|
||||
roofGLName9 db "glDeleteRenderbuffers",0
|
||||
roofGLName10 db "glGenerateMipmap",0
|
||||
roofGLExt0 db "glGenFramebuffersEXT",0
|
||||
roofGLExt1 db "glBindFramebufferEXT",0
|
||||
roofGLExt2 db "glFramebufferTexture2DEXT",0
|
||||
roofGLExt3 db "glCheckFramebufferStatusEXT",0
|
||||
roofGLExt4 db "glDeleteFramebuffersEXT",0
|
||||
roofGLExt5 db "glGenRenderbuffersEXT",0
|
||||
roofGLExt6 db "glBindRenderbufferEXT",0
|
||||
roofGLExt7 db "glRenderbufferStorageEXT",0
|
||||
roofGLExt8 db "glFramebufferRenderbufferEXT",0
|
||||
roofGLExt9 db "glDeleteRenderbuffersEXT",0
|
||||
roofGLExt10 db "glGenerateMipmapEXT",0
|
||||
roofGLFogDay3D REAL4 0.67,0.61,0.69,1.0
|
||||
roofGLFogNight3D REAL4 0.05,0.09,0.17,1.0
|
||||
roofGLFogCurrent3D REAL4 0.67,0.61,0.69,1.0
|
||||
roofGLFogWhite3D REAL4 1.0,1.0,1.0,1.0
|
||||
roofGLFogBlack3D REAL4 0.0,0.0,0.0,1.0
|
||||
roofGLColorScale3D REAL4 255.0
|
||||
roofGLWaveDiv3D REAL4 100000.0
|
||||
roofGLHalf3D REAL4 0.5
|
||||
roofGLAmbientDay3D REAL4 0.43,0.43,0.48,1.0
|
||||
roofGLAmbientNight3D REAL4 0.20,0.23,0.32,1.0
|
||||
roofGLSunColorDay3D REAL4 0.75,0.57,0.39,1.0
|
||||
roofGLSunColorNight3D REAL4 0.12,0.15,0.25,1.0
|
||||
roofGLSunDirection3D REAL4 -0.8,0.8,0.4,0.0
|
||||
roofGLEmissionZero3D REAL4 0.0,0.0,0.0,1.0
|
||||
roofGLEmissionGold3D REAL4 0.42,0.28,0.08,1.0
|
||||
roofGLEmissionLamp3D REAL4 0.9,0.67,0.3,1.0
|
||||
roofGLInvFP3D REAL4 0.0000152587890625
|
||||
roofGLDegrees3D REAL4 0.703125
|
||||
roofGLNinety3D REAL4 90.0
|
||||
roofGLHalfWidth3D REAL8 0.66
|
||||
roofGLLeft3D REAL8 -0.66
|
||||
roofGLRight3D REAL8 0.66
|
||||
roofGLNear3D REAL8 1.0
|
||||
roofGLFar3D REAL8 2048.0
|
||||
roofGLZeroD3D REAL8 0.0
|
||||
roofGLOneD3D REAL8 1.0
|
||||
roofGLMinusOneD3D REAL8 -1.0
|
||||
roofGLScreenWidthD3D REAL8 1280.0
|
||||
roofGLScreenHeightD3D REAL8 960.0
|
||||
roofGLCameraH3D dd 480
|
||||
roofHDRenderX3D dd -1
|
||||
roofHDRenderY3D dd -1
|
||||
roofHDRenderAngle3D dd -1
|
||||
roofHDRenderEye3D dd -1
|
||||
roofHDRenderHorizon3D dd -1
|
||||
roofHDRenderTheme3D dd -1
|
||||
roofHDRenderClock3D dd -1
|
||||
roofRenderW3D dd 960
|
||||
roofRenderH3D dd 720
|
||||
roofQualityMode3D dd 0
|
||||
roofAutoWidth3D dd 960
|
||||
roofQualityMapW3D dd 0
|
||||
roofQualityClock3D dd -1
|
||||
roofQualitySlow3D dd 0
|
||||
roofQualityToast3D dd 0
|
||||
roofQualityAutoText3D db 202,208,219,216,192,58,32,192,194,210,206,0
|
||||
roofQualityEcoText3D db 202,208,219,216,192,58,32,221,202,206,0
|
||||
roofQualityHighText3D db 202,208,219,216,192,58,32,196,197,210,192,203,200,0
|
||||
roofLogPath3D db "runtime\roof-renderer.txt",0
|
||||
roofLogHeader3D db "Native MASM rooftop renderer",13,10,0
|
||||
roofLogTerrain3D db "Terrain path: tiled ground and animated mesh water v2",13,10,0
|
||||
roofLogTriangles3D db "City mesh triangles: ",0
|
||||
roofLogVendor3D db "Vendor: ",0
|
||||
roofLogRenderer3D db "Renderer: ",0
|
||||
roofLogVersion3D db "OpenGL: ",0
|
||||
roofLogStage3D db "Init stage (0=ready): ",0
|
||||
roofLogAssets3D db "City texture pack present: ",0
|
||||
roofLogWidth3D db "Render width: ",0
|
||||
roofLogSmall3D db "640",0
|
||||
roofLogMedium3D db "960",0
|
||||
roofLogLarge3D db "1280",0
|
||||
roofLogUnknown3D db "unavailable",0
|
||||
roofLogNL3D db 13,10,0
|
||||
roofLogDigit3D db "0",0
|
||||
.data?
|
||||
roofHDBackdrop3D dd SCREEN_SIZE dup(?)
|
||||
roofHDUsedMaterials3D db ROOF_GL_TEXTURES dup(?)
|
||||
roofQualityX3D dd SCREEN_W dup(?)
|
||||
roofQualityXCount3D dd SCREEN_W dup(?)
|
||||
roofQualityY3D dd SCREEN_H dup(?)
|
||||
roofQualityYCount3D dd SCREEN_H dup(?)
|
||||
.code
|
||||
74
src_graphics/rooftop_map.inc
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
; Generated by tools/generators/pack_rooftop.py.
|
||||
.data
|
||||
roofMap3D db 102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102
|
||||
db 102,102,102,102,102,102,102,102,102,102,102,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,102,102,102
|
||||
db 102,102,102,102,102,102,102,102,102,102,102,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,102,102,102
|
||||
db 102,102,102,102,102,102,102,102,102,102,102,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,102,102,102
|
||||
db 102,102,102,102,102,102,102,102,102,102,102,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,102,102,102
|
||||
db 102,102,102,102,102,102,102,102,102,102,102,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,102,102,102
|
||||
db 102,102,102,102,102,102,102,102,102,102,102,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,102,102,102
|
||||
db 102,102,102,102,102,102,102,102,102,102,102,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,102,102,102
|
||||
db 102,102,102,102,102,102,102,102,102,102,102,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,102,102,102
|
||||
db 102,102,102,102,102,102,102,102,102,102,102,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,102,102,102
|
||||
db 102,102,102,102,102,102,102,102,102,102,102,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,102,102,102
|
||||
db 102,102,102,102,102,102,102,102,102,102,102,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,102,102,102
|
||||
db 102,102,102,102,102,102,102,102,102,102,102,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,102,102,102
|
||||
db 102,102,102,102,102,102,102,102,102,102,102,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,102,102,102
|
||||
db 102,102,102,102,102,102,102,102,102,102,102,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,102,102,102
|
||||
db 102,102,102,102,102,102,102,102,102,102,102,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,102,102,102
|
||||
db 102,102,102,102,102,102,102,102,102,102,102,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,102,102,102
|
||||
db 102,102,102,102,102,102,102,102,102,102,102,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,102,102,102
|
||||
db 102,102,102,102,102,102,102,102,102,102,102,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,102,102,102
|
||||
db 102,102,102,102,102,102,102,102,102,102,102,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,102,102,102
|
||||
db 102,102,102,102,102,102,102,102,102,102,102,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,102,102,102
|
||||
db 102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102
|
||||
db 102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102
|
||||
db 102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102
|
||||
db 102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102
|
||||
db 102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102
|
||||
db 102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102
|
||||
db 102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102
|
||||
db 102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102
|
||||
db 102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102
|
||||
db 102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102
|
||||
db 102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102
|
||||
roofSkyColumns3D dd -190,-190,-189,-189,-188,-188,-187,-187,-186,-186,-185,-185,-184,-184,-184,-183
|
||||
dd -183,-182,-182,-181,-181,-180,-180,-179,-179,-178,-178,-177,-177,-176,-176,-175
|
||||
dd -175,-174,-174,-173,-173,-172,-172,-171,-171,-170,-170,-169,-169,-168,-168,-167
|
||||
dd -167,-166,-166,-165,-165,-164,-164,-163,-163,-162,-161,-161,-160,-160,-159,-159
|
||||
dd -158,-158,-157,-157,-156,-156,-155,-155,-154,-154,-153,-153,-152,-151,-151,-150
|
||||
dd -150,-149,-149,-148,-148,-147,-147,-146,-145,-145,-144,-144,-143,-143,-142,-142
|
||||
dd -141,-141,-140,-139,-139,-138,-138,-137,-137,-136,-135,-135,-134,-134,-133,-133
|
||||
dd -132,-132,-131,-130,-130,-129,-129,-128,-128,-127,-126,-126,-125,-125,-124,-123
|
||||
dd -123,-122,-122,-121,-121,-120,-119,-119,-118,-118,-117,-116,-116,-115,-115,-114
|
||||
dd -113,-113,-112,-112,-111,-111,-110,-109,-109,-108,-108,-107,-106,-106,-105,-105
|
||||
dd -104,-103,-103,-102,-101,-101,-100,-100,-99,-98,-98,-97,-97,-96,-95,-95
|
||||
dd -94,-93,-93,-92,-92,-91,-90,-90,-89,-89,-88,-87,-87,-86,-85,-85
|
||||
dd -84,-84,-83,-82,-82,-81,-80,-80,-79,-78,-78,-77,-77,-76,-75,-75
|
||||
dd -74,-73,-73,-72,-71,-71,-70,-70,-69,-68,-68,-67,-66,-66,-65,-64
|
||||
dd -64,-63,-62,-62,-61,-60,-60,-59,-59,-58,-57,-57,-56,-55,-55,-54
|
||||
dd -53,-53,-52,-51,-51,-50,-49,-49,-48,-47,-47,-46,-45,-45,-44,-43
|
||||
dd -43,-42,-41,-41,-40,-39,-39,-38,-37,-37,-36,-35,-35,-34,-33,-33
|
||||
dd -32,-31,-31,-30,-29,-29,-28,-27,-27,-26,-25,-25,-24,-23,-23,-22
|
||||
dd -21,-21,-20,-19,-19,-18,-17,-17,-16,-15,-15,-14,-13,-13,-12,-11
|
||||
dd -11,-10,-9,-9,-8,-7,-7,-6,-5,-5,-4,-3,-3,-2,-1,-1
|
||||
dd 0,1,1,2,3,3,4,5,5,6,7,7,8,9,9,10
|
||||
dd 11,11,12,13,13,14,15,15,16,17,17,18,19,19,20,21
|
||||
dd 21,22,23,23,24,25,25,26,27,27,28,29,29,30,31,31
|
||||
dd 32,33,33,34,35,35,36,37,37,38,39,39,40,41,41,42
|
||||
dd 43,43,44,45,45,46,47,47,48,49,49,50,51,51,52,53
|
||||
dd 53,54,55,55,56,57,57,58,59,59,60,60,61,62,62,63
|
||||
dd 64,64,65,66,66,67,68,68,69,70,70,71,71,72,73,73
|
||||
dd 74,75,75,76,77,77,78,78,79,80,80,81,82,82,83,84
|
||||
dd 84,85,85,86,87,87,88,89,89,90,90,91,92,92,93,93
|
||||
dd 94,95,95,96,97,97,98,98,99,100,100,101,101,102,103,103
|
||||
dd 104,105,105,106,106,107,108,108,109,109,110,111,111,112,112,113
|
||||
dd 113,114,115,115,116,116,117,118,118,119,119,120,121,121,122,122
|
||||
dd 123,123,124,125,125,126,126,127,128,128,129,129,130,130,131,132
|
||||
dd 132,133,133,134,134,135,135,136,137,137,138,138,139,139,140,141
|
||||
dd 141,142,142,143,143,144,144,145,145,146,147,147,148,148,149,149
|
||||
dd 150,150,151,151,152,153,153,154,154,155,155,156,156,157,157,158
|
||||
dd 158,159,159,160,160,161,161,162,163,163,164,164,165,165,166,166
|
||||
dd 167,167,168,168,169,169,170,170,171,171,172,172,173,173,174,174
|
||||
dd 175,175,176,176,177,177,178,178,179,179,180,180,181,181,182,182
|
||||
dd 183,183,184,184,184,185,185,186,186,187,187,188,188,189,189,190
|
||||
280
src_graphics/rooftop_quality.inc
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
; Quality changes only roof rendering. Simulation, collisions and other games stay native.
|
||||
UpdateRoofQuality3D PROC uses ebx esi edi
|
||||
cmp hallLevel3D,HALL_ROOF
|
||||
jne roof_quality_apply
|
||||
cmp engineMode,MODE_WALK
|
||||
jne roof_quality_apply
|
||||
mov eax,animationClock3D
|
||||
cmp eax,roofQualityClock3D
|
||||
je roof_quality_apply
|
||||
mov roofQualityClock3D,eax
|
||||
invoke Pressed3D,77h
|
||||
test eax,eax
|
||||
jz roof_quality_auto
|
||||
inc roofQualityMode3D
|
||||
cmp roofQualityMode3D,3
|
||||
jb @F
|
||||
mov roofQualityMode3D,0
|
||||
mov roofAutoWidth3D,960
|
||||
@@:
|
||||
mov roofQualityToast3D,1800
|
||||
mov roofQualitySlow3D,0
|
||||
roof_quality_auto:
|
||||
mov eax,deltaTime3D
|
||||
cmp eax,100
|
||||
jbe @F
|
||||
mov eax,100
|
||||
@@:
|
||||
sub roofQualityToast3D,eax
|
||||
jns @F
|
||||
mov roofQualityToast3D,0
|
||||
@@:
|
||||
cmp roofQualityMode3D,0
|
||||
jne roof_quality_apply
|
||||
cmp roofAutoWidth3D,640
|
||||
je roof_quality_apply
|
||||
cmp deltaTime3D,34
|
||||
jbe roof_quality_cool
|
||||
add roofQualitySlow3D,eax
|
||||
cmp roofQualitySlow3D,1000
|
||||
jb roof_quality_apply
|
||||
mov roofAutoWidth3D,640
|
||||
mov roofQualitySlow3D,0
|
||||
mov roofQualityToast3D,1800
|
||||
jmp roof_quality_apply
|
||||
roof_quality_cool:
|
||||
sub roofQualitySlow3D,8
|
||||
jns roof_quality_apply
|
||||
mov roofQualitySlow3D,0
|
||||
roof_quality_apply:
|
||||
mov eax,roofAutoWidth3D
|
||||
cmp roofQualityMode3D,0
|
||||
je roof_quality_size
|
||||
mov eax,640
|
||||
cmp roofQualityMode3D,1
|
||||
je roof_quality_size
|
||||
mov eax,1280
|
||||
roof_quality_size:
|
||||
mov roofRenderW3D,eax
|
||||
imul eax,3
|
||||
shr eax,2
|
||||
mov roofRenderH3D,eax
|
||||
mov eax,roofRenderW3D
|
||||
cmp eax,roofQualityMapW3D
|
||||
je roof_quality_done
|
||||
mov roofQualityMapW3D,eax
|
||||
mov roofHDRenderClock3D,-1
|
||||
xor esi,esi
|
||||
roof_quality_x:
|
||||
mov eax,esi
|
||||
imul eax,roofRenderW3D
|
||||
xor edx,edx
|
||||
mov ecx,SCREEN_W
|
||||
div ecx
|
||||
mov ebx,eax
|
||||
shl eax,2
|
||||
mov roofQualityX3D[esi*4],eax
|
||||
mov eax,esi
|
||||
inc eax
|
||||
imul eax,roofRenderW3D
|
||||
xor edx,edx
|
||||
div ecx
|
||||
sub eax,ebx
|
||||
mov roofQualityXCount3D[esi*4],eax
|
||||
inc esi
|
||||
cmp esi,SCREEN_W
|
||||
jb roof_quality_x
|
||||
xor esi,esi
|
||||
roof_quality_y:
|
||||
mov eax,esi
|
||||
imul eax,roofRenderH3D
|
||||
xor edx,edx
|
||||
mov ecx,SCREEN_H
|
||||
div ecx
|
||||
mov ebx,eax
|
||||
mov edi,ROOF_HD_H-1
|
||||
sub edi,eax
|
||||
imul edi,ROOF_HD_W*4
|
||||
mov roofQualityY3D[esi*4],edi
|
||||
mov eax,esi
|
||||
inc eax
|
||||
imul eax,roofRenderH3D
|
||||
xor edx,edx
|
||||
div ecx
|
||||
sub eax,ebx
|
||||
mov roofQualityYCount3D[esi*4],eax
|
||||
inc esi
|
||||
cmp esi,SCREEN_H
|
||||
jb roof_quality_y
|
||||
cmp roofHDReady3D,1
|
||||
jne roof_quality_done
|
||||
call WriteRoofRendererInfo3D
|
||||
roof_quality_done:
|
||||
ret
|
||||
UpdateRoofQuality3D ENDP
|
||||
|
||||
ComposeRoofFrame3D PROC uses ebx esi edi
|
||||
local rows:DWORD,y:DWORD,x:DWORD,targetRow:DWORD,repeatRow:DWORD
|
||||
invoke GdiFlush
|
||||
mov esi,roofHDBuffer3D
|
||||
mov eax,ROOF_HD_H
|
||||
sub eax,roofRenderH3D
|
||||
imul eax,ROOF_HD_W*4
|
||||
add eax,roofHDFinalPixels3D
|
||||
mov edi,eax
|
||||
mov eax,roofRenderH3D
|
||||
mov rows,eax
|
||||
roof_compose_background:
|
||||
mov ecx,roofRenderW3D
|
||||
rep movsd
|
||||
mov eax,ROOF_HD_W
|
||||
sub eax,roofRenderW3D
|
||||
lea edi,[edi+eax*4]
|
||||
dec rows
|
||||
jnz roof_compose_background
|
||||
xor esi,esi
|
||||
mov y,0
|
||||
roof_compose_row:
|
||||
mov ebx,y
|
||||
mov eax,roofQualityY3D[ebx*4]
|
||||
add eax,roofHDFinalPixels3D
|
||||
mov targetRow,eax
|
||||
mov eax,roofQualityYCount3D[ebx*4]
|
||||
mov repeatRow,eax
|
||||
mov x,0
|
||||
roof_compose_pixel:
|
||||
mov eax,screenBuffer[esi*4]
|
||||
cmp roofCityMask3D[esi],0
|
||||
je roof_compose_opaque
|
||||
cmp eax,roofHDBackdrop3D[esi*4]
|
||||
je roof_compose_next
|
||||
roof_compose_opaque:
|
||||
mov ebx,x
|
||||
mov edi,roofQualityX3D[ebx*4]
|
||||
add edi,targetRow
|
||||
mov [edi],eax
|
||||
cmp roofQualityXCount3D[ebx*4],2
|
||||
jne @F
|
||||
mov [edi+4],eax
|
||||
@@:
|
||||
cmp repeatRow,2
|
||||
jne roof_compose_next
|
||||
mov [edi-ROOF_HD_W*4],eax
|
||||
cmp roofQualityXCount3D[ebx*4],2
|
||||
jne roof_compose_next
|
||||
mov [edi-ROOF_HD_W*4+4],eax
|
||||
roof_compose_next:
|
||||
inc esi
|
||||
inc x
|
||||
cmp x,SCREEN_W
|
||||
jb roof_compose_pixel
|
||||
inc y
|
||||
cmp y,SCREEN_H
|
||||
jb roof_compose_row
|
||||
ret
|
||||
ComposeRoofFrame3D ENDP
|
||||
|
||||
RenderRoofQualityToast3D PROC
|
||||
cmp roofQualityToast3D,0
|
||||
je roof_quality_toast_done
|
||||
mov eax,OFFSET roofQualityAutoText3D
|
||||
cmp roofRenderW3D,640
|
||||
jne @F
|
||||
mov eax,OFFSET roofQualityEcoText3D
|
||||
@@:
|
||||
cmp roofQualityMode3D,2
|
||||
jne @F
|
||||
mov eax,OFFSET roofQualityHighText3D
|
||||
@@:
|
||||
push eax
|
||||
invoke DrawBox,12,40,160,18,00151B1Eh
|
||||
pop eax
|
||||
invoke DrawString8x8,20,45,eax,00E8D7ADh
|
||||
roof_quality_toast_done:
|
||||
ret
|
||||
RenderRoofQualityToast3D ENDP
|
||||
|
||||
WriteRoofLogString3D PROC uses ebx esi handle:DWORD,value:DWORD
|
||||
local written:DWORD
|
||||
mov ebx,handle
|
||||
mov esi,value
|
||||
test esi,esi
|
||||
jnz @F
|
||||
mov esi,OFFSET roofLogUnknown3D
|
||||
@@:
|
||||
xor ecx,ecx
|
||||
roof_log_length:
|
||||
cmp BYTE PTR [esi+ecx],0
|
||||
je roof_log_write
|
||||
inc ecx
|
||||
jmp roof_log_length
|
||||
roof_log_write:
|
||||
invoke WriteFile,ebx,esi,ecx,ADDR written,0
|
||||
ret
|
||||
WriteRoofLogString3D ENDP
|
||||
|
||||
WriteRoofRendererInfo3D PROC uses ebx esi
|
||||
local number[16]:BYTE
|
||||
invoke CreateDirectoryA,OFFSET runtime3D,0
|
||||
invoke CreateFileA,OFFSET roofLogPath3D,40000000h,1,0,2,80h,0
|
||||
cmp eax,-1
|
||||
je roof_log_done
|
||||
mov ebx,eax
|
||||
invoke WriteRoofLogString3D,ebx,OFFSET roofLogHeader3D
|
||||
invoke WriteRoofLogString3D,ebx,OFFSET roofLogTerrain3D
|
||||
invoke WriteRoofLogString3D,ebx,OFFSET roofLogTriangles3D
|
||||
invoke FormatNumber,roofHDMeshScene3D[4],ADDR number
|
||||
invoke WriteRoofLogString3D,ebx,ADDR number
|
||||
invoke WriteRoofLogString3D,ebx,OFFSET roofLogNL3D
|
||||
mov esi,0
|
||||
roof_log_gl_string:
|
||||
mov eax,OFFSET roofLogVendor3D
|
||||
cmp esi,0
|
||||
je @F
|
||||
mov eax,OFFSET roofLogRenderer3D
|
||||
cmp esi,1
|
||||
je @F
|
||||
mov eax,OFFSET roofLogVersion3D
|
||||
@@:
|
||||
invoke WriteRoofLogString3D,ebx,eax
|
||||
xor eax,eax
|
||||
cmp roofHDRC3D,0
|
||||
je @F
|
||||
mov eax,esi
|
||||
add eax,1F00h
|
||||
invoke glGetString,eax
|
||||
@@:
|
||||
invoke WriteRoofLogString3D,ebx,eax
|
||||
invoke WriteRoofLogString3D,ebx,OFFSET roofLogNL3D
|
||||
inc esi
|
||||
cmp esi,3
|
||||
jb roof_log_gl_string
|
||||
invoke WriteRoofLogString3D,ebx,OFFSET roofLogStage3D
|
||||
mov eax,roofHDStage3D
|
||||
add al,'0'
|
||||
mov roofLogDigit3D,al
|
||||
invoke WriteRoofLogString3D,ebx,OFFSET roofLogDigit3D
|
||||
invoke WriteRoofLogString3D,ebx,OFFSET roofLogNL3D
|
||||
invoke WriteRoofLogString3D,ebx,OFFSET roofLogAssets3D
|
||||
mov roofLogDigit3D,'0'
|
||||
cmp roofCityArt3D.pixels,0
|
||||
je @F
|
||||
mov roofLogDigit3D,'1'
|
||||
@@:
|
||||
invoke WriteRoofLogString3D,ebx,OFFSET roofLogDigit3D
|
||||
invoke WriteRoofLogString3D,ebx,OFFSET roofLogNL3D
|
||||
invoke WriteRoofLogString3D,ebx,OFFSET roofLogWidth3D
|
||||
mov eax,OFFSET roofLogMedium3D
|
||||
cmp roofRenderW3D,960
|
||||
je @F
|
||||
mov eax,OFFSET roofLogSmall3D
|
||||
cmp roofRenderW3D,640
|
||||
je @F
|
||||
mov eax,OFFSET roofLogLarge3D
|
||||
@@:
|
||||
invoke WriteRoofLogString3D,ebx,eax
|
||||
invoke WriteRoofLogString3D,ebx,OFFSET roofLogNL3D
|
||||
invoke CloseHandle,ebx
|
||||
roof_log_done:
|
||||
ret
|
||||
WriteRoofRendererInfo3D ENDP
|
||||
317
src_graphics/rooftop_surfaces.inc
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
; Ray/plane intersection for real pitched roofs, including perspective depth.
|
||||
DrawRoofSlope3D PROC uses ebx esi edi axis:DWORD,x0:DWORD,x1:DWORD,y0:DWORD,y1:DWORD,baseZ:DWORD,rise:DWORD,material:DWORD
|
||||
local slope:DWORD,numerator:DWORD,leftPx:DWORD,rightPx:DWORD,topPx:DWORD,bottomPx:DWORD
|
||||
local corner:DWORD,wx:DWORD,wy:DWORD,wz:DWORD,visible:DWORD,nearClip:DWORD
|
||||
local row:DWORD,column:DWORD,rayZ:DWORD,depth:DWORD,along:DWORD,denominator:DWORD
|
||||
local texture:DWORD,textureW:DWORD,textureH:DWORD,light:DWORD,sourceU:DWORD
|
||||
invoke ResolveRoofSurface3D,material
|
||||
test eax,eax
|
||||
jz roof_slope_done
|
||||
mov texture,eax
|
||||
mov textureW,ecx
|
||||
mov textureH,edx
|
||||
mov eax,rise
|
||||
cdq
|
||||
shld edx,eax,16
|
||||
shl eax,16
|
||||
mov ecx,x1
|
||||
sub ecx,x0
|
||||
cmp axis,0
|
||||
je @F
|
||||
mov ecx,y1
|
||||
sub ecx,y0
|
||||
@@:
|
||||
idiv ecx
|
||||
mov slope,eax
|
||||
mov light,242
|
||||
test eax,eax
|
||||
jns @F
|
||||
mov light,184
|
||||
@@:
|
||||
mov eax,playerX
|
||||
sub eax,x0
|
||||
cmp axis,0
|
||||
je @F
|
||||
mov eax,playerY
|
||||
sub eax,y0
|
||||
@@:
|
||||
imul slope
|
||||
shrd eax,edx,16
|
||||
neg eax
|
||||
add eax,viewEye3D
|
||||
sub eax,baseZ
|
||||
mov numerator,eax
|
||||
mov leftPx,SCREEN_W
|
||||
mov rightPx,0
|
||||
mov topPx,SCREEN_H
|
||||
mov bottomPx,0
|
||||
mov visible,0
|
||||
mov nearClip,0
|
||||
mov corner,0
|
||||
roof_slope_corner:
|
||||
mov eax,x0
|
||||
test corner,1
|
||||
jz @F
|
||||
mov eax,x1
|
||||
@@:
|
||||
mov wx,eax
|
||||
mov ebx,y0
|
||||
test corner,2
|
||||
jz @F
|
||||
mov ebx,y1
|
||||
@@:
|
||||
mov wy,ebx
|
||||
mov ecx,baseZ
|
||||
mov eax,corner
|
||||
cmp axis,0
|
||||
jne @F
|
||||
and eax,1
|
||||
jmp roof_slope_corner_height
|
||||
@@:
|
||||
and eax,2
|
||||
roof_slope_corner_height:
|
||||
test eax,eax
|
||||
jz @F
|
||||
add ecx,rise
|
||||
@@:
|
||||
mov wz,ecx
|
||||
invoke ProjectCityPoint3D,wx,wy
|
||||
cmp edx,4096
|
||||
jg roof_slope_projected_corner
|
||||
mov nearClip,1
|
||||
jmp roof_slope_corner_next
|
||||
roof_slope_projected_corner:
|
||||
inc visible
|
||||
mov esi,edx
|
||||
cmp eax,leftPx
|
||||
jge @F
|
||||
mov leftPx,eax
|
||||
@@:
|
||||
cmp eax,rightPx
|
||||
jle @F
|
||||
mov rightPx,eax
|
||||
@@:
|
||||
mov eax,viewEye3D
|
||||
sub eax,wz
|
||||
mov ecx,SCREEN_H
|
||||
imul ecx
|
||||
idiv esi
|
||||
add eax,viewHorizon3D
|
||||
cmp eax,topPx
|
||||
jge @F
|
||||
mov topPx,eax
|
||||
@@:
|
||||
cmp eax,bottomPx
|
||||
jle @F
|
||||
mov bottomPx,eax
|
||||
@@:
|
||||
roof_slope_corner_next:
|
||||
inc corner
|
||||
cmp corner,4
|
||||
jb roof_slope_corner
|
||||
cmp visible,0
|
||||
je roof_slope_done
|
||||
cmp nearClip,0
|
||||
je @F
|
||||
mov leftPx,0
|
||||
mov rightPx,SCREEN_W-1
|
||||
mov topPx,0
|
||||
mov bottomPx,SCREEN_H-1
|
||||
@@:
|
||||
cmp leftPx,0
|
||||
jge @F
|
||||
mov leftPx,0
|
||||
@@:
|
||||
cmp rightPx,SCREEN_W-1
|
||||
jle @F
|
||||
mov rightPx,SCREEN_W-1
|
||||
@@:
|
||||
cmp topPx,0
|
||||
jge @F
|
||||
mov topPx,0
|
||||
@@:
|
||||
cmp bottomPx,SCREEN_H-1
|
||||
jle @F
|
||||
mov bottomPx,SCREEN_H-1
|
||||
@@:
|
||||
mov eax,leftPx
|
||||
cmp eax,rightPx
|
||||
jge roof_slope_done
|
||||
mov eax,topPx
|
||||
cmp eax,bottomPx
|
||||
jge roof_slope_done
|
||||
mov row,eax
|
||||
mov ebx,leftPx
|
||||
roof_slope_prepare:
|
||||
mov eax,cityRayX3D[ebx*4]
|
||||
cmp axis,0
|
||||
je @F
|
||||
mov eax,cityRayY3D[ebx*4]
|
||||
@@:
|
||||
imul slope
|
||||
shrd eax,edx,16
|
||||
mov roofSlopeRay3D[ebx*4],eax
|
||||
inc ebx
|
||||
cmp ebx,rightPx
|
||||
jbe roof_slope_prepare
|
||||
roof_slope_row:
|
||||
mov eax,viewHorizon3D
|
||||
sub eax,row
|
||||
shl eax,16
|
||||
cdq
|
||||
mov ecx,SCREEN_H
|
||||
idiv ecx
|
||||
mov rayZ,eax
|
||||
mov eax,row
|
||||
imul eax,SCREEN_W*4
|
||||
mov ebx,leftPx
|
||||
lea edi,[eax+ebx*4]
|
||||
mov column,ebx
|
||||
roof_slope_pixel:
|
||||
mov ebx,column
|
||||
mov ecx,roofSlopeRay3D[ebx*4]
|
||||
sub ecx,rayZ
|
||||
mov denominator,ecx
|
||||
mov eax,ecx
|
||||
cdq
|
||||
xor eax,edx
|
||||
sub eax,edx
|
||||
cmp eax,64
|
||||
jb roof_slope_advance
|
||||
shl eax,10
|
||||
mov along,eax
|
||||
mov eax,numerator
|
||||
cdq
|
||||
xor eax,edx
|
||||
sub eax,edx
|
||||
cmp eax,along
|
||||
jae roof_slope_advance
|
||||
mov eax,numerator
|
||||
cdq
|
||||
shld edx,eax,16
|
||||
shl eax,16
|
||||
idiv denominator
|
||||
cmp eax,4096
|
||||
jle roof_slope_advance
|
||||
cmp eax,cityDepthBuffer3D[edi]
|
||||
jae roof_slope_advance
|
||||
mov depth,eax
|
||||
imul cityRayX3D[ebx*4]
|
||||
shrd eax,edx,16
|
||||
add eax,playerX
|
||||
cmp eax,x0
|
||||
jl roof_slope_advance
|
||||
cmp eax,x1
|
||||
jge roof_slope_advance
|
||||
mov wx,eax
|
||||
sub eax,x0
|
||||
sar eax,9
|
||||
mov ecx,textureW
|
||||
dec ecx
|
||||
and eax,ecx
|
||||
mov sourceU,eax
|
||||
mov eax,depth
|
||||
imul cityRayY3D[ebx*4]
|
||||
shrd eax,edx,16
|
||||
add eax,playerY
|
||||
cmp eax,y0
|
||||
jl roof_slope_advance
|
||||
cmp eax,y1
|
||||
jge roof_slope_advance
|
||||
mov wy,eax
|
||||
cmp roofSlopeMask3D,0
|
||||
je roof_slope_sample
|
||||
mov eax,wx
|
||||
sub eax,roofSlopeCentreX3D
|
||||
cdq
|
||||
xor eax,edx
|
||||
sub eax,edx
|
||||
imul roofSlopeHalfY3D
|
||||
shrd eax,edx,16
|
||||
mov along,eax
|
||||
mov eax,wy
|
||||
sub eax,roofSlopeCentreY3D
|
||||
cdq
|
||||
xor eax,edx
|
||||
sub eax,edx
|
||||
imul roofSlopeHalfX3D
|
||||
shrd eax,edx,16
|
||||
cmp roofSlopeMask3D,1
|
||||
jne @F
|
||||
cmp eax,along
|
||||
ja roof_slope_advance
|
||||
jmp roof_slope_sample
|
||||
@@:
|
||||
cmp eax,along
|
||||
jb roof_slope_advance
|
||||
roof_slope_sample:
|
||||
mov eax,wy
|
||||
sub eax,y0
|
||||
sar eax,9
|
||||
mov ecx,textureH
|
||||
dec ecx
|
||||
and eax,ecx
|
||||
imul eax,textureW
|
||||
add eax,sourceU
|
||||
mov esi,texture
|
||||
mov eax,[esi+eax*4]
|
||||
invoke ShadeRoofCityPixel3D,eax,depth,light,material
|
||||
mov ecx,edi
|
||||
shr ecx,2
|
||||
mov edx,roofPixelGlow3D
|
||||
mov roofGlowMask3D[ecx],dl
|
||||
mov screenBuffer[edi],eax
|
||||
mov eax,depth
|
||||
mov cityDepthBuffer3D[edi],eax
|
||||
mov eax,edi
|
||||
shr eax,2
|
||||
mov roofCityMask3D[eax],1
|
||||
roof_slope_advance:
|
||||
add edi,4
|
||||
inc column
|
||||
mov eax,column
|
||||
cmp eax,rightPx
|
||||
jbe roof_slope_pixel
|
||||
inc row
|
||||
mov eax,row
|
||||
cmp eax,bottomPx
|
||||
jbe roof_slope_row
|
||||
roof_slope_done:
|
||||
ret
|
||||
DrawRoofSlope3D ENDP
|
||||
|
||||
DrawRoofPyramid3D PROC uses ebx esi centreX:DWORD,centreY:DWORD,baseZ:DWORD,halfX:DWORD,halfY:DWORD,height:DWORD,mat:DWORD
|
||||
local x0:DWORD,x1:DWORD,y0:DWORD,y1:DWORD,topZ:DWORD,down:DWORD
|
||||
mov eax,centreX
|
||||
mov roofSlopeCentreX3D,eax
|
||||
sub eax,halfX
|
||||
mov x0,eax
|
||||
mov eax,centreX
|
||||
add eax,halfX
|
||||
mov x1,eax
|
||||
mov eax,centreY
|
||||
mov roofSlopeCentreY3D,eax
|
||||
sub eax,halfY
|
||||
mov y0,eax
|
||||
mov eax,centreY
|
||||
add eax,halfY
|
||||
mov y1,eax
|
||||
mov eax,halfX
|
||||
mov roofSlopeHalfX3D,eax
|
||||
mov eax,halfY
|
||||
mov roofSlopeHalfY3D,eax
|
||||
mov eax,baseZ
|
||||
add eax,height
|
||||
mov topZ,eax
|
||||
mov eax,height
|
||||
neg eax
|
||||
mov down,eax
|
||||
mov roofSlopeMask3D,1
|
||||
invoke DrawRoofSlope3D,0,x0,centreX,y0,y1,baseZ,height,mat
|
||||
invoke DrawRoofSlope3D,0,centreX,x1,y0,y1,topZ,down,mat
|
||||
mov roofSlopeMask3D,2
|
||||
invoke DrawRoofSlope3D,1,x0,x1,y0,centreY,baseZ,height,mat
|
||||
invoke DrawRoofSlope3D,1,x0,x1,centreY,y1,topZ,down,mat
|
||||
mov roofSlopeMask3D,0
|
||||
ret
|
||||
DrawRoofPyramid3D ENDP
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
"""Exact clock-board geometry plus fixed-grid full-arm animation packing."""
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
import math
|
||||
import random
|
||||
import struct
|
||||
|
|
@ -19,58 +20,76 @@ def pack(name, frames):
|
|||
|
||||
def board():
|
||||
rng = random.Random(301)
|
||||
image = Image.new("RGBA", (512, 512))
|
||||
scale = 2
|
||||
center = 256*scale
|
||||
image = Image.new("RGBA", (512*scale, 512*scale))
|
||||
pixels = image.load()
|
||||
for y in range(512):
|
||||
for x in range(512):
|
||||
dx, dy = x-256, 256-y
|
||||
for y in range(image.height):
|
||||
for x in range(image.width):
|
||||
dx, dy = (x-center)/scale, (center-y)/scale
|
||||
radius = math.hypot(dx, dy)
|
||||
angle = math.atan2(dx, dy)
|
||||
sector = int(((angle + math.pi/20) % math.tau) / (math.tau/20))
|
||||
red = sector % 2 == 0
|
||||
color = (30, 36, 39)
|
||||
if radius < 170:
|
||||
color = (35, 38, 37) if red else (214, 206, 178)
|
||||
color = (23, 29, 31) if red else (232, 225, 201)
|
||||
if 99 <= radius <= 107 or 162 <= radius:
|
||||
color = (174, 37, 47) if red else (28, 112, 87)
|
||||
color = (191, 42, 55) if red else (24, 145, 103)
|
||||
if radius <= 15.9:
|
||||
color = (28, 112, 87)
|
||||
color = (24, 145, 103)
|
||||
if radius <= 6.35:
|
||||
color = (187, 37, 47)
|
||||
elif radius < 210:
|
||||
color = (20, 24, 25)
|
||||
color = (191, 42, 55)
|
||||
elif radius < 220:
|
||||
color = (10, 14, 17)
|
||||
elif radius < 240:
|
||||
color = (72, 29, 35)
|
||||
elif radius < 248:
|
||||
color = (31, 37, 41)
|
||||
else:
|
||||
continue
|
||||
grain = rng.randrange(-11, 12) if radius < 170 else rng.randrange(-3, 4)
|
||||
grain = rng.randrange(-2, 3)
|
||||
pixels[x, y] = tuple(max(0, min(255, c+grain)) for c in color) + (255,)
|
||||
draw = ImageDraw.Draw(image)
|
||||
wire = (171, 178, 173, 255)
|
||||
wire = (139, 158, 161, 255)
|
||||
for r in (6.35, 15.9, 99, 107, 162, 170):
|
||||
draw.ellipse((256-r, 256-r, 256+r, 256+r), outline=wire, width=1)
|
||||
bounds = tuple(round(v*scale) for v in (256-r, 256-r, 256+r, 256+r))
|
||||
draw.ellipse(bounds, outline=wire, width=2*scale)
|
||||
for index in range(20):
|
||||
theta = (index*18-9)*math.pi/180
|
||||
draw.line([(256+math.sin(theta)*16, 256-math.cos(theta)*16),
|
||||
(256+math.sin(theta)*170, 256-math.cos(theta)*170)], fill=wire)
|
||||
font = ImageFont.truetype("C:/Windows/Fonts/arialbd.ttf", 23)
|
||||
ends = [(round((256+math.sin(theta)*r)*scale),
|
||||
round((256-math.cos(theta)*r)*scale)) for r in (16,170)]
|
||||
draw.line(ends, fill=wire, width=2*scale)
|
||||
font = ImageFont.truetype("C:/Windows/Fonts/arialbd.ttf", 32*scale)
|
||||
labels = []
|
||||
for index, value in enumerate(SECTORS):
|
||||
theta = index*math.tau/20
|
||||
draw.text((256+math.sin(theta)*190, 256-math.cos(theta)*190), str(value),
|
||||
font=font, fill=(227, 230, 220), anchor="mm", stroke_width=1)
|
||||
draw.ellipse((12, 12, 500, 500), outline=(104, 114, 120), width=3)
|
||||
image.save(ART / "board-v1.png")
|
||||
point = ((256+math.sin(theta)*198)*scale, (256-math.cos(theta)*198)*scale)
|
||||
labels.append(draw.textbbox(point, str(value), font=font, anchor="mm"))
|
||||
draw.text(point, str(value),
|
||||
font=font, fill=(249, 247, 232), anchor="mm")
|
||||
for i, a in enumerate(labels):
|
||||
for b in labels[i+1:]:
|
||||
assert a[2] <= b[0] or b[2] <= a[0] or a[3] <= b[1] or b[3] <= a[1], "Overlapping board numbers"
|
||||
draw.ellipse((12*scale, 12*scale, 500*scale, 500*scale),
|
||||
outline=(104, 114, 120), width=3*scale)
|
||||
image = image.resize((512,512), Image.Resampling.LANCZOS)
|
||||
image.save(ART / "board-v2.png")
|
||||
return image
|
||||
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--board-only", action="store_true")
|
||||
args = parser.parse_args()
|
||||
ART.mkdir(parents=True, exist_ok=True)
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
pack("board-v1.bin", [board()])
|
||||
pack("board-v2.bin", [board()])
|
||||
if args.board_only:
|
||||
print("Packed sharper 512px board: unchanged footprint and scoring radii.")
|
||||
return
|
||||
wall = Image.open(ART / "lounge-wall-v1.png").convert("RGBA")
|
||||
pack("lounge-wall-v1.bin", [wall.resize((512,512), Image.Resampling.LANCZOS)])
|
||||
anchors = []
|
||||
|
|
|
|||
102
tools/generators/pack_fortune_orb.py
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
"""Offline orb tracking metadata and antialiased numeral sprites; no runtime Python."""
|
||||
from pathlib import Path
|
||||
import json
|
||||
import struct
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image, ImageDraw, ImageFilter, ImageFont
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
OUT = ROOT / "assets/animations/fortune"
|
||||
ART = ROOT / "assets/sprites/fortune"
|
||||
PREVIEW = ROOT / "build/hall-expansion"
|
||||
|
||||
|
||||
def load_orbs():
|
||||
raw = (OUT / "orb-mist-v1.bin").read_bytes()
|
||||
magic, width, height, count, length = struct.unpack_from("<4s4I", raw)
|
||||
assert magic == b"ANM1" and count == 16
|
||||
assert length == width * height * count * 4 == len(raw) - 20
|
||||
stride = width * height * 4
|
||||
return [Image.frombytes("RGBA", (width, height), raw[20+i*stride:20+(i+1)*stride],
|
||||
"raw", "BGRA") for i in range(count)]
|
||||
|
||||
|
||||
def sphere_geometry(frame):
|
||||
# Fit the upper glass silhouette, explicitly excluding the metal pedestal.
|
||||
alpha = np.asarray(frame.getchannel("A"))
|
||||
rows, midpoints, half_widths = [], [], []
|
||||
for y in range(12, round(frame.height * .70)):
|
||||
xs = np.flatnonzero(alpha[y] >= 170)
|
||||
if len(xs) < frame.width * .35:
|
||||
continue
|
||||
left, right = int(xs[0]), int(xs[-1])
|
||||
rows.append(y)
|
||||
midpoints.append((left + right) / 2)
|
||||
half_widths.append((right - left) / 2)
|
||||
a, b, c = np.polyfit(rows, np.square(half_widths), 2)
|
||||
assert a < 0
|
||||
cy = -b / (2*a)
|
||||
cx = float(np.median(midpoints))
|
||||
rx = float(np.sqrt(c - b*b/(4*a)))
|
||||
ry = float(rx / np.sqrt(-a))
|
||||
assert 70 < cx < 125 and 60 < cy < 115
|
||||
assert 55 < rx < 95 and 55 < ry < 95
|
||||
return {"cx": cx, "cy": cy, "rx": rx, "ry": ry}
|
||||
|
||||
|
||||
def numeral(digit, font):
|
||||
factor = 4
|
||||
mask = Image.new("L", (32*factor, 48*factor))
|
||||
draw = ImageDraw.Draw(mask)
|
||||
box = draw.textbbox((0, 0), str(digit), font=font)
|
||||
x = (mask.width-(box[2]-box[0]))/2-box[0]
|
||||
y = (mask.height-(box[3]-box[1]))/2-box[1]
|
||||
draw.text((round(x), round(y)), str(digit), font=font, fill=255)
|
||||
mask = mask.resize((32, 48), Image.Resampling.LANCZOS)
|
||||
shadow = Image.new("RGBA", mask.size, (8, 30, 66, 0))
|
||||
shadow.putalpha(mask.filter(ImageFilter.MaxFilter(3)).point(lambda a: round(a*.65)))
|
||||
halo = Image.new("RGBA", mask.size, (65, 168, 243, 0))
|
||||
halo.putalpha(mask.filter(ImageFilter.GaussianBlur(2.0)).point(lambda a: round(a*.65)))
|
||||
ink = Image.new("RGBA", mask.size, (200, 235, 255, 0))
|
||||
ink.putalpha(mask.point(lambda a: round(a*.90)))
|
||||
return Image.alpha_composite(Image.alpha_composite(halo, shadow), ink)
|
||||
|
||||
|
||||
def main():
|
||||
PREVIEW.mkdir(parents=True, exist_ok=True)
|
||||
orbs = load_orbs()
|
||||
geometry = [sphere_geometry(frame) for frame in orbs]
|
||||
# Q8 offsets in the exact 128px runtime drawing rectangle, not its centre.
|
||||
lines = ["; Generated by tools/generators/pack_fortune_orb.py; glass only, pedestal excluded."]
|
||||
for name, key in (("fortuneOrbAnchorX3D", "cx"), ("fortuneOrbAnchorY3D", "cy")):
|
||||
values = [round(item[key] * 128 / 192 * 256) for item in geometry]
|
||||
lines.append(name + " dd " + ",".join(map(str, values)))
|
||||
(ROOT / "src_graphics/fortune_orb_art.inc").write_text("\n".join(lines)+"\n", encoding="ascii")
|
||||
font = ImageFont.truetype("C:/Windows/Fonts/cambria.ttc", 34*4)
|
||||
digits = [numeral(i, font) for i in range(10)]
|
||||
payload = b"".join(frame.tobytes("raw", "BGRA") for frame in digits)
|
||||
(OUT / "orb-numerals-v1.bin").write_bytes(struct.pack("<4s4I", b"ANM1", 32, 48, 10,
|
||||
len(payload)) + payload)
|
||||
sheet = Image.new("RGBA", (320, 48))
|
||||
for i, digit in enumerate(digits):
|
||||
sheet.alpha_composite(digit, (32*i, 0))
|
||||
sheet.save(ART / "orb-numerals-v1.png")
|
||||
(ART / "orb-tracking-v1.json").write_text(json.dumps({"canvas": [192, 192],
|
||||
"font": "Cambria Regular, rasterized at 4x; no font needed at runtime",
|
||||
"frames": geometry}, indent=2)+"\n", encoding="ascii")
|
||||
sheet = Image.new("RGB", (192*4, 192*4), (5, 15, 28))
|
||||
for i, (frame, item) in enumerate(zip(orbs, geometry)):
|
||||
debug = frame.copy()
|
||||
draw = ImageDraw.Draw(debug)
|
||||
x, y = item["cx"], item["cy"]
|
||||
draw.ellipse((x-item["rx"], y-item["ry"], x+item["rx"], y+item["ry"]), outline="yellow")
|
||||
draw.line((x-5, y, x+5, y), fill="yellow")
|
||||
draw.line((x, y-5, x, y+5), fill="yellow")
|
||||
sheet.paste(debug, ((i%4)*192, (i//4)*192), debug)
|
||||
sheet.save(PREVIEW / "fortune-orb-tracking.png")
|
||||
print("Packed 10 antialiased Cambria numerals and 16 measured glass-centre anchors.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
419
tools/generators/pack_rooftop.py
Normal file
|
|
@ -0,0 +1,419 @@
|
|||
"""Offline rooftop atlas/map compiler. Game rendering and travel remain MASM."""
|
||||
from pathlib import Path
|
||||
import json
|
||||
import math
|
||||
import struct
|
||||
import numpy as np
|
||||
import cv2
|
||||
from PIL import Image
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
ART = ROOT / "assets/sprites/rooftop"
|
||||
OUT = ROOT / "assets/animations/rooftop"
|
||||
|
||||
|
||||
def grid(path, columns, rows):
|
||||
source = Image.open(ART/path).convert("RGBA")
|
||||
return [source.crop((round(x*source.width/columns), round(y*source.height/rows),
|
||||
round((x+1)*source.width/columns), round((y+1)*source.height/rows)))
|
||||
.resize((256, 256), Image.Resampling.LANCZOS)
|
||||
for y in range(rows) for x in range(columns)]
|
||||
|
||||
|
||||
def pack(name, frames):
|
||||
w, h = frames[0].size
|
||||
payload = b"".join(frame.tobytes("raw", "BGRA") for frame in frames)
|
||||
(OUT/name).write_bytes(struct.pack("<4s4I", b"ANM1", w, h, len(frames), len(payload))+payload)
|
||||
|
||||
|
||||
def clean_hands(frame):
|
||||
pixels = np.asarray(frame).copy()
|
||||
mask = (pixels[:,:,3] >= 128).astype(np.uint8)
|
||||
mask = cv2.morphologyEx(mask,cv2.MORPH_OPEN,np.ones((3,3),np.uint8))
|
||||
count,labels,stats,_ = cv2.connectedComponentsWithStats(mask)
|
||||
keep = [i for i in range(1,count) if stats[i,cv2.CC_STAT_AREA] > 2500]
|
||||
support = np.isin(labels,keep).astype(np.uint8)
|
||||
support = cv2.erode(support,np.ones((3,3),np.uint8))
|
||||
pixels[:,:,3] = support*255
|
||||
pixels[support == 0,:3] = 0
|
||||
return Image.fromarray(pixels)
|
||||
|
||||
|
||||
def night_facade(frame, seed):
|
||||
"""Identify window apertures; mark lit glass with FF alpha and unlit walls FE."""
|
||||
pixels = np.asarray(frame).copy()
|
||||
rgb = pixels[:,:,:3].astype(np.int16)
|
||||
mask = ((rgb.max(axis=2)<145) & (rgb[:,:,2]>rgb[:,:,0]-10)).astype(np.uint8)*255
|
||||
mask = cv2.morphologyEx(mask,cv2.MORPH_CLOSE,np.ones((7,5),np.uint8))
|
||||
contours,_ = cv2.findContours(mask,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
|
||||
pixels[:,:,3] = 254
|
||||
rng = np.random.default_rng(seed)
|
||||
count = 0
|
||||
for contour in contours:
|
||||
x,y,w,h = cv2.boundingRect(contour)
|
||||
if not (8 <= w <= 110 and 14 <= h <= 180 and .8 <= h/w <= 4):
|
||||
continue
|
||||
if rng.random() > .48:
|
||||
continue
|
||||
region = pixels[y+2:y+h-2,x+2:x+w-2]
|
||||
source = region[:,:,:3].astype(np.int16)
|
||||
glass = (source[:,:,2]>source[:,:,0]+4) & (source.max(axis=2)<155)
|
||||
if glass.sum() < 15:
|
||||
continue
|
||||
yy,xx = np.mgrid[:h-4,:w-4]
|
||||
glow = np.clip(1-((xx-(w-4)/2)/(w/2))**2*.22, .5, 1)
|
||||
warmth = np.array([210,148,76])*rng.uniform(.7,1.1)
|
||||
lit = np.clip(glow[:,:,None]*warmth,0,255).astype(np.uint8)
|
||||
region[:,:,:3][glass] = lit[glass]
|
||||
region[:,:,3][glass] = 255
|
||||
count += 1
|
||||
return Image.fromarray(pixels),count
|
||||
|
||||
|
||||
def main():
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
sky = Image.open(ART/"sunset-sky-v1.png").convert("RGBA").resize((2048,512),Image.Resampling.LANCZOS)
|
||||
# A short symmetric overlap makes the cylindrical wrap continuous.
|
||||
original = sky.copy()
|
||||
for x in range(32):
|
||||
mixed = Image.blend(original.crop((x,0,x+1,512)),
|
||||
original.crop((2047-x,0,2048-x,512)), (32-x)/64)
|
||||
sky.paste(mixed, (x,0))
|
||||
mixed = Image.blend(original.crop((2047-x,0,2048-x,512)),
|
||||
original.crop((x,0,x+1,512)), (32-x)/64)
|
||||
sky.paste(mixed, (2047-x,0))
|
||||
pack("panorama-v1.bin", [sky.crop((i*512,0,(i+1)*512,512)) for i in range(4)])
|
||||
night = Image.open(ART/"night-sky-v1.png").convert("RGBA").resize((2048,512),Image.Resampling.LANCZOS)
|
||||
pack("night-v1.bin",[night.crop((i*512,0,(i+1)*512,512)) for i in range(4)])
|
||||
hands = Image.open(ART/"rail-hands-v1.png").convert("RGBA")
|
||||
poses = [hands.crop((round(x*hands.width/4),round(y*hands.height/2),
|
||||
round((x+1)*hands.width/4),round((y+1)*hands.height/2)))
|
||||
.resize((640,320),Image.Resampling.LANCZOS)
|
||||
for y in range(2) for x in range(4)]
|
||||
# Offline bidirectional flow supplies in-between poses; playback is pure MASM.
|
||||
coordinates = np.mgrid[:320,:640][::-1].transpose(1,2,0).astype(np.float32)
|
||||
smooth = []
|
||||
for first,second in zip(poses,poses[1:]):
|
||||
a,b = np.asarray(first),np.asarray(second)
|
||||
ag = cv2.cvtColor(a,cv2.COLOR_RGBA2GRAY)
|
||||
bg = cv2.cvtColor(b,cv2.COLOR_RGBA2GRAY)
|
||||
forward = cv2.calcOpticalFlowFarneback(ag,bg,None,.5,5,35,6,7,1.5,0)
|
||||
backward = cv2.calcOpticalFlowFarneback(bg,ag,None,.5,5,35,6,7,1.5,0)
|
||||
for index in range(4):
|
||||
q = index/4
|
||||
aw = cv2.remap(a,coordinates-forward*q,None,cv2.INTER_LINEAR,
|
||||
borderMode=cv2.BORDER_CONSTANT)
|
||||
bw = cv2.remap(b,coordinates-backward*(1-q),None,cv2.INTER_LINEAR,
|
||||
borderMode=cv2.BORDER_CONSTANT)
|
||||
smooth.append(Image.fromarray(cv2.addWeighted(aw,1-q,bw,q,0)))
|
||||
smooth += [poses[-1]]*4
|
||||
assert len(smooth) == 32
|
||||
pack("rail-hands-v1.bin",[clean_hands(frame) for frame in smooth])
|
||||
materials = grid("materials-v1.png",4,2)
|
||||
pack("materials-v1.bin",materials)
|
||||
panels = grid("panels-v1.png",4,2)[:4]
|
||||
# One moving leaf, not a photograph of a whole elevator on each leaf.
|
||||
panels[0] = panels[0].crop((72,16,126,235)).resize((256,256),Image.Resampling.LANCZOS)
|
||||
# Use only the fluted facade, not the reference's pictured skyline above it.
|
||||
panels[2] = panels[2].crop((0,116,256,240)).resize((256,256),Image.Resampling.LANCZOS)
|
||||
cutouts = grid("cutouts-v1.png",2,2)
|
||||
for frame in cutouts:
|
||||
assert frame.getchannel("A").getextrema()[0] == 0, "cutouts require actual alpha"
|
||||
sofas = Image.open(ART/"sofa-views-v1.png").convert("RGBA")
|
||||
occupied = np.sum(np.asarray(sofas.getchannel("A")) > 128,axis=0) > 10
|
||||
edges = np.flatnonzero(np.diff(np.r_[False,occupied,False]))
|
||||
spans = [(int(a),int(b)) for a,b in zip(edges[::2],edges[1::2]) if b-a > 80]
|
||||
assert len(spans) == 3, spans
|
||||
boxes = []
|
||||
for lo,hi in spans:
|
||||
box = sofas.crop((lo,0,hi,sofas.height)).getchannel("A").point(lambda a:255 if a>128 else 0).getbbox()
|
||||
boxes.append((lo,box[1],hi,box[3]))
|
||||
top = min(box[1] for box in boxes)
|
||||
bottom = max(box[3] for box in boxes)
|
||||
views = [sofas.crop((box[0],top,box[2],bottom)).resize((256,256),Image.Resampling.LANCZOS)
|
||||
for box in boxes]
|
||||
chairs = Image.open(ART/"chair-views-v1.png").convert("RGBA")
|
||||
split = round(chairs.width*.55)
|
||||
boxes = [(0,0,split,chairs.height//2),(split,0,chairs.width,chairs.height//2),
|
||||
(0,chairs.height//2,split,chairs.height),(split,chairs.height//2,chairs.width,chairs.height)]
|
||||
chair_views = []
|
||||
for box in boxes:
|
||||
frame = chairs.crop(box)
|
||||
tight = frame.getchannel("A").point(lambda a:255 if a>128 else 0).getbbox()
|
||||
chair_views.append(frame.crop(tight).resize((256,256),Image.Resampling.LANCZOS))
|
||||
pack("details-v1.bin",panels+cutouts+views+chair_views)
|
||||
facade = Image.open(ART/"city-facades-v1.png").convert("RGBA")
|
||||
city = [facade.crop((round(x*facade.width/4),round(y*facade.height/2),
|
||||
round((x+1)*facade.width/4),round((y+1)*facade.height/2)))
|
||||
.resize((512,512),Image.Resampling.LANCZOS)
|
||||
for y in range(2) for x in range(4)]
|
||||
# Foliage-only tuft, not a planter photographed on a vertical card.
|
||||
city.append(cutouts[1].crop((0,0,256,160)).resize((512,512),Image.Resampling.LANCZOS))
|
||||
earth = np.asarray(materials[1].resize((512,512))).copy()
|
||||
earth[:,:,:3] = (earth[:,:,:3].astype(np.uint16)*np.array([37,31,24])//100).astype(np.uint8)
|
||||
city.append(Image.fromarray(earth))
|
||||
city.append(Image.open(ART/"roof-zinc-v1.png").convert("RGBA").resize((512,512),Image.Resampling.LANCZOS))
|
||||
# Existing high-detail stone is also a road aggregate; water is a generated
|
||||
# native texture below. Neither is a photograph of a whole city object.
|
||||
road = np.asarray(materials[1].resize((512,512))).copy()
|
||||
road[:,:,:3] = (road[:,:,:3].astype(np.uint16)*45//100).astype(np.uint8)
|
||||
city.append(Image.fromarray(road))
|
||||
yy,xx = np.mgrid[:512,:512]
|
||||
ripple = (np.sin(xx*.17+np.sin(yy*.09)*2)+np.sin(yy*.31))*4
|
||||
water = np.zeros((512,512,4),dtype=np.uint8)
|
||||
for channel,base in enumerate((24,55,72)):
|
||||
water[:,:,channel] = np.clip(base+ripple,0,255).astype(np.uint8)
|
||||
water[:,:,3] = 255
|
||||
city.append(Image.fromarray(water))
|
||||
modern = Image.open(ART/"modern-facades-v1.png").convert("RGBA")
|
||||
city += [modern.crop((round(x*modern.width/4),round(y*modern.height/2),
|
||||
round((x+1)*modern.width/4),round((y+1)*modern.height/2)))
|
||||
.resize((512,512),Image.Resampling.LANCZOS)
|
||||
for y in range(2) for x in range(4)]
|
||||
noise = np.random.default_rng(701).integers(-8,9,size=(512,512,1))
|
||||
lawn = np.empty((512,512,4),dtype=np.uint8)
|
||||
lawn[:,:,:3] = np.clip(np.array([39,68,43])+noise,0,255)
|
||||
lawn[:,:,3] = 255
|
||||
city.append(Image.fromarray(lawn))
|
||||
pack("city-v1.bin",city)
|
||||
night_city,lit_windows = [],0
|
||||
for index,frame in enumerate(city):
|
||||
if index < 8 or 13 <= index < 21:
|
||||
frame,count = night_facade(frame,2000+index)
|
||||
lit_windows += count
|
||||
night_city.append(frame)
|
||||
pack("city-night-v1.bin",night_city)
|
||||
count = pack_city_district()
|
||||
pack_roof_map()
|
||||
print(f"Packed roof: sunset/night, {lit_windows} distinct lit window apertures, 32 hand frames, {count} buildings.")
|
||||
|
||||
|
||||
def pack_city_district():
|
||||
# A connected street/courtyard district, not a ring of independent props.
|
||||
blocks = []
|
||||
lots,parks = [],[]
|
||||
for gy in range(-5,6):
|
||||
for gx in range(-5,6):
|
||||
x,y = 20+gx*36,11+gy*36
|
||||
if gx == 0 and gy == 0:
|
||||
continue
|
||||
# The cross-city canal has continuous quays, not houses over water.
|
||||
if y+14 > 54 and y-14 < 82:
|
||||
continue
|
||||
# Two open squares reserve space around the dome and golden spire.
|
||||
if (gx,gy) in ((0,3),(4,0)):
|
||||
continue
|
||||
key = (gx*17+gy*31)%37
|
||||
if key%11 == 0 or (gx,gy) in ((1,0),(-1,0)):
|
||||
parks.append((x,y))
|
||||
continue
|
||||
facade = 186+(gx*3+gy)%8
|
||||
if max(abs(gx),abs(gy)) <= 1:
|
||||
top = -60+6+key%6
|
||||
facade = 203
|
||||
wings = [(x-6,x+5,y-5,y+5)]
|
||||
elif key%7 == 0:
|
||||
top = -60+24+key%17
|
||||
facade = 199+(key%2)*5
|
||||
wings = [(x-8,x+8,y-10,y+10)]
|
||||
else:
|
||||
top = -60+12+key%16
|
||||
wings = [(x-14,x+14,y-14,y-7),(x-14,x+14,y+7,y+14),
|
||||
(x-14,x-7,y-7,y+7),(x+7,x+14,y-7,y+7)]
|
||||
if key%3 == 0:
|
||||
wings = wings[:3]
|
||||
elif key%5 == 0:
|
||||
wings = wings[1:3]
|
||||
lots.append({"centre":[x,y],"courtyard":[x-7,x+7,y-7,y+7],
|
||||
"facade":facade,"height":top+60})
|
||||
for index,wing in enumerate(wings):
|
||||
blocks.append((*wing,top+(index%2)*.3,facade,index))
|
||||
# Distant quarters are aggregated geometry, not a scaled city photograph.
|
||||
for gy in range(-8,9):
|
||||
for gx in range(-8,9):
|
||||
if max(abs(gx),abs(gy)) <= 3:
|
||||
continue
|
||||
x,y = 20+gx*64,11+gy*64
|
||||
if y+24 > 54 and y-24 < 82:
|
||||
continue
|
||||
key = (gx*5+gy*11)%29
|
||||
top = -60+10+key
|
||||
half = 17+key%10
|
||||
blocks.append((x-half,x+half,y-24,y+24,top,186+(gx+gy)%8,4))
|
||||
towers = [(320,-130,90),(355,-165,126),(390,-125,108),(425,-195,155),
|
||||
(460,-145,102),(345,-225,115),(405,-260,132),(475,-240,88)]
|
||||
for index,(x,y,height) in enumerate(towers):
|
||||
blocks.append((x-8,x+8,y-10,y+10,-60+height,200+index%3,2))
|
||||
blocks.append((x-5,x+5,y-7,y+7,-60+height+7,200+index%3,2))
|
||||
# The eastern avenue opens into a broad river, not an endless paved corridor.
|
||||
blocks = [b for b in blocks if not (b[1] > 68 and b[2] < 81 and b[3] > 26)]
|
||||
# Far riverfront quarters break the empty, perfectly straight horizon.
|
||||
for side in (-1,1):
|
||||
for i in range(10):
|
||||
x = 570+i*34
|
||||
y = 6-(i%3)*8 if side < 0 else 106+(i%4)*9
|
||||
h = 16+(i*7)%24
|
||||
blocks.append((x-11,x+11,y-10,y+10,-60+h,186+i%8,4))
|
||||
# Canal clearance removes water footprints, not entire empty city blocks.
|
||||
waterfront = []
|
||||
for gx in range(-13,2):
|
||||
x = 20+gx*36
|
||||
for bank in (0,1):
|
||||
key = (gx*19+bank*7)%31
|
||||
front,back = (53,39-key%4) if bank == 0 else (82,94+key%3)
|
||||
y0,y1 = sorted((front,back))
|
||||
mid = x-2+key%5
|
||||
for index,(x0,x1) in enumerate(((x-13,mid-.45),(mid+.45,x+13))):
|
||||
top = -60+12+(key+index*3)%11
|
||||
waterfront.append((x0,x1,y0,y1,top,186+(gx+bank*3+index)%8,5))
|
||||
blocks.extend(waterfront)
|
||||
blocks.sort(key=lambda b:(((b[0]+b[1])/2-20)**2+((b[2]+b[3])/2-11)**2))
|
||||
# Ground-only baked illumination follows the same real building footprints.
|
||||
elevation = np.zeros((512,512),dtype=np.float32)
|
||||
for x0,x1,y0,y1,z,_,_ in blocks:
|
||||
loX,hiX = max(0,math.floor(x0/2)+256),min(512,math.ceil(x1/2)+256)
|
||||
loY,hiY = max(0,math.floor(y0/2)+256),min(512,math.ceil(y1/2)+256)
|
||||
if loX < hiX and loY < hiY:
|
||||
elevation[loY:hiY,loX:hiX] = np.maximum(elevation[loY:hiY,loX:hiX],z+60)
|
||||
occlusion = np.zeros_like(elevation)
|
||||
padded = np.pad(elevation,128)
|
||||
for step in range(1,100):
|
||||
sx,sy = round(-step*.89),round(step*.45)
|
||||
ray_height = step*2*math.tan(math.radians(18))
|
||||
blocked = padded[128+sy:640+sy,128+sx:640+sx]-ray_height
|
||||
occlusion = np.maximum(occlusion,blocked)
|
||||
shadow = cv2.GaussianBlur(np.where(occlusion>0,155,250).astype(np.uint8),(5,5),1)
|
||||
day = np.empty((512,512,4),dtype=np.uint8)
|
||||
day[:,:,:3] = shadow[:,:,None]
|
||||
day[:,:,3] = 255
|
||||
lamps = []
|
||||
for gx in range(-5,6):
|
||||
for gy in range(-6,7):
|
||||
lamps.append((34.8+36*gx,11+30*gy))
|
||||
lamps += [(x,y) for x in range(-170,211,18) for y in (58.5,77.5)]
|
||||
lamps = [(x,y) for x,y in lamps if not (60 < y < 76 or (x > 68 and 29 < y < 76))]
|
||||
lamps += [(x,y) for x in range(86,650,18) for y in (27,78)]
|
||||
yy,xx = np.mgrid[:512,:512]
|
||||
world_x,world_y = (xx-256)*2,(yy-256)*2
|
||||
illumination = np.zeros((512,512,3),dtype=np.float32)+[24,30,46]
|
||||
for lx,ly in lamps:
|
||||
energy = np.exp(-((world_x-lx)**2+(world_y-ly)**2)/(2*5.5**2))
|
||||
illumination += energy[:,:,None]*np.array([215,150,64])
|
||||
if ly in (27,78,58.5,77.5):
|
||||
reflected = np.exp(-((world_x-lx)**2/(2*1.6**2)+(world_y-ly)**2/(2*13**2)))
|
||||
reflected *= ((world_y>=60)&(world_y<=76)) | ((world_x>=70)&(world_y>=29)&(world_y<=76))
|
||||
illumination += reflected[:,:,None]*np.array([180,125,60])
|
||||
night_light = np.empty((512,512,4),dtype=np.uint8)
|
||||
night_light[:,:,:3] = np.clip(illumination,0,255).astype(np.uint8)
|
||||
night_light[:,:,3] = 255
|
||||
glow = np.empty_like(night_light)
|
||||
glow[:,:,:3] = np.clip((illumination-np.array([24,30,46]))*.38,0,255).astype(np.uint8)
|
||||
glow[:,:,3] = 255
|
||||
pack("lighting-v1.bin",[Image.fromarray(day),Image.fromarray(night_light),Image.fromarray(glow)])
|
||||
district = ["; Generated city block footprints and rooftop-relative heights.",
|
||||
f"ROOF_BUILDING_COUNT equ {len(blocks)}", ".data", "roofDistrict3D LABEL DWORD"]
|
||||
for block in blocks:
|
||||
district.append(" dd "+",".join(str(round(v*65536)) if i<5 else str(v)
|
||||
for i,v in enumerate(block)))
|
||||
district += [f"ROOF_PARK_COUNT equ {len(parks)}","roofCityParks3D LABEL DWORD"]
|
||||
district += [f" dd {round(x*65536)},{round(y*65536)}" for x,y in parks]
|
||||
district += [f"ROOF_CITY_LAMP_COUNT equ {len(lamps)}","roofCityLamps3D LABEL DWORD"]
|
||||
district += [f" dd {round(x*65536)},{round(y*65536)}" for x,y in lamps]
|
||||
pieces = []
|
||||
def landmark_piece(kind,coords,material,pivot,target,scale=2):
|
||||
ox,oy = pivot
|
||||
tx,ty = target
|
||||
if kind == 0:
|
||||
a,b,c,d,e,f = coords
|
||||
coords = ((a-ox)*scale+tx,(b-ox)*scale+tx,(c-oy)*scale+ty,(d-oy)*scale+ty,
|
||||
(e+17)*scale-60,(f+17)*scale-60)
|
||||
else:
|
||||
a,b,c,d,e,f = coords
|
||||
coords = ((a-ox)*scale+tx,(b-oy)*scale+ty,(c+17)*scale-60,d*scale,e*scale,f*scale)
|
||||
pieces.append((kind,*[round(v*65536) for v in coords],material))
|
||||
dome = lambda kind,coords,mat: landmark_piece(kind,coords,mat,(56,47),(20,119))
|
||||
dome(0,(47,65,38,56,-17,2),193)
|
||||
dome(0,(50,62,41,53,2,5),168)
|
||||
for index in range(16):
|
||||
angle = index*math.tau/16
|
||||
x,y = 56+5*math.cos(angle),47+5*math.sin(angle)
|
||||
dome(0,(x,x+.35,y,y+.35,5,8),168)
|
||||
dome(0,(50,62,41,53,8,8.4),196)
|
||||
dome(1,(56,47,8.3,5.9,5.9,5),196)
|
||||
dome(0,(55.4,56.6,46.4,47.6,13,15),166)
|
||||
dome(0,(55.9,56.1,46.9,47.1,15,18),166)
|
||||
spire = lambda kind,coords,mat: landmark_piece(kind,coords,mat,(-16,-61),(164,11))
|
||||
spire(0,(-23,-9,-68,-54,-17,0),191)
|
||||
spire(0,(-19,-13,-64,-58,0,9),206)
|
||||
spire(0,(-18,-14,-63,-59,9,15),191)
|
||||
spire(2,(-16,-61,15,1.8,1.8,20),166)
|
||||
# The same modest quay geometry is consumed by both native renderers.
|
||||
quays = []
|
||||
def quay_piece(kind,coords,mat):
|
||||
piece = (kind,*[round(v*65536) for v in coords],mat)
|
||||
pieces.append(piece)
|
||||
quays.append((kind,*coords,mat))
|
||||
for y0,y1 in ((55,60),(76,81)):
|
||||
quay_piece(0,(-520,70,y0,y1,-62,-59.75),162)
|
||||
edge = 59.65 if y0 < 60 else 76
|
||||
for a,b in ((-520,-37.6),(-30.4,34.4),(41.6,70)):
|
||||
quay_piece(0,(a,b,edge,edge+.35,-59.75,-58.85),162)
|
||||
quay_piece(0,(a,b,edge-.04,edge+.39,-58.85,-58.7),168)
|
||||
for x in range(-184,68,18):
|
||||
if min(abs(x-b) for b in (-34,38)) < 6:
|
||||
continue
|
||||
for y in (56.7,79.3):
|
||||
quay_piece(0,(x-.12,x+.12,y-.12,y+.12,-59.75,-56.8),162)
|
||||
quay_piece(1,(x,y,-56.8,1.5,1.5,2.1),207)
|
||||
quay_piece(0,(x+3,x+5,y-.25,y+.25,-59.35,-59.15),196)
|
||||
for leg in (x+3.2,x+4.6):
|
||||
quay_piece(0,(leg,leg+.18,y-.2,y+.2,-59.75,-59.35),165)
|
||||
district += [f"ROOF_LANDMARK_PARTS equ {len(pieces)}","roofLandmarkParts3D LABEL DWORD"]
|
||||
district += [" dd "+",".join(map(str,piece)) for piece in pieces]
|
||||
fog = [min(190,round(d*180/(d+250))) for d in range(1024)]
|
||||
for start in range(0,1024,32):
|
||||
district.append(("roofFog3D" if start == 0 else " ")+" db "+
|
||||
",".join(map(str,fog[start:start+32])))
|
||||
district.append(".code")
|
||||
(ROOT/"src_graphics/rooftop_district_data.inc").write_text("\n".join(district)+"\n",encoding="ascii")
|
||||
(ART/"district-map-v1.json").write_text(json.dumps({"extent":[-520,560,-530,550],
|
||||
"casino":[10.9,29.1,.9,21.1],"street_spacing":36,"street_width":8,
|
||||
"street_level":-60,"canal":[-190,230,60,76],"bridges":[-34,38,110],
|
||||
"east_river":[70,1800,29,76],"river_bridges":[110,254,398],
|
||||
"waterfront_buildings":waterfront,"canal_quay_parts":quays,
|
||||
"lots":lots,"parks":parks,"lamps":lamps,"towers":towers,"buildings":blocks,
|
||||
"landmarks":{"dome":[20,119],"spire":[164,11]}},indent=2)+"\n",encoding="ascii")
|
||||
return len(blocks)
|
||||
|
||||
|
||||
def pack_roof_map():
|
||||
# Collision-only enclosure uses a valid legacy prepass material; the final
|
||||
# rooftop pass replaces its tall walls with low world-space railings.
|
||||
roof = [[102]*32 for _ in range(32)]
|
||||
for y in range(1,21):
|
||||
for x in range(11,29):
|
||||
roof[y][x] = 0
|
||||
lines = ["; Generated by tools/generators/pack_rooftop.py.", ".data"]
|
||||
for y,row in enumerate(roof):
|
||||
lines.append(("roofMap3D" if y == 0 else " ")+" db "+",".join(map(str,row)))
|
||||
columns = [round(math.atan((x/320-1)*.66)*2048/(2*math.pi)) for x in range(640)]
|
||||
for start in range(0,640,16):
|
||||
lines.append(("roofSkyColumns3D" if start == 0 else " ")+" dd "+
|
||||
",".join(map(str,columns[start:start+16])))
|
||||
(ROOT/"src_graphics/rooftop_map.inc").write_text("\n".join(lines)+"\n",encoding="ascii")
|
||||
(ART/"packed-v1.json").write_text(json.dumps({"panorama":[512,512,4],
|
||||
"materials":[256,256,8],"details":[256,256,15],"city":[512,512,22],
|
||||
"city-night":[512,512,22],"night":[512,512,4],"rail-hands":[640,320,32],
|
||||
"lighting":[512,512,3],"city_renderer":"world-space MASM geometry; sky only is raster",
|
||||
"source":"Built-in image generation; runtime geometry and interaction are assembly-only",
|
||||
"reference":"https://www.loftprojectetagi.ru/visitors/krysha/"},indent=2)+"\n",encoding="ascii")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--district-only",action="store_true")
|
||||
if parser.parse_args().district_only:
|
||||
print(f"Packed city district: {pack_city_district()} building parts")
|
||||
else:
|
||||
main()
|
||||
335
tools/generators/pack_rooftop_gl.py
Normal file
|
|
@ -0,0 +1,335 @@
|
|||
"""Offline city meshes. Playback, GL submission and compositing remain MASM."""
|
||||
from pathlib import Path
|
||||
import json
|
||||
import math
|
||||
import struct
|
||||
from collections import Counter
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
OUT = ROOT/"assets/animations/rooftop"
|
||||
ART = ROOT/"assets/sprites/rooftop"
|
||||
|
||||
|
||||
class Mesh:
|
||||
def __init__(self):
|
||||
self.triangles = []
|
||||
|
||||
def quad(self, material, points, uv=None, smooth=None):
|
||||
# Points use the game's X/Y ground plane and Z up. GL receives X/Z/Y.
|
||||
a,b,c = points[:3]
|
||||
u = [b[i]-a[i] for i in range(3)]
|
||||
v = [c[i]-a[i] for i in range(3)]
|
||||
normal = (u[1]*v[2]-u[2]*v[1],u[2]*v[0]-u[0]*v[2],u[0]*v[1]-u[1]*v[0])
|
||||
size = math.sqrt(sum(x*x for x in normal))
|
||||
if size < 1e-8:
|
||||
return
|
||||
normal = tuple(x/size for x in normal)
|
||||
uv = uv or [(0,0),(1,0),(1,1),(0,1)]
|
||||
for ids in ((0,1,2),(0,2,3)):
|
||||
if len({points[i] for i in ids}) < 3:
|
||||
continue
|
||||
vertices = []
|
||||
for i in ids:
|
||||
x,y,z = points[i]
|
||||
nx,ny,nz = smooth[i] if smooth else normal
|
||||
# Vertical texture convention: the top of a facade is v=0.
|
||||
vertices.extend((x,z,y,nx,nz,ny,*uv[i]))
|
||||
self.triangles.append((material,vertices))
|
||||
|
||||
def box(self, x0,x1,y0,y1,z0,z1,material,roof=None,repeat_v=1):
|
||||
if min(x1-x0,y1-y0,z1-z0) <= 0:
|
||||
return
|
||||
# Explicit winding/normals face outwards; negative scale is never used.
|
||||
uv = [(0,repeat_v),(1,repeat_v),(1,0),(0,0)]
|
||||
self.quad(material,[(x0,y0,z0),(x1,y0,z0),(x1,y0,z1),(x0,y0,z1)],uv)
|
||||
self.quad(material,[(x1,y1,z0),(x0,y1,z0),(x0,y1,z1),(x1,y1,z1)],uv)
|
||||
self.quad(material,[(x0,y1,z0),(x0,y0,z0),(x0,y0,z1),(x0,y1,z1)],uv)
|
||||
self.quad(material,[(x1,y0,z0),(x1,y1,z0),(x1,y1,z1),(x1,y0,z1)],uv)
|
||||
self.horizontal(x0,x1,y0,y1,z1,roof or material)
|
||||
|
||||
def horizontal(self,x0,x1,y0,y1,z,material,uv=None):
|
||||
uv = uv or [(0,0),(1,0),(1,1),(0,1)]
|
||||
# Vertex-fog drivers must not interpolate a whole city from distant corners.
|
||||
def cuts(lo,hi):
|
||||
if hi-lo <= 64:
|
||||
return [lo,hi]
|
||||
candidates = list(range(-512,513,64))+[-96,-32,32,96,-1536,-1024,-768,768,1024,1536]
|
||||
return [lo,*sorted(v for v in candidates if lo < v < hi),hi]
|
||||
def texcoord(x,y):
|
||||
s,t = (x-x0)/(x1-x0),(y1-y)/(y1-y0)
|
||||
return tuple((1-t)*((1-s)*uv[0][i]+s*uv[1][i])+
|
||||
t*((1-s)*uv[3][i]+s*uv[2][i]) for i in range(2))
|
||||
xs,ys = cuts(x0,x1),cuts(y0,y1)
|
||||
for a,b in zip(xs,xs[1:]):
|
||||
for c,d in zip(ys,ys[1:]):
|
||||
points = [(a,d,z),(b,d,z),(b,c,z),(a,c,z)]
|
||||
coords = [texcoord(x,y) for x,y,_ in points]
|
||||
self.quad(material,list(reversed(points)),list(reversed(coords)))
|
||||
|
||||
def ellipsoid(self,x,y,z,rx,ry,rz,material,upper=False,rings=10,sides=24):
|
||||
end = math.pi/2 if upper else math.pi
|
||||
def point(t,p):
|
||||
nx,ny,nz = math.sin(t)*math.cos(p),math.sin(t)*math.sin(p),math.cos(t)
|
||||
n = (nx/rx,ny/ry,nz/rz)
|
||||
length = math.sqrt(sum(v*v for v in n))
|
||||
return (x+rx*nx,y+ry*ny,z+rz*nz),tuple(v/length for v in n)
|
||||
for row in range(rings):
|
||||
t0,t1 = max(.0001,row*end/rings),(row+1)*end/rings
|
||||
for col in range(sides):
|
||||
p0,p1 = col*math.tau/sides,(col+1)*math.tau/sides
|
||||
samples = [point(t0,p0),point(t1,p0),point(t1,p1),point(t0,p1)]
|
||||
self.quad(material,[p for p,n in samples],
|
||||
[(col/sides,row/rings),(col/sides,(row+1)/rings),
|
||||
((col+1)/sides,(row+1)/rings),((col+1)/sides,row/rings)],
|
||||
[n for p,n in samples])
|
||||
|
||||
def cylinder(self,x,y,z0,z1,radius,material,sides=16,repeat=1):
|
||||
for col in range(sides):
|
||||
a,b = col*math.tau/sides,(col+1)*math.tau/sides
|
||||
na,nb = (math.cos(a),math.sin(a),0),(math.cos(b),math.sin(b),0)
|
||||
self.quad(material,[(x+radius*na[0],y+radius*na[1],z0),
|
||||
(x+radius*nb[0],y+radius*nb[1],z0),
|
||||
(x+radius*nb[0],y+radius*nb[1],z1),
|
||||
(x+radius*na[0],y+radius*na[1],z1)],
|
||||
uv=[(col*repeat/sides,1),((col+1)*repeat/sides,1),
|
||||
((col+1)*repeat/sides,0),(col*repeat/sides,0)],
|
||||
smooth=[na,nb,nb,na])
|
||||
self.quad(material,[(x,y,z1),(x+radius*na[0],y+radius*na[1],z1),
|
||||
(x+radius*nb[0],y+radius*nb[1],z1),
|
||||
(x+radius*nb[0],y+radius*nb[1],z1)])
|
||||
|
||||
def hip(self,x0,x1,y0,y1,z,height,material=196):
|
||||
if x1-x0 >= y1-y0:
|
||||
ridge = [(x0+1,(y0+y1)/2,z+height),(x1-1,(y0+y1)/2,z+height)]
|
||||
self.quad(material,[(x0,y0,z),(x1,y0,z),ridge[1],ridge[0]],[(0,0),((x1-x0)/4,0),((x1-x0)/4,1),(0,1)])
|
||||
self.quad(material,[(x1,y1,z),(x0,y1,z),ridge[0],ridge[1]],[(0,0),((x1-x0)/4,0),((x1-x0)/4,1),(0,1)])
|
||||
self.quad(material,[(x0,y1,z),(x0,y0,z),ridge[0],ridge[0]])
|
||||
self.quad(material,[(x1,y0,z),(x1,y1,z),ridge[1],ridge[1]])
|
||||
else:
|
||||
# Transposing X/Y reverses winding; save() normalises every triangle.
|
||||
cy = (x0+x1)/2
|
||||
a,b = (cy,y0+1,z+height),(cy,y1-1,z+height)
|
||||
self.quad(material,[(x0,y1,z),(x0,y0,z),a,b],[(0,0),((y1-y0)/4,0),((y1-y0)/4,1),(0,1)])
|
||||
self.quad(material,[(x1,y0,z),(x1,y1,z),b,a],[(0,0),((y1-y0)/4,0),((y1-y0)/4,1),(0,1)])
|
||||
self.quad(material,[(x0,y0,z),(x1,y0,z),a,a])
|
||||
self.quad(material,[(x1,y1,z),(x0,y1,z),b,b])
|
||||
|
||||
def save(self,name,static=False):
|
||||
original_count = len(self.triangles)
|
||||
oriented = []
|
||||
for mat,packed in self.triangles:
|
||||
vertices = [packed[i:i+8] for i in (0,8,16)]
|
||||
a,b,c = [v[:3] for v in vertices]
|
||||
u,v = [b[i]-a[i] for i in range(3)],[c[i]-a[i] for i in range(3)]
|
||||
normal = [u[1]*v[2]-u[2]*v[1],u[2]*v[0]-u[0]*v[2],u[0]*v[1]-u[1]*v[0]]
|
||||
if sum(x*x for x in normal) < 1e-16:
|
||||
continue
|
||||
expected = [sum(vertex[i+3] for vertex in vertices) for i in range(3)]
|
||||
if sum(x*y for x,y in zip(normal,expected)) < 0:
|
||||
vertices[1],vertices[2] = vertices[2],vertices[1]
|
||||
normal = [-x for x in normal]
|
||||
if static:
|
||||
# Union of every standing/seated/leaning eye position, in GL X/Z/Y axes.
|
||||
bounds = ((11.0,29.0),(1.05,1.8),(1.0,21.0))
|
||||
maximum = sum(n*((hi if n >= 0 else lo)-p)
|
||||
for n,(lo,hi),p in zip(normal,bounds,a))
|
||||
if maximum < -1e-7:
|
||||
continue
|
||||
oriented.append((mat,[value for vertex in vertices for value in vertex]))
|
||||
self.triangles = oriented
|
||||
self.triangles.sort(key=lambda t:t[0])
|
||||
payload = b"".join(struct.pack("<I24f",mat,*vertices) for mat,vertices in self.triangles)
|
||||
assert len(payload) == len(self.triangles)*100
|
||||
assert all(161 <= mat <= 209 for mat,_ in self.triangles)
|
||||
assert all(math.isfinite(v) for _,vs in self.triangles for v in vs)
|
||||
assert len(self.triangles) <= 65000
|
||||
(OUT/name).write_bytes(struct.pack("<4sII",b"RGM1",len(self.triangles),len(payload))+payload)
|
||||
self.removed = original_count-len(self.triangles)
|
||||
return len(self.triangles)
|
||||
|
||||
|
||||
def main():
|
||||
city = json.loads((ART/"district-map-v1.json").read_text())
|
||||
scene = Mesh()
|
||||
# The terrace is on a real sixty-metre high-rise, not a floating platform.
|
||||
scene.box(10.9,29.1,.9,21.1,-60,0,200,roof=165,repeat_v=3)
|
||||
scene.horizontal(10.9,29.1,.9,21.1,.004,161,[(0,10),(9,10),(9,0),(0,0)])
|
||||
# Terrain is actual split geometry, leaving a lowered water channel.
|
||||
def patches(lo,hi):
|
||||
return [(lo,70,lo,60,False),(70,hi,lo,29,False),(lo,hi,76,hi,False),
|
||||
(lo,hi,60,76,True),(70,hi,29,60,True)]
|
||||
def world_uv(x0,x1,y0,y1,scale):
|
||||
return [(x0/scale,y1/scale),(x1/scale,y1/scale),
|
||||
(x1/scale,y0/scale),(x0/scale,y0/scale)]
|
||||
for x0,x1,y0,y1,water in patches(-2000,2000):
|
||||
scene.horizontal(x0,x1,y0,y1,-62 if water else -60,198 if water else 162,
|
||||
world_uv(x0,x1,y0,y1,12 if water else 4))
|
||||
def subtract(rect,obstacle):
|
||||
a,b,c,d = rect
|
||||
x0,x1,y0,y1 = obstacle
|
||||
x0,x1,y0,y1 = max(a,x0),min(b,x1),max(c,y0),min(d,y1)
|
||||
if x0 >= x1 or y0 >= y1:
|
||||
return [rect]
|
||||
return [r for r in ((a,x0,c,d),(x1,b,c,d),(x0,x1,c,y0),(x0,x1,y1,d))
|
||||
if r[1]-r[0] > .01 and r[3]-r[2] > .01]
|
||||
def road(rect,vertical=False):
|
||||
pieces = [rect]
|
||||
obstacles = [(-2000,2000,58,79),(68,2000,26,81)]
|
||||
if vertical:
|
||||
obstacles += [(-2000,2000,29+36*i-4,29+36*i+4) for i in range(-16,17)]
|
||||
for obstacle in obstacles:
|
||||
pieces = [p for r in pieces for p in subtract(r,obstacle)]
|
||||
for a,b,c,d in pieces:
|
||||
roads.append((a,b,c,d))
|
||||
scene.horizontal(a,b,c,d,-59.98,197,world_uv(a,b,c,d,4))
|
||||
roads = []
|
||||
for i in range(-16,17):
|
||||
x = 38+36*i
|
||||
road((x-4,x+4,-600,600),vertical=True)
|
||||
y = 29+36*i
|
||||
road((-600,600,y-4,y+4))
|
||||
# Small lane dashes and pedestrian crossings give the streets a readable scale.
|
||||
for gx in range(-6,7):
|
||||
x = 38+36*gx
|
||||
for y in range(-205,231,9):
|
||||
if not 54 < y < 82 and not (x > 68 and 24 < y < 82):
|
||||
scene.horizontal(x-.07,x+.07,y,y+3,-59.965,168)
|
||||
for gy in range(-6,7):
|
||||
y = 29+36*gy
|
||||
if 54 < y < 82 or (x > 68 and 24 < y < 82):
|
||||
continue
|
||||
for stripe in range(7):
|
||||
sx = x-3.2+stripe*.95
|
||||
scene.horizontal(sx,sx+.45,y+5,y+7,-59.96,168)
|
||||
for index,(x0,x1,y0,y1,roof,mat,kind) in enumerate(city["buildings"]):
|
||||
modern = mat >= 199
|
||||
repeat = max(1,round((roof+60)/26)) if modern else 1
|
||||
scene.box(x0,x1,y0,y1,-60,roof,mat,repeat_v=repeat)
|
||||
scene.box(x0-.18,x1+.18,y0-.18,y1+.18,roof,roof+.18,165 if modern else 168)
|
||||
distance = math.hypot((x0+x1)/2-20,(y0+y1)/2-11)
|
||||
if not modern and distance < 220:
|
||||
scene.box(x0-.12,x1+.12,y0-.12,y1+.12,-60,-58.8,162)
|
||||
for z in (roof-.55,roof-.3):
|
||||
scene.box(x0-.25,x1+.25,y0-.25,y1+.25,z,z+.12,168)
|
||||
if distance < 150:
|
||||
for x in (x0,x1-.28):
|
||||
for y in (y0,y1-.28):
|
||||
scene.box(x-.08,x+.36,y-.08,y+.36,-58.8,roof-.5,168)
|
||||
scene.hip(x0-.25,x1+.25,y0-.25,y1+.25,roof+.18,2.4)
|
||||
scene.box(x0+.8,x0+1.45,y0+.8,y0+1.35,roof+1.2,roof+3.2,190,roof=162)
|
||||
# Recessed dormer volumes interrupt long, otherwise empty roof slopes.
|
||||
if x1-x0 > 12 and distance < 150:
|
||||
for x in range(math.ceil(x0+3),math.floor(x1-2),6):
|
||||
scene.box(x-.55,x+.55,y0+.8,y0+1.55,roof+.45,roof+1.45,168)
|
||||
scene.quad(200,[(x-.4,y0+.79,roof+.6),(x+.4,y0+.79,roof+.6),
|
||||
(x+.4,y0+.79,roof+1.25),(x-.4,y0+.79,roof+1.25)])
|
||||
scene.hip(x-.65,x+.65,y0+.7,y0+1.65,roof+1.45,.4)
|
||||
else:
|
||||
scene.horizontal(x0,x1,y0,y1,roof+.19,196)
|
||||
# Static landmark records share their exact footprints with the MASM world.
|
||||
data = (ROOT/"src_graphics/rooftop_district_data.inc").read_text()
|
||||
section = data.split("roofLandmarkParts3D LABEL DWORD",1)[1].split("roofFog3D",1)[0]
|
||||
for line in section.splitlines():
|
||||
if not line.strip().startswith("dd "):
|
||||
continue
|
||||
values = list(map(int,line.strip()[3:].split(",")))
|
||||
kind,coords,mat = values[0],[v/65536 for v in values[1:7]],values[7]
|
||||
# The dome's old rectangular drum is replaced with a proper circular one.
|
||||
if kind == 0 and 7 < coords[0] < 12 and 128 < coords[3] < 134 and coords[4] > -30:
|
||||
continue
|
||||
if kind == 0:
|
||||
x0,x1,y0,y1,z0,z1 = coords
|
||||
if x1-x0 < 1.1 and y1-y0 < 1.1 and z1-z0 > 3:
|
||||
scene.cylinder((x0+x1)/2,(y0+y1)/2,z0,z1,(x1-x0)/2,mat)
|
||||
scene.box(x0-.12,x1+.12,y0-.12,y1+.12,z1-.25,z1,mat)
|
||||
else:
|
||||
scene.box(*coords,mat)
|
||||
elif kind == 1:
|
||||
foliage = mat == 207
|
||||
scene.ellipsoid(*coords,mat,upper=not foliage,
|
||||
rings=4 if foliage else 14,sides=8 if foliage else 48)
|
||||
elif kind == 2:
|
||||
x,y,z,rx,ry,h = coords
|
||||
h *= .65
|
||||
for i in range(8):
|
||||
a,b = i*math.tau/8,(i+1)*math.tau/8
|
||||
scene.quad(mat,[(x+rx*math.cos(a),y+ry*math.sin(a),z),
|
||||
(x+rx*math.cos(b),y+ry*math.sin(b),z),
|
||||
(x,y,z+h),(x,y,z+h)])
|
||||
# Cathedral drum, portico and a solid entablature under the copper dome.
|
||||
scene.cylinder(20,119,-22,-10,9.5,191,sides=48,repeat=6)
|
||||
for z,radius in ((-22,10.3),(-16,10.0),(-10.5,12.4)):
|
||||
scene.cylinder(20,119,z,z+.45,radius,168,sides=48,repeat=6)
|
||||
for step in range(4):
|
||||
scene.box(3+step*.4,37-step*.4,95+step*.5,104,-60+step*.35,-59.65+step*.35,162)
|
||||
for x in (6,11.5,17,22.5,28,33.5):
|
||||
scene.cylinder(x,99,-58.6,-40,.55,168,sides=16)
|
||||
scene.cylinder(x,99,-58.6,-57.8,.8,162,sides=16)
|
||||
scene.cylinder(x,99,-40,-39.5,.8,168,sides=16)
|
||||
scene.box(3,37,97,104,-39.5,-38.5,168)
|
||||
scene.quad(168,[(3,96.99,-38.5),(37,96.99,-38.5),(20,96.99,-33),(20,96.99,-33)])
|
||||
scene.hip(2.8,37.2,96.8,104.2,-38.5,5.5)
|
||||
for x in sorted(set(city["bridges"]+city["river_bridges"])):
|
||||
start,end = (28,77) if x >= 70 else (59,77)
|
||||
scene.horizontal(x-3,x+3,start,end,-59.9,197,world_uv(x-3,x+3,start,end,4))
|
||||
for side in (x-3,x+3):
|
||||
for i in range(24):
|
||||
y0,y1 = start+(end-start)*i/24,start+(end-start)*(i+1)/24
|
||||
mid,radius = (start+end)/2,(end-start)/2
|
||||
z0 = -62+1.8*math.sqrt(max(0,1-((y0-mid)/radius)**2))
|
||||
z1 = -62+1.8*math.sqrt(max(0,1-((y1-mid)/radius)**2))
|
||||
points = [(side,y0,z0),(side,y1,z1),(side,y1,-60),(side,y0,-60)]
|
||||
scene.quad(162,list(reversed(points)) if side < x else points)
|
||||
for y in range(start,end+1,2):
|
||||
scene.box(side-.1,side+.1,y-.1,y+.1,-60,-59,165)
|
||||
scene.box(side-.08,side+.08,start,end,-59.1,-59,165)
|
||||
# Raised embankments, a tree-lined promenade and small piers follow the river.
|
||||
for y0,y1 in ((26,29),(76,81)):
|
||||
scene.box(70,1100,y0,y1,-62,-59.75,162)
|
||||
for x in range(84,640,28):
|
||||
y = (y0+y1)/2
|
||||
if min(abs(x-b) for b in city["river_bridges"]) < 6:
|
||||
continue
|
||||
scene.cylinder(x,y,-59.75,-56,.14,162,sides=6)
|
||||
scene.ellipsoid(x,y,-55,1.7,1.7,2.1,207,rings=4,sides=8)
|
||||
scene.box(x+3,x+5,y-.3,y+.3,-59.75,-59.3,162)
|
||||
for x in (156,300,444):
|
||||
scene.box(x,x+7,28,34,-62,-60.3,196)
|
||||
for x,y in city["parks"]:
|
||||
scene.horizontal(x-12,x+12,y-12,y+12,-59.9,207)
|
||||
scene.horizontal(x-1,x+1,y-12,y+12,-59.8,162)
|
||||
for dx in (-8,8):
|
||||
for dy in (-8,0,8):
|
||||
scene.cylinder(x+dx,y+dy,-60,-56,.15,162,sides=8)
|
||||
scene.ellipsoid(x+dx,y+dy,-55.5,2.0,2.0,2.4,207,rings=5,sides=10)
|
||||
for x,y in city.get("lamps",[]):
|
||||
scene.cylinder(x,y,-60,-55,.055,165,sides=6)
|
||||
scene.box(x-.22,x+.22,y-.22,y+.22,-55.15,-54.9,178)
|
||||
# A second ground pass multiplies the baked, geometry-derived light map.
|
||||
for a,b,c,d,water in patches(-512,512):
|
||||
uv = [((a+512)/1024,(d+512)/1024),((b+512)/1024,(d+512)/1024),
|
||||
((b+512)/1024,(c+512)/1024),((a+512)/1024,(c+512)/1024)]
|
||||
scene.horizontal(a,b,c,d,-61.94 if water else -59.94,208,uv)
|
||||
scene.horizontal(a,b,c,d,-61.93 if water else -59.93,209,uv)
|
||||
car = Mesh()
|
||||
car.box(-1.9,1.9,-.8,.8,.25,.85,196)
|
||||
car.box(-.9,.9,-.65,.65,.85,1.35,200)
|
||||
for y in (-.57,.57):
|
||||
car.box(1.88,1.93,y-.12,y+.12,.5,.68,178)
|
||||
for x in (-1.2,1.2):
|
||||
for y in (-.83,.83):
|
||||
car.ellipsoid(x,y,.3,.32,.12,.32,197,rings=5,sides=10)
|
||||
boat = Mesh()
|
||||
boat.ellipsoid(0,0,-1.6,3,1,.5,196,rings=6,sides=16)
|
||||
boat.box(-1,1,-.6,.6,-1.4,-.7,168)
|
||||
counts = {"scene":scene.save("mesh-city-v1.bin",static=True),"car":car.save("mesh-car-v1.bin"),
|
||||
"boat":boat.save("mesh-boat-v1.bin")}
|
||||
(ART/"mesh-v1.json").write_text(json.dumps({"format":"RGM1: count/bytes + material/3*(position,normal,uv)",
|
||||
"gl_axes":"X/Z/Y; normals explicitly outward; no runtime model engine", "triangles":counts,
|
||||
"road_rectangles":roads,"river_patches":[p for p in patches(-2000,2000) if p[-1]],
|
||||
"removed_invisible_triangles":scene.removed,"front_face":"CCW"},indent=2)+"\n")
|
||||
print("Packed native GL meshes:",counts)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -6,6 +6,7 @@ from pathlib import Path
|
|||
from PIL import Image
|
||||
from check_card_exit_build import Coff
|
||||
from check_coin_detail import font, text_matches
|
||||
from check_terminals import signature
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
OUT = ROOT / "build/actions"
|
||||
|
|
@ -62,11 +63,11 @@ def main(parity=False):
|
|||
contact.paste(frames[name], ((i % 3) * 640, (i // 3) * 480))
|
||||
contact.save(OUT / "billiards-contact.png")
|
||||
if parity:
|
||||
tested = Coff(OUT / "actions-test.obj")
|
||||
tested = Coff(ROOT / "build/billiards/module.obj")
|
||||
shipped = Coff(ROOT / "build/graphics/poker.obj")
|
||||
for name, size in FUNCTIONS.items():
|
||||
symbol = f"_{name}@{size}"
|
||||
assert tested.signature(symbol) == shipped.signature(symbol), name
|
||||
assert signature(tested,symbol,tail_branches=True) == signature(shipped,symbol,tail_branches=True), name
|
||||
print("PASS: Russian selection, both ball sets, native captures, rebound prediction" +
|
||||
(" and shipping/tested billiards parity" if parity else ""))
|
||||
|
||||
|
|
|
|||
|
|
@ -133,7 +133,9 @@ def main():
|
|||
print("PASS: dart scene, scoring, input and camera match the shipping object")
|
||||
return
|
||||
masm = Path("F:/MASM32/masm32")
|
||||
subprocess.run([str(masm / "bin/ml.exe"), "/c", "/coff", "/Cp", "/nologo", "/I.",
|
||||
modern_ml = Path("C:/Program Files/Microsoft Visual Studio/18/Community/VC/Tools/MSVC/14.51.36231/bin/Hostx64/x86/ml.exe")
|
||||
assembler = modern_ml if modern_ml.is_file() else masm / "bin/ml.exe"
|
||||
subprocess.run([str(assembler), "/c", "/coff", "/Cp", "/nologo", "/I.",
|
||||
"/Fobuild/darts/module.obj", "build/darts/module.asm"], cwd=ROOT, check=True)
|
||||
sdk = sorted(Path("C:/Program Files (x86)/Windows Kits/10/Lib").glob("*/um/x86"))[-1]
|
||||
target = "ui" if args.interactive else "module"
|
||||
|
|
@ -165,7 +167,7 @@ def main():
|
|||
sway[0].save(OUT / "sway.gif", save_all=True, append_images=sway[1:], duration=40, loop=0)
|
||||
triples, = struct.unpack("<I", (OUT / "aim-metrics.bin").read_bytes())
|
||||
print(f"PASS: stationary centre-of-T20 aim scored triples in {triples}/100 native samples")
|
||||
print("PASS: 67 scoring vectors, 20 complete bot matches, bust/turn isolation, fine mouse/release, sway/fatigue, board footprint, Tab, orbit camera and native moving frames")
|
||||
print("PASS: 67 scoring vectors, 20 complete bot matches, bust/turn isolation, fine mouse/release, sway/fatigue, board footprint, nine-pixel reticle, Tab, orbit camera and native moving frames")
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
"""Compile and execute fresh MASM navigation, actors and room renderers."""
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import struct
|
||||
import subprocess
|
||||
from PIL import Image, ImageStat
|
||||
from PIL import Image, ImageStat, ImageChops
|
||||
from check_card_exit_build import Coff
|
||||
from check_terminals import signature
|
||||
|
||||
|
|
@ -36,6 +37,7 @@ def prepare():
|
|||
code += "\n" + read("src_graphics/steel_door.inc")
|
||||
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" + proc(read("src_graphics/unavailable.inc"),"BeginUnavailable3D")
|
||||
code += "\n" + read("src_graphics/observer.inc")
|
||||
code += "\n" + expand("src_graphics/darts.inc").replace(":SDWORD", ":DWORD")
|
||||
|
|
@ -48,7 +50,7 @@ def prepare():
|
|||
"CheckHallInteractions3D","TriggerHallInteraction3D","HandleStairs3D","RenderStairs3D",
|
||||
"CanWalkHall3D","UpdateHall3D","UpdatePatrons3D","RenderPatrons3D","RenderBar3D",
|
||||
"BuyBarDrink3D","HandleBar3D"]),
|
||||
("src_graphics/platform_code.inc",["CanWalk3D","WindowEvents3D"]),
|
||||
("src_graphics/platform_code.inc",["CanWalk3D","WindowEvents3D","Present3D"]),
|
||||
("casino3d.inc",["HandlePlayerInput","CheckInteractions","RenderWallsRange","RenderWalls","RaycastWorkerProc","Trigger3DInteraction"]),
|
||||
("src_graphics/city_bins.inc",["ResolveCitySurface3D","PrepareCityDepth3D","PrepareCityRays3D",
|
||||
"CityRayPlane3D","DrawCityVertical3D","DrawCityHorizontal3D","DrawCityBoxSides3D"]),
|
||||
|
|
@ -60,10 +62,10 @@ def prepare():
|
|||
own_funcs = set(re.findall(r"^(\w+) PROC",code,re.M))
|
||||
npc = read("src_graphics/npc_life_data.inc")
|
||||
npc = npc[npc.index("npcGuestArt3D "):npc.index("npcLifeClock3D ")]
|
||||
data = "\n".join(read(path) for path in ("src_graphics/hall_expansion_data.inc",
|
||||
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")) + "\n.data\n" + npc
|
||||
"src_graphics/observer_data.inc","src_graphics/rooftop_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)
|
||||
|
|
@ -134,7 +136,8 @@ def prepare():
|
|||
# Win32 prototypes are not declarations in the helper object.
|
||||
for name,args in re.findall(r"^(\w+)\s+PROTO([^\n]*)",all_source,re.M):
|
||||
protos.setdefault(name,args.count(":DWORD"))
|
||||
protos.update({"ExitProcess":1,"WriteFile":5,"GetTickCount":0})
|
||||
protos.update({"ExitProcess":1,"WriteFile":5,"GetTickCount":0,"SetUnhandledExceptionFilter":1,
|
||||
"CreateDirectoryA":2})
|
||||
header = ".386\n.model flat\noption casemap:none\n" + "\n".join(structures+ordered+externs)
|
||||
header += "\noption language:stdcall\n" + "\n".join(
|
||||
name+" PROTO"+(" "+",".join([":DWORD"]*size) if size else "")
|
||||
|
|
@ -144,6 +147,64 @@ def prepare():
|
|||
return own_funcs
|
||||
|
||||
|
||||
def check_rooftop_navigation():
|
||||
for name,dimensions in (("panorama",(512,512,4)),("materials",(256,256,8)),
|
||||
("details",(256,256,15)),("city",(512,512,22)),
|
||||
("night",(512,512,4)),("rail-hands",(640,320,32))):
|
||||
raw = (ROOT/f"assets/animations/rooftop/{name}-v1.bin").read_bytes()
|
||||
magic,w,h,frames,payload = struct.unpack_from("<4s4I",raw)
|
||||
assert magic == b"ANM1" and (w,h,frames) == dimensions
|
||||
assert len(raw) == payload+20 and payload == w*h*frames*4
|
||||
navigation = (OUT/"roof-navigation.bin").read_bytes()
|
||||
width,height,ox,oy,step,count = struct.unpack_from("<6I",navigation)
|
||||
assert count == 13
|
||||
xs = struct.unpack_from(f"<{count}I",navigation,24)
|
||||
ys = struct.unpack_from(f"<{count}I",navigation,24+count*4)
|
||||
kinds = struct.unpack_from(f"<{count}I",navigation,24+count*8)
|
||||
cells = navigation[24+count*12:]
|
||||
assert len(cells) == width*height
|
||||
def nav_point(x,y):
|
||||
return round((x*65536-ox)/step),round((y*65536-oy)/step)
|
||||
start = nav_point(24.5,2.5)
|
||||
assert cells[start[1]*width+start[0]], "lift cabin must be walkable when open"
|
||||
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 0 <= a < width and 0 <= b < height and cells[b*width+a] and (a,b) not in reached:
|
||||
reached.add((a,b))
|
||||
pending.append((a,b))
|
||||
assert len(reached) > 1500
|
||||
for i,(sx,sy) in enumerate(zip(xs,ys)):
|
||||
assert any((ox+x*step-sx)**2+(oy+y*step-sy)**2 < (1.8*65536)**2
|
||||
for x,y in reached),(i,"seat has no reachable approach")
|
||||
for point in ((15.5,6.75),(27,18),(13,17)):
|
||||
assert nav_point(*point) in reached,(point,"blocked rooftop route")
|
||||
assert list(kinds).count(0) == 3 and list(kinds).count(1) == 4
|
||||
city = json.loads((ROOT/"assets/sprites/rooftop/district-map-v1.json").read_text())
|
||||
assert city["street_level"] == -60
|
||||
assert len(city["parks"]) >= 6 and len(city["towers"]) >= 8
|
||||
assert len(city["buildings"]) > 400
|
||||
waterfront = city["waterfront_buildings"]
|
||||
assert len(waterfront) == 60
|
||||
assert all(b in city["buildings"] for b in waterfront)
|
||||
assert all(b[3] <= 54 or b[2] >= 82 for b in waterfront), "canal must stay open"
|
||||
for bank in (0,1):
|
||||
row = [b for b in waterfront if (b[3] < 60) == (bank == 0)]
|
||||
assert len(row) == 30 and min(b[0] for b in row) < -450 and max(b[1] for b in row) >= 68
|
||||
assert len(city["canal_quay_parts"]) > 100
|
||||
heights = {round(b[4]+60,1) for b in city["buildings"]}
|
||||
assert len(heights) >= 35 and min(heights) <= 6 and max(heights) >= 150
|
||||
assert len({(round(b[1]-b[0],1),round(b[3]-b[2],1)) for b in city["buildings"]}) >= 15
|
||||
assert {b[5] for b in city["buildings"]} >= {186,187,188,199,200,201,202,203,204}
|
||||
for x0,x1,y0,y1,top,material,kind in city["buildings"]:
|
||||
assert not (x0 < 29.1 and x1 > 10.9 and y0 < 21.1 and y1 > .9)
|
||||
if max(abs((x0+x1)/2-20),abs((y0+y1)/2-11)) < 60:
|
||||
assert top <= -19,"a neighbour must not block the sixty-metre-high terrace"
|
||||
print(f"PASS: sixty-metre rooftop, {len(heights)} distinct city heights, streets/courtyards, parks, bridges and distant towers")
|
||||
print(f"PASS: dense rooftop, {count} seats, {len(reached)} connected native walk points, lift/bar return and camera restore")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--parity",action="store_true")
|
||||
|
|
@ -151,6 +212,7 @@ def main():
|
|||
parser.add_argument("--prove-seam-regression",action="store_true")
|
||||
parser.add_argument("--prove-distance-regression",action="store_true")
|
||||
parser.add_argument("--prove-bar-camera-regression",action="store_true")
|
||||
parser.add_argument("--prove-fate-anchor-regression",action="store_true")
|
||||
args = parser.parse_args()
|
||||
if args.parity:
|
||||
native,shipping = Coff(OUT/"module.obj"),Coff(ROOT/"build/graphics/poker.obj")
|
||||
|
|
@ -162,10 +224,25 @@ def main():
|
|||
print("PASS: all tested navigation, collision, hall/upper rendering, guests and cabaret procedures match the shipping assembly")
|
||||
return
|
||||
prepare()
|
||||
walk = read("casino3d.inc").split("check_walk_mode:",1)[1].split("check_slots_mode:",1)[0]
|
||||
assert walk.index("je walk_world_ready") < walk.index("call RenderCeilingAndFloor")
|
||||
assert walk.index("walk_world_ready:") > walk.index("call RenderSprites")
|
||||
room = proc(read("src_graphics/fortune_room.inc"), "RenderFortuneFurnishings3D")
|
||||
assert "DrawCityObject3D" not in room, "tarot furniture must not face the camera"
|
||||
assert room.count("DrawFortuneFixedProp3D") == 24
|
||||
stem = "module"
|
||||
if args.prove_fate_anchor_regression:
|
||||
stem = "module-fate-anchor-regression"
|
||||
source = (OUT/"module.asm").read_text(encoding="cp1251")
|
||||
before = proc(source,"ResolveFortuneOrb3D")
|
||||
broken = before.replace("mov fortuneOrbCenterX3D,eax", "mov fortuneOrbCenterX3D,278")
|
||||
broken = broken.replace("mov fortuneOrbCenterY3D,eax", "mov fortuneOrbCenterY3D,259")
|
||||
assert broken != before
|
||||
source = source.replace(before,broken)
|
||||
source = source.replace("include tools/tests/hall_expansion_test.inc",
|
||||
expand("tools/tests/hall_expansion_test.inc"))
|
||||
source = source.replace("build\\hall-expansion\\", "build\\hall-expansion\\anchor-regression-")
|
||||
(OUT/(stem+".asm")).write_text(source,encoding="cp1251")
|
||||
if args.prove_bar_camera_regression:
|
||||
stem = "module-camera-regression"
|
||||
source = (OUT/"module.asm").read_text(encoding="cp1251")
|
||||
|
|
@ -199,7 +276,9 @@ def main():
|
|||
"invoke ExpansionPortalSnapshot,OFFSET expansionUpEntry\n invoke ExitProcess,0")
|
||||
(OUT/(stem+".asm")).write_text(source,encoding="cp1251")
|
||||
masm = Path("F:/MASM32/masm32")
|
||||
subprocess.run([str(masm/"bin/ml.exe"),"/c","/coff","/Cp","/nologo","/I.",
|
||||
modern_ml = Path("C:/Program Files/Microsoft Visual Studio/18/Community/VC/Tools/MSVC/14.51.36231/bin/Hostx64/x86/ml.exe")
|
||||
assembler = modern_ml if modern_ml.is_file() else masm/"bin/ml.exe"
|
||||
subprocess.run([str(assembler),"/c","/coff","/Cp","/nologo","/I.",
|
||||
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",
|
||||
|
|
@ -207,6 +286,10 @@ def main():
|
|||
f"build/hall-expansion/{stem}.obj","build/hall-expansion/helpers.obj",
|
||||
"kernel32.lib","user32.lib","gdi32.lib","winmm.lib","ws2_32.lib"],cwd=ROOT,check=True)
|
||||
result = subprocess.run([str(OUT/(stem+".exe"))],cwd=ROOT,timeout=360)
|
||||
if args.prove_fate_anchor_regression:
|
||||
assert result.returncode == 81,result.returncode
|
||||
print("PASS: restoring fixed screen coordinates fails the native crystal-ball tracking assertions")
|
||||
return
|
||||
if args.prove_bar_camera_regression:
|
||||
assert result.returncode == 70,result.returncode
|
||||
print("PASS: removing camera recovery fails the native bar regression")
|
||||
|
|
@ -220,6 +303,8 @@ def main():
|
|||
print("PASS: restoring eight-metre cutoff fails the native far-doorway assertion")
|
||||
return
|
||||
assert result.returncode == 0,f"native case {result.returncode}"
|
||||
if not args.prove_seam_regression:
|
||||
check_rooftop_navigation()
|
||||
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"
|
||||
|
|
@ -246,6 +331,37 @@ def main():
|
|||
assert len({im.crop((190,285,475,398)).tobytes() for im in frames}) >= 7
|
||||
question = frames[0].crop((18,400,620,444)).tobytes()
|
||||
assert all(im.crop((18,400,620,444)).tobytes() == question for im in frames)
|
||||
fate_motion = [Image.open(OUT/f"fortune-fate-motion-{i:02}.bmp").convert("RGB")
|
||||
for i in range(32)]
|
||||
assert len({im.crop((134,32,414,323)).tobytes() for im in fate_motion}) >= 24
|
||||
fate_motion[0].save(OUT/"fortune-fate-mist.gif", save_all=True,
|
||||
append_images=fate_motion[1:], duration=32, loop=0)
|
||||
fate_result = Image.open(OUT/"fortune-fate-result.bmp").convert("RGB")
|
||||
fate_form = Image.open(OUT/"fortune-fate-form.bmp").convert("RGB")
|
||||
assert fate_form.crop((16,298,624,446)).tobytes() != fate_result.crop((16,298,624,446)).tobytes()
|
||||
# Difference captures isolate the actual numeral, independently of fog highlights.
|
||||
tracking = (ROOT/"assets/sprites/fortune/orb-tracking-v1.json").read_text()
|
||||
anchors = json.loads(tracking)["frames"]
|
||||
tracked = []
|
||||
for i, anchor in enumerate(anchors):
|
||||
result = Image.open(OUT/f"fortune-fate-track-{i:02}.bmp").convert("RGB")
|
||||
base = Image.open(OUT/f"fortune-fate-base-{i:02}.bmp").convert("RGB")
|
||||
box = ImageChops.difference(result, base).getbbox()
|
||||
assert box, (i, "no numeral rendered")
|
||||
cx = 214 + int(anchor["cx"]*128/192 + .5)
|
||||
cy = 195 + int(anchor["cy"]*128/192 + .5)
|
||||
assert abs((box[0]+box[2])/2-cx) <= 2.5, (i, box, cx)
|
||||
assert abs((box[1]+box[3])/2-cy) <= 2.5, (i, box, cy)
|
||||
assert cx-42 <= box[0] < box[2] <= cx+42
|
||||
assert cy-28 <= box[1] < box[3] <= cy+28
|
||||
tracked.append(result)
|
||||
assert max(a["cx"] for a in anchors)-min(a["cx"] for a in anchors) > 8
|
||||
assert max(a["cy"] for a in anchors)-min(a["cy"] for a in anchors) > 8
|
||||
tracked[0].save(OUT/"fortune-fate-tracked.gif", save_all=True,
|
||||
append_images=tracked[1:], duration=64, loop=0)
|
||||
numerals = (ROOT/"assets/animations/fortune/orb-numerals-v1.bin").read_bytes()
|
||||
assert struct.unpack_from("<4s4I", numerals) == (b"ANM1", 32, 48, 10, 32*48*10*4)
|
||||
assert len(set(numerals[23::4])) > 50, "font must retain soft edges and glow alpha"
|
||||
for name in ("room","shuffle","deal","gesture","orb-hands","orb-mist","cards"):
|
||||
path = ROOT/f"assets/animations/fortune/{name}-v1.bin"
|
||||
data = path.read_bytes()
|
||||
|
|
@ -270,8 +386,54 @@ def main():
|
|||
assert dominant < 0.5,"stair entrance must show textured continuation on both sides, not a solid rear-wall block"
|
||||
for path in OUT.glob("*.bmp"):
|
||||
image = Image.open(path).convert("RGB")
|
||||
assert image.size == (640,480) and len(image.getcolors(640*480)) > 100
|
||||
expected = (1280,960) if path.name.startswith("roof-hd-") else (640,480)
|
||||
assert image.size == expected and len(image.getcolors(image.width*image.height)) > 100
|
||||
image.save(path.with_suffix(".png"))
|
||||
hd_day = Image.open(OUT/"roof-hd-south.bmp").convert("RGB")
|
||||
hd_night = Image.open(OUT/"roof-hd-night.bmp").convert("RGB")
|
||||
assert ImageChops.difference(hd_day,hd_night).getbbox(), "day/night must change actual native geometry lighting"
|
||||
white_ground = sum(1 for rgb in hd_night.crop((0,650,1280,960)).get_flattened_data() if min(rgb) > 235)
|
||||
assert white_ground < 500, "uninitialised emission textures must not turn ground white"
|
||||
traffic = Image.open(OUT/"roof-hd-traffic.bmp").convert("RGB")
|
||||
east = Image.open(OUT/"roof-hd-east.bmp").convert("RGB")
|
||||
assert ImageChops.difference(east,traffic).getbbox(), "native clock must move city vehicles"
|
||||
left = Image.open(OUT/"roof-hd-look-left.bmp").convert("RGB")
|
||||
right = Image.open(OUT/"roof-hd-look-right.bmp").convert("RGB")
|
||||
assert left.tobytes() != east.tobytes() != right.tobytes(), "leaning must allow actual head turns"
|
||||
mesh = json.loads((ROOT/"assets/sprites/rooftop/mesh-v1.json").read_text())
|
||||
assert mesh["triangles"]["scene"] < 48000, "driver-safe ground must stay within the optimised mesh budget"
|
||||
assert mesh["removed_invisible_triangles"] > 18000
|
||||
packed_mesh = (ROOT/"assets/animations/rooftop/mesh-city-v1.bin").read_bytes()
|
||||
water_tiles = 0
|
||||
for at in range(12,len(packed_mesh),100):
|
||||
mat,*values = struct.unpack_from("<I24f",packed_mesh,at)
|
||||
if mat == 198:
|
||||
water_tiles += 1
|
||||
if mat not in (162,197,198,208,209):
|
||||
continue
|
||||
vertices = [values[i:i+8] for i in (0,8,16)]
|
||||
if not all(-62.1 <= v[1] <= -59.7 and v[4] > .99 for v in vertices):
|
||||
continue
|
||||
xs,zs = [v[0] for v in vertices],[v[2] for v in vertices]
|
||||
if max(xs) > -480 and min(xs) < 480 and max(zs) > -480 and min(zs) < 480:
|
||||
assert max(xs)-min(xs) <= 64.01 and max(zs)-min(zs) <= 64.01, "giant vertex-fog ground polygon"
|
||||
assert water_tiles >= 80, "animated water must reuse the tessellated scene mesh"
|
||||
fast_fog = Image.open(OUT/"roof-hd-vertex-fog.bmp").convert("RGB")
|
||||
nice_fog = Image.open(OUT/"roof-hd-canal-quays.bmp").convert("RGB")
|
||||
assert max(ImageStat.Stat(ImageChops.difference(fast_fog,nice_fog)).mean) < 20
|
||||
for profile in ("eco", "auto"):
|
||||
low = Image.open(OUT/f"roof-hd-{profile}.bmp").convert("RGB")
|
||||
difference = ImageStat.Stat(ImageChops.difference(low, east)).mean
|
||||
assert max(difference) < 12, (profile, "quality must not remove scene geometry", difference)
|
||||
roads = mesh["road_rectangles"]
|
||||
for i,(a,b,c,d) in enumerate(roads):
|
||||
for x0,x1,y0,y1 in roads[i+1:]:
|
||||
assert min(b,x1)-max(a,x0) < 1e-6 or min(d,y1)-max(c,y0) < 1e-6, "coplanar street overlap"
|
||||
millis,frames,cpu,gpu,present = struct.unpack("<5I",(OUT/"roof-hd-timing.bin").read_bytes())
|
||||
print(f"Roof passes ms/frame: CPU {cpu/frames:.2f}, GPU/readback {gpu/frames:.2f}, composite {present/frames:.2f}")
|
||||
print(f"PASS: non-overlapping roads, head turns with anchored hands, final offscreen composite; {millis/frames:.2f} ms/frame in 24-frame native roof probe")
|
||||
print("PASS: native GL city at 640/960/1280, auto downgrade and F8, direct foreground composite")
|
||||
print("PASS: sunset/night, animated traffic, software roads/water even with missing city textures")
|
||||
gait = [Image.open(OUT/f"gait-{i}.bmp").convert("RGB") for i in range(8)]
|
||||
assert len({frame.crop((245,360,410,480)).tobytes() for frame in gait}) == 8, "legs must visibly change in native renders"
|
||||
gait[0].save(OUT/"gait-native.gif",save_all=True,append_images=gait[1:],duration=110,loop=0)
|
||||
|
|
|
|||
|
|
@ -64,11 +64,11 @@ def test_code_structure():
|
|||
|
||||
# Check constants
|
||||
assert "MODE_JACKBOX equ 28" in data_inc
|
||||
assert "INTERACT_JACKBOX equ 48" in data_inc
|
||||
assert "INTERACT_JACKBOX equ 52" in data_inc
|
||||
assert "JACKBOX_X equ FP_1*306/10" in data_inc
|
||||
assert "JACKBOX_Y equ FP_1*235/10" in data_inc
|
||||
assert "MATERIAL_JACKBOX_SIDE equ 121" in data_inc
|
||||
assert "MATERIAL_JACKBOX_BACK equ 163" in data_inc
|
||||
assert "MATERIAL_JACKBOX_BACK equ 213" in data_inc
|
||||
|
||||
# Check procs
|
||||
procs = [
|
||||
|
|
|
|||
|
|
@ -71,6 +71,51 @@ dtVectorCount3D equ 67
|
|||
dtTestTriples3D dd 0
|
||||
dtTestMetricPath db "build\darts\aim-metrics.bin",0
|
||||
.code
|
||||
DartsTestReticle PROC uses ebx esi edi
|
||||
mov edi,OFFSET screenBuffer
|
||||
mov ecx,640*480
|
||||
mov eax,00E0E0E0h
|
||||
rep stosd
|
||||
invoke DrawDartsReticle3D,320,214
|
||||
mov esi,OFFSET screenBuffer
|
||||
mov ecx,640*480
|
||||
xor ebx,ebx
|
||||
dt_test_reticle_light:
|
||||
lodsd
|
||||
cmp eax,00E0E0E0h
|
||||
je @F
|
||||
cmp eax,00152021h
|
||||
jne dt_test_reticle_fail
|
||||
inc ebx
|
||||
@@:
|
||||
loop dt_test_reticle_light
|
||||
cmp ebx,9
|
||||
jne dt_test_reticle_fail
|
||||
mov edi,OFFSET screenBuffer
|
||||
mov ecx,640*480
|
||||
mov eax,00101010h
|
||||
rep stosd
|
||||
invoke DrawDartsReticle3D,0,32
|
||||
mov esi,OFFSET screenBuffer
|
||||
mov ecx,640*480
|
||||
xor ebx,ebx
|
||||
dt_test_reticle_dark:
|
||||
lodsd
|
||||
cmp eax,00101010h
|
||||
je @F
|
||||
cmp eax,00F6E6ADh
|
||||
jne dt_test_reticle_fail
|
||||
inc ebx
|
||||
@@:
|
||||
loop dt_test_reticle_dark
|
||||
cmp ebx,5
|
||||
jne dt_test_reticle_fail
|
||||
ret
|
||||
dt_test_reticle_fail:
|
||||
invoke ExitProcess,8506
|
||||
ret
|
||||
DartsTestReticle ENDP
|
||||
|
||||
DartsTestWriteMetrics PROC uses ebx
|
||||
local written:DWORD
|
||||
invoke CreateFileA,OFFSET dtTestMetricPath,40000000h,0,0,2,80h,0
|
||||
|
|
@ -88,6 +133,7 @@ DartsTestWriteMetrics ENDP
|
|||
DartsTestStart PROC uses ebx esi edi
|
||||
local legs:DWORD
|
||||
call InitPaths3D
|
||||
call DartsTestReticle
|
||||
lea esi,dtVectors3D
|
||||
mov ebx,dtVectorCount3D
|
||||
dt_test_vector:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,18 @@
|
|||
.data
|
||||
fortuneSessionBank dd 0
|
||||
fortuneSessionPlayer dd 0
|
||||
fortuneFateSavedResult dd 0
|
||||
fortuneFateSavedSeed dd 0
|
||||
fortuneFateTestText db "Ivan",0,"Petrov",0,"29022000",0,"IS-42",0
|
||||
fortuneFateFormShot db "build\hall-expansion\fortune-fate-form.bmp",0
|
||||
fortuneFateResultShot db "build\hall-expansion\fortune-fate-result.bmp",0
|
||||
fortuneFateHundredShot db "build\hall-expansion\fortune-fate-hundred.bmp",0
|
||||
fortuneFateMotion db "build\hall-expansion\fortune-fate-motion-"
|
||||
fortuneFateDigits db "00.bmp",0
|
||||
fortuneFateTracking db "build\hall-expansion\fortune-fate-track-"
|
||||
fortuneFateTrackingDigits db "00.bmp",0
|
||||
fortuneFateTrackingBase db "build\hall-expansion\fortune-fate-base-"
|
||||
fortuneFateTrackingBaseDigits db "00.bmp",0
|
||||
fortuneSessionMotion db "build\hall-expansion\fortune-session-motion-"
|
||||
fortuneSessionDigits db "00.bmp",0
|
||||
fortuneSessionQuestion db 193,243,228,229,242,32,235,232,32,231,224,226,242,240,224,32,228,238,230,228,252,63,0
|
||||
|
|
@ -43,6 +55,8 @@ FortuneDialogNativeTests PROC uses ebx esi edi
|
|||
jne fortune_session_fail
|
||||
cmp fortuneSessionCards3D.frames,45
|
||||
jne fortune_session_fail
|
||||
cmp fortuneSessionNumerals3D.frames,10
|
||||
jne fortune_session_fail
|
||||
cmp fortuneProcess3D,0
|
||||
jne fortune_session_fail
|
||||
cmp fortuneRead3D,0
|
||||
|
|
@ -181,7 +195,7 @@ fortune_session_unique_next:
|
|||
ja fortune_session_fail
|
||||
cmp fortuneSpeechHeight3D,140
|
||||
ja fortune_session_fail
|
||||
; Return to selection, then display the neutral, non-grading orb sequence.
|
||||
; The fictional crystal-ball lottery requires a form, not a neural worker.
|
||||
mov mouseX3D,200
|
||||
mov mouseY3D,232
|
||||
mov mouseClick3D,1
|
||||
|
|
@ -191,6 +205,126 @@ fortune_session_unique_next:
|
|||
invoke WindowEvents3D,0,102h,'2',0
|
||||
cmp fortuneSubMode3D,FORTUNE_SUBMODE_ORB
|
||||
jne fortune_session_fail
|
||||
cmp fortunePhase3D,0
|
||||
jne fortune_session_fail
|
||||
call SubmitFortuneFate3D
|
||||
cmp fortunePhase3D,0
|
||||
jne fortune_session_fail
|
||||
cmp fortuneFateField3D,0
|
||||
jne fortune_session_fail
|
||||
; CP1251, control filtering and maximum field widths use the real input path.
|
||||
mov ebx,40
|
||||
fortune_fate_name_limit:
|
||||
invoke WindowEvents3D,0,102h,200,0
|
||||
dec ebx
|
||||
jnz fortune_fate_name_limit
|
||||
cmp fortuneFateLengths3D,30
|
||||
jne fortune_session_fail
|
||||
cmp fortuneFateFields3D[30],0
|
||||
jne fortune_session_fail
|
||||
invoke FortuneChar3D,127
|
||||
cmp fortuneFateLengths3D,30
|
||||
jne fortune_session_fail
|
||||
call ResetFortuneFate3D
|
||||
mov esi,OFFSET fortuneFateTestText
|
||||
xor ebx,ebx
|
||||
fortune_fate_input_fields:
|
||||
movzx eax,BYTE PTR [esi]
|
||||
inc esi
|
||||
test eax,eax
|
||||
jz fortune_fate_input_next
|
||||
invoke WindowEvents3D,0,102h,eax,0
|
||||
jmp fortune_fate_input_fields
|
||||
fortune_fate_input_next:
|
||||
invoke WindowEvents3D,0,102h,9,0
|
||||
inc ebx
|
||||
cmp ebx,4
|
||||
jb fortune_fate_input_fields
|
||||
cmp fortuneFateLengths3D[8],10
|
||||
jne fortune_session_fail
|
||||
call ValidateFortuneBirth3D
|
||||
cmp eax,1
|
||||
jne fortune_session_fail
|
||||
mov fortuneFateFields3D[137],'1'
|
||||
call ValidateFortuneBirth3D
|
||||
test eax,eax
|
||||
jnz fortune_session_fail
|
||||
call SubmitFortuneFate3D
|
||||
cmp fortuneFateField3D,2
|
||||
jne fortune_session_fail
|
||||
cmp fortunePhase3D,0
|
||||
jne fortune_session_fail
|
||||
mov fortuneFateFields3D[137],'0'
|
||||
mov fortuneFateField3D,4
|
||||
invoke FortuneChar3D,'x'
|
||||
cmp fortuneFateLengths3D[16],1
|
||||
jne fortune_session_fail
|
||||
; A value larger than 100 is rejected, not clamped or used as an outcome.
|
||||
mov fortuneFateFields3D[320],'1'
|
||||
mov fortuneFateFields3D[321],'0'
|
||||
mov fortuneFateFields3D[322],'1'
|
||||
mov fortuneFateLengths3D[20],3
|
||||
call SubmitFortuneFate3D
|
||||
cmp fortunePhase3D,0
|
||||
jne fortune_session_fail
|
||||
mov fortuneFateFields3D[320],'8'
|
||||
mov fortuneFateFields3D[321],0
|
||||
mov fortuneFateFields3D[322],0
|
||||
mov fortuneFateLengths3D[20],1
|
||||
call SubmitFortuneFate3D
|
||||
cmp fortunePhase3D,0
|
||||
jne fortune_session_fail
|
||||
cmp fortuneFateField3D,5
|
||||
jne fortune_session_fail
|
||||
mov fortuneFateFields3D[320],'1'
|
||||
mov fortuneFateFields3D[321],'0'
|
||||
mov fortuneFateLengths3D[20],2
|
||||
invoke SetFortuneSpeech3D,OFFSET fortuneFateReady3D
|
||||
mov fortuneSpeechTime3D,3000
|
||||
call RenderFortuneDialog3D
|
||||
invoke ExpansionSnapshot,OFFSET fortuneFateFormShot
|
||||
; Mouse focus and backspace obey per-field limits and reject control bytes.
|
||||
mov mouseX3D,200
|
||||
mov mouseY3D,168
|
||||
mov mouseClick3D,1
|
||||
call HandleFortuneDialog3D
|
||||
cmp fortuneFateField3D,1
|
||||
jne fortune_session_fail
|
||||
invoke FortuneChar3D,8
|
||||
cmp fortuneFateLengths3D[4],5
|
||||
jne fortune_session_fail
|
||||
invoke FortuneChar3D,'v'
|
||||
invoke WindowEvents3D,0,100h,0Dh,0
|
||||
invoke WindowEvents3D,0,102h,0Dh,0
|
||||
call HandleFortuneDialog3D
|
||||
invoke WindowEvents3D,0,101h,0Dh,0
|
||||
cmp fortunePhase3D,1
|
||||
jne fortune_session_fail
|
||||
mov eax,fortuneFateResult3D
|
||||
cmp eax,9
|
||||
jb fortune_session_fail
|
||||
cmp eax,10
|
||||
ja fortune_session_fail
|
||||
mov fortuneFateSavedResult,eax
|
||||
mov eax,rngSeed3D
|
||||
mov fortuneFateSavedSeed,eax
|
||||
call SubmitFortuneFate3D
|
||||
invoke FortuneChar3D,'X'
|
||||
cmp fortuneFateLengths3D[4],6
|
||||
jne fortune_session_fail
|
||||
mov eax,fortuneFateSavedResult
|
||||
cmp fortuneFateResult3D,eax
|
||||
jne fortune_session_fail
|
||||
mov eax,rngSeed3D
|
||||
cmp eax,fortuneFateSavedSeed
|
||||
jne fortune_session_fail
|
||||
; Again cannot bypass the ritual or reroll while hands are moving.
|
||||
mov mouseX3D,120
|
||||
mov mouseY3D,232
|
||||
mov mouseClick3D,1
|
||||
call HandleFortuneDialog3D
|
||||
cmp fortunePhase3D,1
|
||||
jne fortune_session_fail
|
||||
mov fortunePhaseTime3D,700
|
||||
call RenderFortuneDialog3D
|
||||
invoke ExpansionSnapshot,OFFSET fortuneSessionShot13
|
||||
|
|
@ -202,6 +336,27 @@ fortune_session_unique_next:
|
|||
mov fortuneSpeechTime3D,3000
|
||||
call RenderFortuneDialog3D
|
||||
invoke ExpansionSnapshot,OFFSET fortuneSessionShot14
|
||||
xor edi,edi
|
||||
fortune_fate_motion:
|
||||
mov eax,edi
|
||||
xor edx,edx
|
||||
mov ecx,10
|
||||
div ecx
|
||||
add al,'0'
|
||||
add dl,'0'
|
||||
mov fortuneFateDigits[0],al
|
||||
mov fortuneFateDigits[1],dl
|
||||
mov eax,edi
|
||||
imul eax,32
|
||||
mov fortunePhaseTime3D,eax
|
||||
call RenderFortuneDialog3D
|
||||
invoke ExpansionSnapshot,OFFSET fortuneFateMotion
|
||||
mov eax,fortuneFateSavedResult
|
||||
cmp fortuneFateResult3D,eax
|
||||
jne fortune_session_fail
|
||||
inc edi
|
||||
cmp edi,32
|
||||
jb fortune_fate_motion
|
||||
mov fortunePhaseTime3D,5000
|
||||
call FortuneSessionStepTest
|
||||
cmp fortunePhase3D,3
|
||||
|
|
@ -209,6 +364,102 @@ fortune_session_unique_next:
|
|||
mov fortuneSpeechTime3D,3000
|
||||
call RenderFortuneDialog3D
|
||||
invoke ExpansionSnapshot,OFFSET fortuneSessionShot15
|
||||
mov fortunePhaseTime3D,1200
|
||||
call RenderFortuneDialog3D
|
||||
invoke ExpansionSnapshot,OFFSET fortuneFateResultShot
|
||||
xor edi,edi
|
||||
fortune_fate_tracking_frames:
|
||||
mov eax,edi
|
||||
xor edx,edx
|
||||
mov ecx,10
|
||||
div ecx
|
||||
add al,'0'
|
||||
add dl,'0'
|
||||
mov fortuneFateTrackingDigits[0],al
|
||||
mov fortuneFateTrackingDigits[1],dl
|
||||
mov fortuneFateTrackingBaseDigits[0],al
|
||||
mov fortuneFateTrackingBaseDigits[1],dl
|
||||
mov eax,edi
|
||||
shl eax,6
|
||||
add eax,2048
|
||||
mov fortunePhaseTime3D,eax
|
||||
call RenderFortuneDialog3D
|
||||
cmp fortuneOrbFrame3D,edi
|
||||
jne fortune_session_fail
|
||||
mov eax,fortuneOrbAnchorX3D[edi*4]
|
||||
add eax,128
|
||||
shr eax,8
|
||||
add eax,214
|
||||
cmp fortuneOrbCenterX3D,eax
|
||||
jne fortune_session_fail
|
||||
mov eax,fortuneOrbAnchorY3D[edi*4]
|
||||
add eax,128
|
||||
shr eax,8
|
||||
add eax,195
|
||||
cmp fortuneOrbCenterY3D,eax
|
||||
jne fortune_session_fail
|
||||
cmp animOpacity3D,255
|
||||
jne fortune_session_fail
|
||||
invoke ExpansionSnapshot,OFFSET fortuneFateTracking
|
||||
mov fortuneFateResult3D,-1
|
||||
call RenderFortuneDialog3D
|
||||
invoke ExpansionSnapshot,OFFSET fortuneFateTrackingBase
|
||||
mov eax,fortuneFateSavedResult
|
||||
mov fortuneFateResult3D,eax
|
||||
inc edi
|
||||
cmp edi,16
|
||||
jb fortune_fate_tracking_frames
|
||||
mov mouseX3D,120
|
||||
mov mouseY3D,232
|
||||
mov mouseClick3D,1
|
||||
call HandleFortuneDialog3D
|
||||
cmp fortunePhase3D,0
|
||||
jne fortune_session_fail
|
||||
cmp fortuneFateResult3D,-1
|
||||
jne fortune_session_fail
|
||||
cmp fortuneFateLengths3D[4],6
|
||||
jne fortune_session_fail
|
||||
; Test the full inclusive range, zero, fixed outcomes and both endpoints.
|
||||
mov ebx,400
|
||||
fortune_fate_range_samples:
|
||||
mov fortunePhase3D,0
|
||||
mov fortuneFateFields3D[256],'0'
|
||||
mov fortuneFateLengths3D[16],1
|
||||
mov fortuneFateFields3D[320],'1'
|
||||
mov fortuneFateFields3D[321],'0'
|
||||
mov fortuneFateFields3D[322],'0'
|
||||
mov fortuneFateFields3D[323],0
|
||||
mov fortuneFateLengths3D[20],3
|
||||
call SubmitFortuneFate3D
|
||||
cmp fortuneFateResult3D,100
|
||||
ja fortune_session_fail
|
||||
dec ebx
|
||||
jnz fortune_fate_range_samples
|
||||
; Endpoints are checked separately without relying on a random sample.
|
||||
mov fortunePhase3D,0
|
||||
mov fortuneFateFields3D[256],'0'
|
||||
mov fortuneFateFields3D[320],'0'
|
||||
mov fortuneFateFields3D[321],0
|
||||
mov fortuneFateLengths3D[20],1
|
||||
call SubmitFortuneFate3D
|
||||
cmp fortuneFateResult3D,0
|
||||
jne fortune_session_fail
|
||||
mov fortunePhase3D,0
|
||||
mov fortuneFateFields3D[256],'1'
|
||||
mov fortuneFateFields3D[257],'0'
|
||||
mov fortuneFateFields3D[258],'0'
|
||||
mov fortuneFateLengths3D[16],3
|
||||
mov fortuneFateFields3D[320],'1'
|
||||
mov fortuneFateFields3D[321],'0'
|
||||
mov fortuneFateFields3D[322],'0'
|
||||
mov fortuneFateLengths3D[20],3
|
||||
call SubmitFortuneFate3D
|
||||
cmp fortuneFateResult3D,100
|
||||
jne fortune_session_fail
|
||||
mov fortunePhase3D,3
|
||||
mov fortunePhaseTime3D,1200
|
||||
call RenderFortuneDialog3D
|
||||
invoke ExpansionSnapshot,OFFSET fortuneFateHundredShot
|
||||
; Exit in the middle of an animation as well as after completion.
|
||||
mov mouseX3D,260
|
||||
mov mouseY3D,232
|
||||
|
|
@ -218,6 +469,12 @@ fortune_session_unique_next:
|
|||
jne fortune_session_fail
|
||||
cmp fortuneSessionRoom3D.pixels,0
|
||||
jne fortune_session_fail
|
||||
cmp fortuneSessionNumerals3D.pixels,0
|
||||
jne fortune_session_fail
|
||||
cmp fortuneFateLengths3D,0
|
||||
jne fortune_session_fail
|
||||
cmp fortuneFateFields3D,0
|
||||
jne fortune_session_fail
|
||||
call BeginFortuneDialog3D
|
||||
invoke FortuneChar3D,'1'
|
||||
invoke FortuneChar3D,'a'
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
RoofTestCrashFilter PROTO :DWORD
|
||||
.data
|
||||
expansionStage dd 1
|
||||
expansionOcclusionPixel dd 0
|
||||
|
|
@ -91,10 +92,13 @@ ExpansionSnapshot ENDP
|
|||
|
||||
ExpansionFrame PROC
|
||||
call UpdateCamera3D
|
||||
cmp hallLevel3D,HALL_ROOF
|
||||
je expansion_world_ready
|
||||
call RenderCeilingAndFloor
|
||||
call RenderWalls
|
||||
call PrepareCityDepth3D
|
||||
call RenderSprites
|
||||
expansion_world_ready:
|
||||
call RenderHall3D
|
||||
ret
|
||||
ExpansionFrame ENDP
|
||||
|
|
@ -130,6 +134,7 @@ expansion_door_tick:
|
|||
ExpansionDoorTicks ENDP
|
||||
|
||||
ExpansionTestStart PROC
|
||||
invoke SetUnhandledExceptionFilter,OFFSET RoofTestCrashFilter
|
||||
call InitPaths3D
|
||||
call LoadTextures3D
|
||||
call InitializeCasinoSprites3D
|
||||
|
|
@ -1164,6 +1169,7 @@ expansion_leaf_shut:
|
|||
call BarCameraNativeTests
|
||||
call FortuneDialogNativeTests
|
||||
call ExpansionDartsTests
|
||||
call RoofNativeTests
|
||||
invoke ExitProcess,0
|
||||
expansion_fail:
|
||||
invoke ExitProcess,expansionStage
|
||||
|
|
@ -1173,3 +1179,4 @@ include tools/tests/fortune_room_test.inc
|
|||
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
|
||||
|
|
|
|||
788
tools/tests/rooftop_test.inc
Normal file
|
|
@ -0,0 +1,788 @@
|
|||
ROOF_NAV_W equ 70
|
||||
ROOF_NAV_H equ 78
|
||||
.data
|
||||
roofTestHall db "build\hall-expansion\roof-lift-hall.bmp",0
|
||||
roofTestOpening db "build\hall-expansion\roof-lift-opening.bmp",0
|
||||
roofTestCabin db "build\hall-expansion\roof-lift-cabin.bmp",0
|
||||
roofTestEntry db "build\hall-expansion\roof-arrival.bmp",0
|
||||
roofTestSouth db "build\hall-expansion\roof-terrace.bmp",0
|
||||
roofTestEast db "build\hall-expansion\roof-city-east.bmp",0
|
||||
roofTestWest db "build\hall-expansion\roof-city-west.bmp",0
|
||||
roofTestSeated db "build\hall-expansion\roof-seated.bmp",0
|
||||
roofTestBar db "build\hall-expansion\roof-bar.bmp",0
|
||||
roofTestMix db "build\hall-expansion\roof-bar-mixing.bmp",0
|
||||
roofTestReturn db "build\hall-expansion\roof-return.bmp",0
|
||||
roofTestChairSide db "build\hall-expansion\roof-chair-side.bmp",0
|
||||
roofTestChairBack db "build\hall-expansion\roof-chair-back.bmp",0
|
||||
roofTestPlanter db "build\hall-expansion\roof-planter.bmp",0
|
||||
roofTestLean db "build\hall-expansion\roof-lean.bmp",0
|
||||
roofTestNight db "build\hall-expansion\roof-night.bmp",0
|
||||
roofTestHDEast db "build\hall-expansion\roof-hd-east.bmp",0
|
||||
roofTestHDSouth db "build\hall-expansion\roof-hd-south.bmp",0
|
||||
roofTestHDNight db "build\hall-expansion\roof-hd-night.bmp",0
|
||||
roofTestHDComposite db "build\hall-expansion\roof-hd-terrace.bmp",0
|
||||
roofTestHDMotion db "build\hall-expansion\roof-hd-traffic.bmp",0
|
||||
roofTestHDLeft db "build\hall-expansion\roof-hd-look-left.bmp",0
|
||||
roofTestHDRight db "build\hall-expansion\roof-hd-look-right.bmp",0
|
||||
roofTestHDQuay db "build\hall-expansion\roof-hd-canal-quays.bmp",0
|
||||
roofTestHDQuayNight db "build\hall-expansion\roof-hd-canal-night.bmp",0
|
||||
roofTestHDFastFog db "build\hall-expansion\roof-hd-vertex-fog.bmp",0
|
||||
roofTestHDWest db "build\hall-expansion\roof-hd-west.bmp",0
|
||||
roofTestHDEco db "build\hall-expansion\roof-hd-eco.bmp",0
|
||||
roofTestHDAuto db "build\hall-expansion\roof-hd-auto.bmp",0
|
||||
roofTestSoftware db "build\hall-expansion\roof-software-ground.bmp",0
|
||||
roofTestMissingArt db "build\hall-expansion\roof-missing-art-ground.bmp",0
|
||||
roofTestHDTiming db "build\hall-expansion\roof-hd-timing.bin",0
|
||||
roofTestDriver db "build\hall-expansion\roof-driver.txt",0
|
||||
roofTestNewline db 13,10
|
||||
roofTestHDMillis dd 0,24,0,0,0
|
||||
roofTestHDBmp db 42h,4Dh
|
||||
dd 54+ROOF_HD_W*ROOF_HD_H*4
|
||||
dw 0,0
|
||||
dd 54,40,ROOF_HD_W,-ROOF_HD_H
|
||||
dw 1,32
|
||||
dd 0,ROOF_HD_W*ROOF_HD_H*4,0,0,0,0
|
||||
roofTestHDBMI dd 40,ROOF_HD_W,-ROOF_HD_H
|
||||
dw 1,32
|
||||
dd 0,ROOF_HD_W*ROOF_HD_H*4,0,0,0,0
|
||||
roofTestHDDC dd 0
|
||||
roofTestHDBitmap dd 0
|
||||
roofTestHDPixels dd 0
|
||||
roofTestHDOld dd 0
|
||||
roofTestLeanMove db "build\hall-expansion\roof-lean-motion.bin",0
|
||||
roofTestGuestX dd 0
|
||||
roofTestFloorColor dd 0
|
||||
roofTestBank dd 0
|
||||
roofTestX dd 0
|
||||
roofTestY dd 0
|
||||
roofTestCrash db "build\hall-expansion\roof-crash.bin",0
|
||||
roofTestNavPath db "build\hall-expansion\roof-navigation.bin",0
|
||||
roofTestNavHeader dd ROOF_NAV_W,ROOF_NAV_H,FP_1*45/4,FP_1*5/4,FP_1/4,ROOF_SEAT_COUNT
|
||||
.data?
|
||||
roofTestWalkPixels db ROOF_NAV_W*ROOF_NAV_H dup(?)
|
||||
.code
|
||||
RoofTestHDSnapshot PROC uses ebx path:DWORD
|
||||
local count:DWORD
|
||||
call UpdateCamera3D
|
||||
call RenderRoof3D
|
||||
invoke PresentRoofHD3D,roofTestHDDC,0,0,ROOF_HD_W,ROOF_HD_H
|
||||
test eax,eax
|
||||
jz roof_test_hd_capture_failed
|
||||
invoke GdiFlush
|
||||
invoke CreateFileA,path,40000000h,0,0,2,80h,0
|
||||
mov ebx,eax
|
||||
invoke WriteFile,ebx,OFFSET roofTestHDBmp,54,ADDR count,0
|
||||
invoke WriteFile,ebx,roofTestHDPixels,ROOF_HD_W*ROOF_HD_H*4,ADDR count,0
|
||||
invoke CloseHandle,ebx
|
||||
ret
|
||||
roof_test_hd_capture_failed:
|
||||
invoke ExitProcess,95
|
||||
ret
|
||||
RoofTestHDSnapshot ENDP
|
||||
|
||||
RoofTestHD PROC uses ebx esi
|
||||
local started:DWORD,written:DWORD,driverPtr:DWORD,driverLen:DWORD
|
||||
mov expansionStage,94
|
||||
mov roofQualityMode3D,2
|
||||
call InitRoofHD3D
|
||||
test eax,eax
|
||||
jnz roof_test_hd_ready
|
||||
mov eax,roofHDStage3D
|
||||
add eax,100
|
||||
invoke ExitProcess,eax
|
||||
roof_test_hd_ready:
|
||||
invoke CreateFileA,OFFSET roofTestDriver,40000000h,0,0,2,80h,0
|
||||
mov ebx,eax
|
||||
mov esi,1F00h
|
||||
roof_test_driver_string:
|
||||
invoke glGetString,esi
|
||||
mov driverPtr,eax
|
||||
xor ecx,ecx
|
||||
roof_test_driver_length:
|
||||
cmp BYTE PTR [eax+ecx],0
|
||||
je roof_test_driver_write
|
||||
inc ecx
|
||||
jmp roof_test_driver_length
|
||||
roof_test_driver_write:
|
||||
mov driverLen,ecx
|
||||
invoke WriteFile,ebx,driverPtr,driverLen,ADDR written,0
|
||||
invoke WriteFile,ebx,OFFSET roofTestNewline,2,ADDR written,0
|
||||
inc esi
|
||||
cmp esi,1F02h
|
||||
jbe roof_test_driver_string
|
||||
invoke CloseHandle,ebx
|
||||
invoke CreateCompatibleDC,0
|
||||
mov roofTestHDDC,eax
|
||||
invoke CreateDIBSection,eax,OFFSET roofTestHDBMI,0,OFFSET roofTestHDPixels,0,0
|
||||
mov roofTestHDBitmap,eax
|
||||
invoke SelectObject,roofTestHDDC,eax
|
||||
mov roofTestHDOld,eax
|
||||
mov hallLevel3D,HALL_ROOF
|
||||
mov engineMode,MODE_WALK
|
||||
mov worldMap3D,OFFSET roofMap3D
|
||||
mov floorTexture3D,ROOF_DECK
|
||||
mov playerX,FP_1*275/10
|
||||
mov playerY,10*FP_1
|
||||
mov playerAngle,0
|
||||
mov roofLean3D,2
|
||||
mov roofLeanAngle3D,0
|
||||
mov roofLeanTime3D,1100
|
||||
mov roofNight3D,0
|
||||
mov keys3D[41h],1
|
||||
mov deltaTime3D,16
|
||||
add animationClock3D,16
|
||||
call UpdateRoof3D
|
||||
cmp roofLean3D,2
|
||||
jne roof_test_hd_failed
|
||||
cmp playerAngle,0
|
||||
je roof_test_hd_failed
|
||||
mov keys3D[41h],0
|
||||
mov playerAngle,0
|
||||
invoke RoofTestHDSnapshot,OFFSET roofTestHDEast
|
||||
add animationClock3D,1600
|
||||
invoke RoofTestHDSnapshot,OFFSET roofTestHDMotion
|
||||
mov playerAngle,464
|
||||
invoke RoofTestHDSnapshot,OFFSET roofTestHDLeft
|
||||
cmp roofLean3D,2
|
||||
jne roof_test_hd_failed
|
||||
cmp playerAngle,464
|
||||
jne roof_test_hd_failed
|
||||
cmp roofHandsContactX3D,320
|
||||
jle roof_test_hd_failed
|
||||
mov playerAngle,48
|
||||
invoke RoofTestHDSnapshot,OFFSET roofTestHDRight
|
||||
cmp playerAngle,48
|
||||
jne roof_test_hd_failed
|
||||
cmp roofHandsContactX3D,320
|
||||
jge roof_test_hd_failed
|
||||
mov playerAngle,180
|
||||
call UpdateRoofLook3D
|
||||
cmp playerAngle,104
|
||||
jne roof_test_hd_failed
|
||||
call RenderRoof3D
|
||||
invoke PresentRoofHD3D,roofTestHDDC,0,0,ROOF_HD_W,ROOF_HD_H
|
||||
test eax,eax
|
||||
jz roof_test_hd_failed
|
||||
mov playerAngle,408
|
||||
call RenderRoof3D
|
||||
invoke PresentRoofHD3D,roofTestHDDC,0,0,ROOF_HD_W,ROOF_HD_H
|
||||
test eax,eax
|
||||
jz roof_test_hd_failed
|
||||
mov roofLean3D,0
|
||||
mov playerX,20*FP_1
|
||||
mov playerY,20*FP_1
|
||||
mov playerAngle,128
|
||||
invoke RoofTestHDSnapshot,OFFSET roofTestHDSouth
|
||||
mov roofNight3D,1
|
||||
invoke RoofTestHDSnapshot,OFFSET roofTestHDNight
|
||||
mov roofNight3D,0
|
||||
mov playerX,12*FP_1
|
||||
mov playerY,10*FP_1
|
||||
mov playerAngle,224
|
||||
invoke RoofTestHDSnapshot,OFFSET roofTestHDQuay
|
||||
mov roofGLFogHint3D,1101h
|
||||
mov roofHDRenderClock3D,-1
|
||||
invoke RoofTestHDSnapshot,OFFSET roofTestHDFastFog
|
||||
mov roofGLFogHint3D,1102h
|
||||
mov roofHDRenderClock3D,-1
|
||||
mov roofNight3D,1
|
||||
invoke RoofTestHDSnapshot,OFFSET roofTestHDQuayNight
|
||||
mov roofNight3D,0
|
||||
mov playerAngle,256
|
||||
invoke RoofTestHDSnapshot,OFFSET roofTestHDWest
|
||||
mov playerX,20*FP_1
|
||||
mov playerY,7*FP_1
|
||||
mov playerAngle,128
|
||||
invoke RoofTestHDSnapshot,OFFSET roofTestHDComposite
|
||||
invoke GetTickCount
|
||||
mov started,eax
|
||||
mov esi,24
|
||||
roof_test_hd_frame_time:
|
||||
add animationClock3D,16
|
||||
call RenderRoof3D
|
||||
invoke PresentRoofHD3D,roofTestHDDC,0,0,ROOF_HD_W,ROOF_HD_H
|
||||
test eax,eax
|
||||
jz roof_test_hd_failed
|
||||
dec esi
|
||||
jnz roof_test_hd_frame_time
|
||||
invoke GdiFlush
|
||||
invoke GetTickCount
|
||||
sub eax,started
|
||||
mov roofTestHDMillis,eax
|
||||
invoke GetTickCount
|
||||
mov started,eax
|
||||
mov esi,24
|
||||
roof_test_cpu_only_time:
|
||||
call RenderRoof3D
|
||||
dec esi
|
||||
jnz roof_test_cpu_only_time
|
||||
invoke GetTickCount
|
||||
sub eax,started
|
||||
mov roofTestHDMillis[8],eax
|
||||
invoke GetTickCount
|
||||
mov started,eax
|
||||
mov esi,24
|
||||
roof_test_gpu_only_time:
|
||||
add animationClock3D,33
|
||||
call RenderRoofHD3D
|
||||
dec esi
|
||||
jnz roof_test_gpu_only_time
|
||||
invoke GetTickCount
|
||||
sub eax,started
|
||||
mov roofTestHDMillis[12],eax
|
||||
invoke GetTickCount
|
||||
mov started,eax
|
||||
mov esi,24
|
||||
roof_test_present_only_time:
|
||||
invoke PresentRoofHD3D,roofTestHDDC,0,0,ROOF_HD_W,ROOF_HD_H
|
||||
dec esi
|
||||
jnz roof_test_present_only_time
|
||||
invoke GdiFlush
|
||||
invoke GetTickCount
|
||||
sub eax,started
|
||||
mov roofTestHDMillis[16],eax
|
||||
invoke CreateFileA,OFFSET roofTestHDTiming,40000000h,0,0,2,80h,0
|
||||
mov ebx,eax
|
||||
invoke WriteFile,ebx,OFFSET roofTestHDMillis,20,ADDR written,0
|
||||
invoke CloseHandle,ebx
|
||||
; Quality profiles retain the full scene and a correctly aligned foreground.
|
||||
mov playerX,FP_1*275/10
|
||||
mov playerY,10*FP_1
|
||||
mov playerAngle,0
|
||||
mov roofLean3D,2
|
||||
mov roofLeanAngle3D,0
|
||||
mov roofQualityMode3D,1
|
||||
invoke RoofTestHDSnapshot,OFFSET roofTestHDEco
|
||||
cmp roofRenderW3D,640
|
||||
jne roof_test_hd_failed
|
||||
cmp roofRenderH3D,480
|
||||
jne roof_test_hd_failed
|
||||
mov roofQualityMode3D,0
|
||||
mov roofAutoWidth3D,960
|
||||
invoke RoofTestHDSnapshot,OFFSET roofTestHDAuto
|
||||
cmp roofRenderW3D,960
|
||||
jne roof_test_hd_failed
|
||||
mov deltaTime3D,100
|
||||
mov roofQualitySlow3D,0
|
||||
mov esi,11
|
||||
roof_test_auto_slow:
|
||||
add animationClock3D,100
|
||||
call UpdateRoofQuality3D
|
||||
dec esi
|
||||
jnz roof_test_auto_slow
|
||||
cmp roofRenderW3D,640
|
||||
jne roof_test_hd_failed
|
||||
call ClearKeys3D
|
||||
invoke WindowEvents3D,0,100h,77h,0
|
||||
add animationClock3D,16
|
||||
call UpdateRoofQuality3D
|
||||
cmp roofQualityMode3D,1
|
||||
jne roof_test_hd_failed
|
||||
call ClearKeys3D
|
||||
invoke WindowEvents3D,0,100h,77h,0
|
||||
add animationClock3D,16
|
||||
call UpdateRoofQuality3D
|
||||
cmp roofQualityMode3D,2
|
||||
jne roof_test_hd_failed
|
||||
cmp roofRenderW3D,1280
|
||||
jne roof_test_hd_failed
|
||||
call ClearKeys3D
|
||||
mov roofLean3D,0
|
||||
invoke SelectObject,roofTestHDDC,roofTestHDOld
|
||||
invoke DeleteObject,roofTestHDBitmap
|
||||
invoke DeleteDC,roofTestHDDC
|
||||
call FreeRoofHD3D
|
||||
cmp roofHDWindow3D,0
|
||||
jne roof_test_hd_failed
|
||||
cmp roofHDBuffer3D,0
|
||||
jne roof_test_hd_failed
|
||||
cmp roofHDRC3D,0
|
||||
jne roof_test_hd_failed
|
||||
cmp roofHDMeshScene3D[12],0
|
||||
jne roof_test_hd_failed
|
||||
cmp roofHDFinalDC3D,0
|
||||
jne roof_test_hd_failed
|
||||
cmp roofHDFinalBmp3D,0
|
||||
jne roof_test_hd_failed
|
||||
; Capability failure must return to software, without repeatedly opening contexts.
|
||||
mov roofHDReady3D,-1
|
||||
invoke PresentRoofHD3D,0,0,0,1280,960
|
||||
test eax,eax
|
||||
jnz roof_test_hd_failed
|
||||
cmp roofHDReady3D,-1
|
||||
jne roof_test_hd_failed
|
||||
; Software roads and water remain visible even if the city pack is missing.
|
||||
mov deltaTime3D,16
|
||||
mov roofQualityMode3D,0
|
||||
mov roofAutoWidth3D,960
|
||||
call UpdateCamera3D
|
||||
call RenderRoof3D
|
||||
cmp roofGroundRoadPixels3D,0
|
||||
je roof_test_hd_failed
|
||||
cmp roofGroundWaterPixels3D,0
|
||||
je roof_test_hd_failed
|
||||
invoke ExpansionSnapshot,OFFSET roofTestSoftware
|
||||
mov eax,roofCityArt3D.pixels
|
||||
push eax
|
||||
mov roofCityArt3D.pixels,0
|
||||
call RenderRoof3D
|
||||
cmp roofGroundRoadPixels3D,0
|
||||
je roof_test_hd_failed
|
||||
cmp roofGroundWaterPixels3D,0
|
||||
je roof_test_hd_failed
|
||||
invoke ExpansionSnapshot,OFFSET roofTestMissingArt
|
||||
pop eax
|
||||
mov roofCityArt3D.pixels,eax
|
||||
mov roofHDReady3D,0
|
||||
ret
|
||||
roof_test_hd_failed:
|
||||
invoke ExitProcess,94
|
||||
ret
|
||||
RoofTestHD ENDP
|
||||
|
||||
RoofTestNavigation PROC uses ebx esi edi
|
||||
local px:DWORD,py:DWORD,written:DWORD
|
||||
lea edi,roofTestWalkPixels
|
||||
xor esi,esi
|
||||
roof_test_nav_row:
|
||||
mov eax,esi
|
||||
imul eax,FP_1/4
|
||||
add eax,FP_1*5/4
|
||||
mov py,eax
|
||||
xor ebx,ebx
|
||||
roof_test_nav_column:
|
||||
mov eax,ebx
|
||||
imul eax,FP_1/4
|
||||
add eax,FP_1*45/4
|
||||
mov px,eax
|
||||
invoke CanWalk3D,px,py
|
||||
mov [edi],al
|
||||
inc edi
|
||||
inc ebx
|
||||
cmp ebx,ROOF_NAV_W
|
||||
jb roof_test_nav_column
|
||||
inc esi
|
||||
cmp esi,ROOF_NAV_H
|
||||
jb roof_test_nav_row
|
||||
invoke CreateFileA,OFFSET roofTestNavPath,40000000h,0,0,2,80h,0
|
||||
mov ebx,eax
|
||||
invoke WriteFile,ebx,OFFSET roofTestNavHeader,24,ADDR written,0
|
||||
invoke WriteFile,ebx,OFFSET roofSeatX3D,ROOF_SEAT_COUNT*4,ADDR written,0
|
||||
invoke WriteFile,ebx,OFFSET roofSeatY3D,ROOF_SEAT_COUNT*4,ADDR written,0
|
||||
invoke WriteFile,ebx,OFFSET roofSeatKind3D,ROOF_SEAT_COUNT*4,ADDR written,0
|
||||
invoke WriteFile,ebx,OFFSET roofTestWalkPixels,ROOF_NAV_W*ROOF_NAV_H,ADDR written,0
|
||||
invoke CloseHandle,ebx
|
||||
ret
|
||||
RoofTestNavigation ENDP
|
||||
RoofTestCrashFilter PROC uses ebx esi pointers:DWORD
|
||||
local written:DWORD
|
||||
mov esi,pointers
|
||||
mov esi,[esi+4]
|
||||
invoke CreateFileA,OFFSET roofTestCrash,40000000h,0,0,2,80h,0
|
||||
mov ebx,eax
|
||||
invoke WriteFile,ebx,esi,204,ADDR written,0
|
||||
invoke CloseHandle,ebx
|
||||
invoke ExitProcess,expansionStage
|
||||
mov eax,1
|
||||
ret
|
||||
RoofTestCrashFilter ENDP
|
||||
RoofTestTicks PROC uses esi count:DWORD
|
||||
mov esi,count
|
||||
roof_test_tick:
|
||||
mov deltaTime3D,16
|
||||
add animationClock3D,16
|
||||
call UpdateInteriorTravel3D
|
||||
call UpdateInteriorEye3D
|
||||
dec esi
|
||||
jnz roof_test_tick
|
||||
ret
|
||||
RoofTestTicks ENDP
|
||||
|
||||
RoofNativeTests PROC uses ebx esi edi
|
||||
invoke SetUnhandledExceptionFilter,OFFSET RoofTestCrashFilter
|
||||
mov expansionStage,90
|
||||
mov eax,bankroll
|
||||
mov roofTestBank,eax
|
||||
call InitRoof3D
|
||||
call LoadRoofAssets3D
|
||||
cmp roofSkyArt3D.imageWidth,512
|
||||
jne roof_test_fail
|
||||
cmp roofSkyArt3D.frames,4
|
||||
jne roof_test_fail
|
||||
cmp roofDetailArt3D.frames,15
|
||||
jne roof_test_fail
|
||||
cmp roofHandsArt3D.frames,32
|
||||
jne roof_test_fail
|
||||
cmp roofCityArt3D.frames,22
|
||||
jne roof_test_fail
|
||||
cmp roofNightArt3D.frames,4
|
||||
jne roof_test_fail
|
||||
xor esi,esi
|
||||
roof_test_surface_theme:
|
||||
mov roofNight3D,esi
|
||||
mov ebx,ROOF_DECK
|
||||
roof_test_surface:
|
||||
invoke ResolveRoofSurface3D,ebx
|
||||
cmp eax,65535
|
||||
jbe roof_test_fail
|
||||
mov edx,256
|
||||
cmp ebx,ROOF_FACADE
|
||||
jb @F
|
||||
mov edx,512
|
||||
@@:
|
||||
cmp ebx,ROOF_INDICATOR
|
||||
jne @F
|
||||
mov edx,64
|
||||
@@:
|
||||
cmp ebx,ROOF_LIGHT
|
||||
jne @F
|
||||
mov edx,1
|
||||
@@:
|
||||
cmp ecx,edx
|
||||
jne roof_test_fail
|
||||
inc ebx
|
||||
cmp ebx,ROOF_LAST_MATERIAL
|
||||
jbe roof_test_surface
|
||||
inc esi
|
||||
cmp esi,2
|
||||
jb roof_test_surface_theme
|
||||
mov roofNight3D,0
|
||||
invoke CityRayPlane3D,-512*FP_1,24*FP_1,405
|
||||
cmp eax,7FFFFFFFh
|
||||
jne roof_test_fail
|
||||
mov engineMode,MODE_WALK
|
||||
mov hallLevel3D,0
|
||||
mov worldMap3D,OFFSET casinoMap
|
||||
mov floorTexture3D,100
|
||||
mov playerX,LIFT_CENTRE_X
|
||||
mov playerY,FP_1*63/10
|
||||
mov playerAngle,384
|
||||
call UpdateCamera3D
|
||||
call UpdateInteriorEye3D
|
||||
call ExpansionFrame
|
||||
invoke ExpansionSnapshot,OFFSET roofTestHall
|
||||
mov playerY,FP_1*48/10
|
||||
call UpdateCamera3D
|
||||
invoke CanWalk3D,LIFT_CENTRE_X,FP_1*34/10
|
||||
test eax,eax
|
||||
jnz roof_test_fail
|
||||
call CheckInteractions
|
||||
cmp interactTargetId,INTERACT_LIFT_CALL
|
||||
jne roof_test_fail
|
||||
call TriggerHallInteraction3D
|
||||
cmp liftPhase3D,1
|
||||
jne roof_test_fail
|
||||
invoke RoofTestTicks,32
|
||||
call ExpansionFrame
|
||||
invoke ExpansionSnapshot,OFFSET roofTestOpening
|
||||
invoke RoofTestTicks,36
|
||||
cmp liftDoor3D,1000
|
||||
jne roof_test_fail
|
||||
invoke CanWalk3D,LIFT_CENTRE_X,FP_1*34/10
|
||||
test eax,eax
|
||||
jz roof_test_fail
|
||||
; Walk through the opened doorway using the actual normal movement handler.
|
||||
mov keys3D[57h],1
|
||||
mov esi,120
|
||||
roof_test_walk_in:
|
||||
call ExpansionWalkFrame
|
||||
cmp playerY,FP_1*26/10
|
||||
jb roof_test_inside
|
||||
dec esi
|
||||
jnz roof_test_walk_in
|
||||
jmp roof_test_fail
|
||||
roof_test_inside:
|
||||
call ClearKeys3D
|
||||
mov eax,playerX
|
||||
mov roofTestX,eax
|
||||
mov eax,playerY
|
||||
mov roofTestY,eax
|
||||
cmp hallLevel3D,0
|
||||
jne roof_test_fail
|
||||
invoke WindowEvents3D,0,100h,32h,0
|
||||
invoke RoofTestTicks,1
|
||||
cmp roofNight3D,1
|
||||
jne roof_test_fail
|
||||
call ClearKeys3D
|
||||
invoke WindowEvents3D,0,100h,31h,0
|
||||
invoke RoofTestTicks,1
|
||||
cmp roofNight3D,0
|
||||
jne roof_test_fail
|
||||
call ClearKeys3D
|
||||
call CheckInteractions
|
||||
cmp interactTargetId,INTERACT_LIFT_TRAVEL
|
||||
jne roof_test_fail
|
||||
call TriggerHallInteraction3D
|
||||
cmp liftPhase3D,3
|
||||
jne roof_test_fail
|
||||
invoke CanWalk3D,LIFT_CENTRE_X,2*FP_1
|
||||
test eax,eax
|
||||
jnz roof_test_fail
|
||||
invoke RoofTestTicks,68
|
||||
cmp liftPhase3D,4
|
||||
jne roof_test_fail
|
||||
cmp hallLevel3D,0
|
||||
jne roof_test_fail
|
||||
call ExpansionFrame
|
||||
invoke ExpansionSnapshot,OFFSET roofTestCabin
|
||||
invoke RoofTestTicks,270
|
||||
cmp hallLevel3D,HALL_ROOF
|
||||
jne roof_test_fail
|
||||
cmp worldMap3D,OFFSET roofMap3D
|
||||
jne roof_test_fail
|
||||
cmp liftPhase3D,2
|
||||
jne roof_test_fail
|
||||
mov eax,roofTestX
|
||||
cmp playerX,eax
|
||||
jne roof_test_fail
|
||||
mov eax,roofTestY
|
||||
cmp playerY,eax
|
||||
jne roof_test_fail
|
||||
mov playerAngle,128
|
||||
call ExpansionFrame
|
||||
invoke ExpansionSnapshot,OFFSET roofTestEntry
|
||||
mov keys3D[57h],1
|
||||
mov esi,120
|
||||
roof_test_walk_out:
|
||||
call ExpansionWalkFrame
|
||||
cmp playerY,FP_1*46/10
|
||||
ja roof_test_outside
|
||||
dec esi
|
||||
jnz roof_test_walk_out
|
||||
jmp roof_test_fail
|
||||
roof_test_outside:
|
||||
call ClearKeys3D
|
||||
call RoofTestNavigation
|
||||
mov expansionStage,91
|
||||
invoke CanWalk3D,29*FP_1,10*FP_1
|
||||
test eax,eax
|
||||
jnz roof_test_fail
|
||||
invoke CanWalk3D,15*FP_1,FP_1*45/10
|
||||
test eax,eax
|
||||
jnz roof_test_fail
|
||||
invoke CanWalk3D,FP_1*145/10,10*FP_1
|
||||
test eax,eax
|
||||
jnz roof_test_fail
|
||||
invoke CanWalk3D,FP_1*145/10,FP_1*77/10
|
||||
test eax,eax
|
||||
jnz roof_test_fail
|
||||
invoke CanWalk3D,12*FP_1,FP_1*68/10
|
||||
test eax,eax
|
||||
jnz roof_test_fail
|
||||
mov playerX,20*FP_1
|
||||
mov playerY,FP_1*66/10
|
||||
mov playerAngle,128
|
||||
call UpdateCamera3D
|
||||
call UpdateRoofEye3D
|
||||
call RenderCeilingAndFloor
|
||||
mov eax,screenBuffer[(460*640+320)*4]
|
||||
mov roofTestFloorColor,eax
|
||||
call ExpansionFrame
|
||||
mov eax,roofTestFloorColor
|
||||
cmp screenBuffer[(460*640+320)*4],eax
|
||||
jne roof_test_fail
|
||||
cmp cityDepthBuffer3D[(460*640+320)*4],4*FP_1
|
||||
ja roof_test_fail
|
||||
cmp playerX,20*FP_1
|
||||
jne roof_test_fail
|
||||
cmp playerY,FP_1*66/10
|
||||
jne roof_test_fail
|
||||
invoke ExpansionSnapshot,OFFSET roofTestSouth
|
||||
mov playerAngle,0
|
||||
call ExpansionFrame
|
||||
invoke ExpansionSnapshot,OFFSET roofTestEast
|
||||
mov playerAngle,256
|
||||
call ExpansionFrame
|
||||
invoke ExpansionSnapshot,OFFSET roofTestWest
|
||||
mov playerX,FP_1*145/10
|
||||
mov playerY,FP_1*84/10
|
||||
mov playerAngle,128
|
||||
call UpdateCamera3D
|
||||
call CheckInteractions
|
||||
cmp interactTargetId,INTERACT_ROOF_SEAT
|
||||
jne roof_test_fail
|
||||
call TriggerHallInteraction3D
|
||||
call UpdateInteriorEye3D
|
||||
cmp roofSeat3D,1
|
||||
jne roof_test_fail
|
||||
cmp viewEye3D,ROOF_SEATED_EYE
|
||||
jne roof_test_fail
|
||||
call ExpansionFrame
|
||||
invoke ExpansionSnapshot,OFFSET roofTestSeated
|
||||
mov keys3D[57h],1
|
||||
invoke RoofTestTicks,1
|
||||
cmp roofSeat3D,0
|
||||
jne roof_test_fail
|
||||
cmp playerY,FP_1*84/10
|
||||
jne roof_test_fail
|
||||
call ClearKeys3D
|
||||
xor ebx,ebx
|
||||
roof_test_all_seats:
|
||||
mov playerX,20*FP_1
|
||||
mov playerY,7*FP_1
|
||||
mov playerAngle,128
|
||||
mov roofTargetSeat3D,ebx
|
||||
mov interactTargetId,INTERACT_ROOF_SEAT
|
||||
call TryRoofInteraction3D
|
||||
lea eax,[ebx+1]
|
||||
cmp roofSeat3D,eax
|
||||
jne roof_test_fail
|
||||
call UpdateRoofEye3D
|
||||
mov eax,ROOF_SEATED_EYE
|
||||
cmp roofSeatKind3D[ebx*4],2
|
||||
jb @F
|
||||
mov eax,ROOF_STANDING_EYE
|
||||
@@:
|
||||
cmp viewEye3D,eax
|
||||
jne roof_test_fail
|
||||
mov eax,384
|
||||
cmp roofSeatKind3D[ebx*4],3
|
||||
jne @F
|
||||
mov eax,256
|
||||
@@:
|
||||
cmp playerAngle,eax
|
||||
jne roof_test_fail
|
||||
mov interactTargetId,INTERACT_ROOF_SEAT
|
||||
call TryRoofInteraction3D
|
||||
cmp roofSeat3D,0
|
||||
jne roof_test_fail
|
||||
cmp playerX,20*FP_1
|
||||
jne roof_test_fail
|
||||
cmp playerY,7*FP_1
|
||||
jne roof_test_fail
|
||||
cmp playerAngle,128
|
||||
jne roof_test_fail
|
||||
call UpdateRoofEye3D
|
||||
inc ebx
|
||||
cmp ebx,ROOF_SEAT_COUNT
|
||||
jb roof_test_all_seats
|
||||
mov expansionStage,92
|
||||
mov playerX,20*FP_1
|
||||
mov playerY,7*FP_1
|
||||
mov engineMode,MODE_BAR
|
||||
mov barPhase3D,0
|
||||
mov esi,30
|
||||
roof_test_bar_repeat:
|
||||
call RenderBar3D
|
||||
cmp playerX,20*FP_1
|
||||
jne roof_test_fail
|
||||
cmp playerY,7*FP_1
|
||||
jne roof_test_fail
|
||||
cmp viewEye3D,ROOF_STANDING_EYE
|
||||
jne roof_test_fail
|
||||
dec esi
|
||||
jnz roof_test_bar_repeat
|
||||
invoke ExpansionSnapshot,OFFSET roofTestBar
|
||||
mov barPhase3D,1
|
||||
mov barSelection3D,1
|
||||
mov barTime3D,1500
|
||||
call RenderBar3D
|
||||
invoke ExpansionSnapshot,OFFSET roofTestMix
|
||||
mov engineMode,MODE_WALK
|
||||
mov barPhase3D,0
|
||||
; Scale and full furniture volumes must survive side and back inspection.
|
||||
mov expansionStage,93
|
||||
mov playerX,FP_1*191/10
|
||||
mov playerY,10*FP_1
|
||||
mov playerAngle,256
|
||||
call ExpansionFrame
|
||||
invoke ExpansionSnapshot,OFFSET roofTestChairSide
|
||||
mov playerX,FP_1*177/10
|
||||
mov playerY,FP_1*116/10
|
||||
mov playerAngle,384
|
||||
call ExpansionFrame
|
||||
invoke ExpansionSnapshot,OFFSET roofTestChairBack
|
||||
mov playerX,FP_1*167/10
|
||||
mov playerY,FP_1*197/10
|
||||
mov playerAngle,256
|
||||
call ExpansionFrame
|
||||
invoke ExpansionSnapshot,OFFSET roofTestPlanter
|
||||
; The native gait advances position while every step respects roof collision.
|
||||
mov eax,roofGuestX3D[0]
|
||||
mov roofTestGuestX,eax
|
||||
mov roofGuestWait3D[0],0
|
||||
mov roofGuestDir3D[0],0
|
||||
invoke RoofTestTicks,20
|
||||
mov eax,roofGuestX3D[0]
|
||||
cmp eax,roofTestGuestX
|
||||
je roof_test_fail
|
||||
cmp roofGuestMoving3D[0],1
|
||||
jne roof_test_fail
|
||||
mov playerX,FP_1*275/10
|
||||
mov playerY,10*FP_1
|
||||
mov playerAngle,0
|
||||
call UpdateCamera3D
|
||||
call CheckRoofInteractions3D
|
||||
cmp interactTargetId,INTERACT_ROOF_RAIL
|
||||
jne roof_test_fail
|
||||
call TryRoofInteraction3D
|
||||
cmp roofLean3D,1
|
||||
jne roof_test_fail
|
||||
invoke RoofTestTicks,75
|
||||
cmp roofLean3D,2
|
||||
jne roof_test_fail
|
||||
cmp roofLeanTime3D,1100
|
||||
jne roof_test_fail
|
||||
call ExpansionFrame
|
||||
invoke ExpansionSnapshot,OFFSET roofTestLean
|
||||
invoke CanWalk3D,27*FP_1,10*FP_1
|
||||
test eax,eax
|
||||
jnz roof_test_fail
|
||||
mov roofNight3D,1
|
||||
call ExpansionFrame
|
||||
invoke ExpansionSnapshot,OFFSET roofTestNight
|
||||
mov roofNight3D,0
|
||||
mov interactTargetId,INTERACT_ROOF_RAIL
|
||||
call TryRoofInteraction3D
|
||||
invoke RoofTestTicks,40
|
||||
cmp roofLean3D,0
|
||||
jne roof_test_fail
|
||||
cmp playerX,FP_1*275/10
|
||||
jne roof_test_fail
|
||||
cmp playerY,10*FP_1
|
||||
jne roof_test_fail
|
||||
cmp viewEye3D,ROOF_STANDING_EYE
|
||||
jne roof_test_fail
|
||||
mov expansionStage,92
|
||||
invoke RoofTestTicks,250
|
||||
cmp liftDoor3D,0
|
||||
jne roof_test_fail
|
||||
mov playerX,LIFT_CENTRE_X
|
||||
mov playerY,FP_1*48/10
|
||||
mov playerAngle,384
|
||||
call UpdateCamera3D
|
||||
call CheckInteractions
|
||||
call TriggerHallInteraction3D
|
||||
invoke RoofTestTicks,70
|
||||
mov playerY,FP_1*24/10
|
||||
call CheckInteractions
|
||||
cmp interactTargetId,INTERACT_LIFT_TRAVEL
|
||||
jne roof_test_fail
|
||||
call TriggerHallInteraction3D
|
||||
invoke RoofTestTicks,340
|
||||
cmp hallLevel3D,0
|
||||
jne roof_test_fail
|
||||
cmp worldMap3D,OFFSET casinoMap
|
||||
jne roof_test_fail
|
||||
mov playerY,FP_1*48/10
|
||||
call ExpansionFrame
|
||||
invoke ExpansionSnapshot,OFFSET roofTestReturn
|
||||
mov eax,bankroll
|
||||
cmp eax,roofTestBank
|
||||
jne roof_test_fail
|
||||
call FreeRoofAssets3D
|
||||
cmp roofSkyArt3D.pixels,0
|
||||
jne roof_test_fail
|
||||
cmp roofMaterialArt3D.pixels,0
|
||||
jne roof_test_fail
|
||||
cmp roofDetailArt3D.pixels,0
|
||||
jne roof_test_fail
|
||||
call LoadRoofAssets3D
|
||||
call RoofTestHD
|
||||
mov hallLevel3D,0
|
||||
mov worldMap3D,OFFSET casinoMap
|
||||
ret
|
||||
roof_test_fail:
|
||||
invoke ExitProcess,expansionStage
|
||||
ret
|
||||
RoofNativeTests ENDP
|
||||
|
|
@ -5,7 +5,10 @@ Push-Location $root
|
|||
try {
|
||||
& 'C:\Python314\python.exe' tools\tests\prepare_billiards_module.py
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Billiards helper preparation failed' }
|
||||
& "$MasmRoot\bin\ml.exe" /c /coff /Cp /nologo /I. /Fobuild\billiards\module.obj tools\tests\billiards_module.asm
|
||||
$assembler = "$MasmRoot\bin\ml.exe"
|
||||
$modernAssembler = 'C:\Program Files\Microsoft Visual Studio\18\Community\VC\Tools\MSVC\14.51.36231\bin\Hostx64\x86\ml.exe'
|
||||
if (Test-Path -LiteralPath $modernAssembler) { $assembler = $modernAssembler }
|
||||
& $assembler /c /coff /Cp /nologo /I. /Fobuild\billiards\module.obj tools\tests\billiards_module.asm
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Billiards module assembly failed' }
|
||||
$sdk = Get-ChildItem -LiteralPath "${env:ProgramFiles(x86)}\Windows Kits\10\Lib" -Directory |
|
||||
Sort-Object Name -Descending |
|
||||
|
|
|
|||