diff --git a/.gitignore b/.gitignore index 5864a41..886ec52 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,8 @@ __pycache__/ /build/buckshot/ /build/coin/ /build/dice/ +/build/darts/ +/build/observer/ /build/hall/ /build/icon_work/ /build/scratch/ diff --git a/assets/animations/darts/arm-throw-v3.bin b/assets/animations/darts/arm-throw-v3.bin new file mode 100644 index 0000000..1ff4260 Binary files /dev/null and b/assets/animations/darts/arm-throw-v3.bin differ diff --git a/assets/animations/darts/board-v1.bin b/assets/animations/darts/board-v1.bin new file mode 100644 index 0000000..be02913 Binary files /dev/null and b/assets/animations/darts/board-v1.bin differ diff --git a/assets/animations/darts/dart-views-v1.bin b/assets/animations/darts/dart-views-v1.bin new file mode 100644 index 0000000..5f03bb8 Binary files /dev/null and b/assets/animations/darts/dart-views-v1.bin differ diff --git a/assets/animations/darts/lounge-wall-v1.bin b/assets/animations/darts/lounge-wall-v1.bin new file mode 100644 index 0000000..52b8075 Binary files /dev/null and b/assets/animations/darts/lounge-wall-v1.bin differ diff --git a/assets/animations/fortune/cards-v1.bin b/assets/animations/fortune/cards-v1.bin new file mode 100644 index 0000000..b1747ab Binary files /dev/null and b/assets/animations/fortune/cards-v1.bin differ diff --git a/assets/animations/fortune/deal-v1.bin b/assets/animations/fortune/deal-v1.bin new file mode 100644 index 0000000..a0cd8a5 Binary files /dev/null and b/assets/animations/fortune/deal-v1.bin differ diff --git a/assets/animations/fortune/gesture-v1.bin b/assets/animations/fortune/gesture-v1.bin new file mode 100644 index 0000000..23f351a Binary files /dev/null and b/assets/animations/fortune/gesture-v1.bin differ diff --git a/assets/animations/fortune/orb-hands-v1.bin b/assets/animations/fortune/orb-hands-v1.bin new file mode 100644 index 0000000..b99d046 Binary files /dev/null and b/assets/animations/fortune/orb-hands-v1.bin differ diff --git a/assets/animations/fortune/orb-mist-v1.bin b/assets/animations/fortune/orb-mist-v1.bin new file mode 100644 index 0000000..3859b57 Binary files /dev/null and b/assets/animations/fortune/orb-mist-v1.bin differ diff --git a/assets/animations/fortune/room-v1.bin b/assets/animations/fortune/room-v1.bin new file mode 100644 index 0000000..98477f5 Binary files /dev/null and b/assets/animations/fortune/room-v1.bin differ diff --git a/assets/animations/fortune/shuffle-v1.bin b/assets/animations/fortune/shuffle-v1.bin new file mode 100644 index 0000000..fbca088 Binary files /dev/null and b/assets/animations/fortune/shuffle-v1.bin differ diff --git a/assets/sprites/darts/arm-throw-v3.png b/assets/sprites/darts/arm-throw-v3.png new file mode 100644 index 0000000..f52ca2c Binary files /dev/null and b/assets/sprites/darts/arm-throw-v3.png differ diff --git a/assets/sprites/darts/board-v1.png b/assets/sprites/darts/board-v1.png new file mode 100644 index 0000000..061fcd4 Binary files /dev/null and b/assets/sprites/darts/board-v1.png differ diff --git a/assets/sprites/darts/dart-views-v1.png b/assets/sprites/darts/dart-views-v1.png new file mode 100644 index 0000000..b8414c2 Binary files /dev/null and b/assets/sprites/darts/dart-views-v1.png differ diff --git a/assets/sprites/darts/lounge-wall-v1.png b/assets/sprites/darts/lounge-wall-v1.png new file mode 100644 index 0000000..426e592 Binary files /dev/null and b/assets/sprites/darts/lounge-wall-v1.png differ diff --git a/assets/sprites/darts/prompts-v1.md b/assets/sprites/darts/prompts-v1.md new file mode 100644 index 0000000..ac80888 --- /dev/null +++ b/assets/sprites/darts/prompts-v1.md @@ -0,0 +1,80 @@ +# Darts Artwork + +Generated with the built-in image-generation tool. Runtime assets live in +`assets/animations/darts/`; original selected PNGs are in this directory. +All grid cells are extracted at one common scale. No separately generated limb +pieces or stretched forearms are used. + +## arm-throw-v3.png + +Sports game animation sprites, harmless pub darts. Create a 4x4 equal-grid +transparent sprite atlas, 16 frames, square 1536x1536. FIRST PERSON from the +thrower's eyes looking at their OWN RIGHT forearm and hand aiming a small +regulation sports dart FORWARD INTO THE SCREEN toward a distant dartboard, +NOT horizontally sideways across the picture. Strong perspective foreshortening: +the blue tail flights are closest to viewer; steel point points AWAY FROM +VIEWER, deeper into scene, above and slightly left of flights. Entire forearm +and visible bent elbow, dark shirt rolled ABOVE elbow. Right upper arm extends +toward lower right, elbow is near bottom right, forearm rises toward upper +middle. Natural darts throwing posture seen from own eyes, not another person's +side-on arm. Detailed realistic digitized retro game sprite art, natural skin, +silver knurled barrel, blue nylon shaft and blue cross flights. Row1 four similar +forward aiming poses. Row2 four gradual drawback poses bending elbow toward +viewer. Row3 four gradual FORWARD THROW poses straightening elbow and opening +fingertips; dart disappears after frame10, frame11 and12 are empty hand. Row4 +four empty-handed follow-through/recovery poses, whole forearm still visible. +Same anatomy, elbow anchor, scale and lighting across frames. EACH WHOLE arm +stays inside its exact grid cell, 10 percent clear transparent margin all sides, +no overflow into neighboring cells. At most one coherent arm per cell. No scene, +no board, no faces, no borders, no text, no artifacts, no wrist-only framing, no +floating dart separated from fingers. Actual alpha transparent background. + +## dart-views-v1.png + +Asset sheet for a pub DARTS sports video game: ONLY a small realistic regulation +steel-tip SPORTS DART, no people, hands or other objects. 4 by 4 equal square +grid, 16 different coherent views on 1536x1536 transparent canvas. Same dart +every cell: short sharp silver steel point, cylindrical metallic silver tungsten +barrel with many machined knurled grooves and dark narrow grip rings, slim dark +blue nylon shaft, translucent rich royal-blue four-cross tail flights with +realistic curved edges and a small white stripe. Detailed realistic digitized +game rendering, NOT low-poly, not paper triangles. The sporting dart is about +14cm long and delicate, not a weapon. Rotate camera around the dart: row1 rear +three-quarter views angle 0,6,12,18 degrees; row2 24,30,36,42 degrees; row3 +48,54,60,66 degrees; row4 72,78,84,90 degrees. Angle0: tail blue flights closest +to camera, point recedes straight away and is mostly hidden. Angle90: complete +clean SIDE VIEW, steel point to LEFT, blue tail to RIGHT. All intermediate angles +smoothly reveal the left-pointing silver barrel. Horizontal projected axis, no +vertical roll. Each entire dart centered in its own cell, same scale, 15 percent +transparent margin all sides. Rear views are naturally narrow in width, side +views longer, same physical scale. Studio highlights for clear metal detail, +sharp blue tail. NO background, checkerboard, grid lines, captions, text or +shadows outside the isolated dart. Actual alpha transparency. + +## lounge-wall-v1.png + +A production game environment texture: straight-on FLAT FRONT ELEVATION of the +wide darts-lounge wall inside a richly furnished vintage casino. Square +1024x1024 raster asset, realistic digitized 1990s adventure game art, crisp small +details. NOT a room perspective photo, no sidewalls, no floor or ceiling. Wall +occupies full image. Deep muted emerald painted plaster above waist-high dark +walnut wainscoting, brass trim, cream plaster details, burgundy accents, varied +restrained colors. Central wall area around x50%,y42% must be clean emerald +plaster with space for a separate dartboard, no board drawn. Left around +x26%,y42%: substantial black chalkboard in an aged brass frame with subtle +hand-written darts scores and tally marks. Right around x76%,y42%: illuminated +glass trophy cabinet with silver cups, darts memorabilia, folded burgundy cloth, +a framed old tournament photo. Warm-shaded brass wall lamps around x33% and +x67% at y23%, realistic pools of warm light on plaster, a small analog clock +above centre. Lower quarter: wainscot, two low cabinets and a narrow shelf for +chalk, coasters and a folded towel; leave centre lower wall clear. The composition +should read as an established pub-sports corner of an elegant casino, not an +elevator, not a small wooden box, not sci-fi, not orange all over. All objects in +true front elevation, fixed to wall, coherent scale and consistent lighting. +No people, hands, sports dart closeup, text labels, logos, watermark or frame +outside the image. + +## board-v1.png + +Deterministically generated exact 20-sector sports geometry, not AI artwork. +The sector order and scoring ring radii are shared with native scoring tests. diff --git a/assets/sprites/fortune/crystal-mist-v1.png b/assets/sprites/fortune/crystal-mist-v1.png new file mode 100644 index 0000000..68ddc6e Binary files /dev/null and b/assets/sprites/fortune/crystal-mist-v1.png differ diff --git a/assets/sprites/fortune/packed-v1.json b/assets/sprites/fortune/packed-v1.json new file mode 100644 index 0000000..57e9dbf --- /dev/null +++ b/assets/sprites/fortune/packed-v1.json @@ -0,0 +1,44 @@ +[ + { + "file": "room-v1.bin", + "width": 640, + "height": 480, + "frames": 1 + }, + { + "file": "shuffle-v1.bin", + "width": 256, + "height": 256, + "frames": 64 + }, + { + "file": "deal-v1.bin", + "width": 256, + "height": 256, + "frames": 32 + }, + { + "file": "gesture-v1.bin", + "width": 256, + "height": 256, + "frames": 32 + }, + { + "file": "orb-hands-v1.bin", + "width": 256, + "height": 256, + "frames": 32 + }, + { + "file": "orb-mist-v1.bin", + "width": 192, + "height": 192, + "frames": 16 + }, + { + "file": "cards-v1.bin", + "width": 96, + "height": 128, + "frames": 45 + } +] diff --git a/assets/sprites/fortune/prompts-v1.md b/assets/sprites/fortune/prompts-v1.md new file mode 100644 index 0000000..96d83f9 --- /dev/null +++ b/assets/sprites/fortune/prompts-v1.md @@ -0,0 +1,60 @@ +# Fortune Session Production Prompts + +Generated with the built-in image generation tool, not an API CLI. Original +outputs were copied into this project. Asset compilation is deterministic and +does not make image-generation calls. These are the production specifications. + +## teller-actions-v1.png + +Reference: `../hall/fortune-room-v2.png`, top-row faceless hooded teller only. +Transparent production sprite sheet, exactly eight columns by five rows, forty +equal square cells. Front-facing seated upper body, navy embroidered robe, +silver crescents, dark gloves, absolutely no face under the hood. Same silhouette, +size, head and shoulder position in every cell. No table, chair, candles, scenery, +labels or borders. Detailed painted retro pixel art. Row one: eight successive +poses taking and splitting a deck and beginning overhand shuffle. Row two: eight +successive packet-transfer shuffle poses ending with the deck centered. Row +three: eight dealing poses, left hand holds the deck, right hand reaches forward, +releases a card and retracts. Row four: eight card-turning and explanatory wrist +and open-palm gestures. Row five: eight poses lifting and circling both hands +around empty space for a separately rendered crystal ball. Articulate fingers, +wrists, forearms and sleeves, no camera changes or torso bouncing. Render the +whole sheet together for consistent character identity and palette. + +## seated-room-v1.png + +Full-screen 4:3 retro pixel-art background. First-person seated across an intimate +round tarot table in a cramped cluttered midnight-blue occult cellar. Empty +carved high-backed chair centered around 43 percent width, curtains behind it, +no person. Dark blue velvet tabletop covers the lower 42 percent, silver edge +trim and blank central area for separate cards. Teller will be composited with +head at 43 percent width, 17 percent height and hands near 54 percent height. +Left cabinets crowded with books, scrolls, herbs, skull and trinkets; right shelves +with glass bottles, blue crystal and a hanging blue lantern. Deep shadow with +cool candlelight, muted burgundy fabric and aged silver/brass accents. Believable +untidiness, draped curtains above and at the sides. No table cards, deck, ball, +lettering, numbers, UI or screen border. Keep the upper-right speech area clear. + +## tarot-symbols-v1.png + +Symbolic tarot atlas, exactly six columns by four rows, 24 equal portrait cells, +no gaps, letters or labels. Objects rather than human figures; detailed retro +pixel art, thin ivory borders, navy, silver, burgundy, gold and cyan. Row-major +symbols: traveller's satchel/staff/cliff; wand/tools; pillars/moon; crown/wheat; +stone throne; ceremonial hat/crossed keys; entwined roses/heart; chariot; lion/ +infinity; lantern/mountain; ornate wheel; scales/sword; inverted watch/tree; +scythe/rose/hourglass; pouring cups; horned stone mask/chains; lightning/tower; +star/water; moon/two towers; sun/sunflowers; trumpet/banner; globe/laurel. Last +two cells: symmetrical silver crescent and stars on midnight-blue card backs. +Keep each recognizable illustration completely within its consistent frame. + +## crystal-mist-v1.png + +Transparent VFX sprite sheet, exactly four columns by four rows, sixteen equal +square cells. Identical crystal ball on a small antique silver stand, front view, +fixed silhouette, scale and bottom-center anchor. No hands, person or table. +Pale-blue smoke spirals clockwise through one full cycle across sixteen frames. +Subtle blue sparks and lighting inside the glass, no text, faces, numbers or +symbols. Keep stand stationary; vary the internal smoke, not object placement. +Detailed painted retro pixel art, dark glass and luminous cyan-blue mist, clean +transparent corners, no matte or background. diff --git a/assets/sprites/fortune/seated-room-v1.png b/assets/sprites/fortune/seated-room-v1.png new file mode 100644 index 0000000..d513560 Binary files /dev/null and b/assets/sprites/fortune/seated-room-v1.png differ diff --git a/assets/sprites/fortune/tarot-symbols-v1.png b/assets/sprites/fortune/tarot-symbols-v1.png new file mode 100644 index 0000000..c3987be Binary files /dev/null and b/assets/sprites/fortune/tarot-symbols-v1.png differ diff --git a/assets/sprites/fortune/teller-actions-v1.png b/assets/sprites/fortune/teller-actions-v1.png new file mode 100644 index 0000000..8a16803 Binary files /dev/null and b/assets/sprites/fortune/teller-actions-v1.png differ diff --git a/bin/poker.exe b/bin/poker.exe index 0255642..88cc494 100644 Binary files a/bin/poker.exe and b/bin/poker.exe differ diff --git a/build/AssemblerCasinoLauncher.cs b/build/AssemblerCasinoLauncher.cs index fb60673..23c8d26 100644 --- a/build/AssemblerCasinoLauncher.cs +++ b/build/AssemblerCasinoLauncher.cs @@ -8,7 +8,7 @@ namespace AssemblerCasinoLauncher { static class Program { - const string ExpectedPokerSha256 = "C1714FEA01C6AA594A1895F56CCC287E5D1F35CDC829F5C2658FF05A4CBB6ECF"; + const string ExpectedPokerSha256 = "C3D857FDBF7597C976B16C5021D7F07951628E54F86FF2B7BB53F8A83FDE11CA"; [STAThread] static void Main() diff --git a/build/PokerGameSetupGui.cs b/build/PokerGameSetupGui.cs index 7680ce7..4264a1d 100644 --- a/build/PokerGameSetupGui.cs +++ b/build/PokerGameSetupGui.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Drawing; using System.Drawing.Drawing2D; @@ -27,7 +27,7 @@ namespace PokerGameSetup sealed class InstallerForm : Form { const string Marker = "<>"; - const string ExpectedPayloadSha256 = "718663039D0CCB6EE83C62D23C8F8F7A6D4C596135E75F757C101C568E8473E9"; + const string ExpectedPayloadSha256 = "1A2AC9A543AAF354EAD5BFA4716A8677143E27D994D55120AAF4ACF8296AAA6B"; readonly Color bg = Color.FromArgb(15, 12, 10); readonly Color panel = Color.FromArgb(28, 22, 18); readonly Color gold = Color.FromArgb(236, 186, 75); @@ -247,19 +247,44 @@ namespace PokerGameSetup Directory.CreateDirectory(Path.Combine(target, "runtime")); var exe = Assembly.GetExecutingAssembly().Location; - var bytes = File.ReadAllBytes(exe); var marker = System.Text.Encoding.UTF8.GetBytes(Marker); - var index = FindMarker(bytes, marker); - if (index < 0) throw new InvalidOperationException("В установщике не найден архив игры."); - var payloadBytes = bytes.Skip(index + marker.Length).ToArray(); - VerifyPayloadIntegrity(payloadBytes); + SetProgress(0, "Поиск архива игры..."); + long payloadStart = -1; + using (var fs = File.OpenRead(exe)) + { + payloadStart = FindMarkerInStream(fs, marker); + } + if (payloadStart < 0) throw new InvalidOperationException("В установщике не найден архив игры."); var tempZip = Path.Combine(Path.GetTempPath(), "assembler_casino_payload_" + Guid.NewGuid().ToString("N") + ".zip"); - File.WriteAllBytes(tempZip, payloadBytes); - try { + SetProgress(5, "Проверка целостности архива..."); + using (var fs = File.OpenRead(exe)) + { + fs.Position = payloadStart; + using (var outFs = File.Create(tempZip, 65536)) + using (var sha = SHA256.Create()) + { + var buf = new byte[65536]; + int r; + while ((r = fs.Read(buf, 0, buf.Length)) > 0) + { + sha.TransformBlock(buf, 0, r, null, 0); + outFs.Write(buf, 0, r); + } + sha.TransformFinalBlock(buf, 0, 0); + var actual = BitConverter.ToString(sha.Hash).Replace("-", ""); + if (!string.IsNullOrWhiteSpace(ExpectedPayloadSha256) && + ExpectedPayloadSha256 != "TO_BE_FILLED_BY_RELEASE_BUILD" && + !actual.Equals(ExpectedPayloadSha256, StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException("Архив игры внутри установщика был изменен. Установка остановлена."); + } + } + } + using (var zip = ZipFile.OpenRead(tempZip)) { var files = zip.Entries.Where(e => !string.IsNullOrEmpty(e.Name)).ToList(); @@ -285,23 +310,10 @@ namespace PokerGameSetup } finally { - try { File.Delete(tempZip); } catch { } + try { if (File.Exists(tempZip)) File.Delete(tempZip); } catch { } } } - void VerifyPayloadIntegrity(byte[] payloadBytes) - { - if (string.IsNullOrWhiteSpace(ExpectedPayloadSha256) || ExpectedPayloadSha256 == "TO_BE_FILLED_BY_RELEASE_BUILD") - return; - - using (var sha = SHA256.Create()) - { - var hash = sha.ComputeHash(payloadBytes); - var actual = BitConverter.ToString(hash).Replace("-", ""); - if (!actual.Equals(ExpectedPayloadSha256, StringComparison.OrdinalIgnoreCase)) - throw new InvalidOperationException("Архив игры внутри установщика был изменен. Установка остановлена."); - } - } void CreateDesktopShortcut(string target) { @@ -347,12 +359,48 @@ namespace PokerGameSetup catch { } } + long FindMarkerInStream(Stream stream, byte[] marker) + { + var buffer = new byte[1024 * 1024]; + long streamPos = 0; + int bytesRead; + int overlap = marker.Length - 1; + int carry = 0; + + while ((bytesRead = stream.Read(buffer, carry, buffer.Length - carry)) > 0) + { + int total = carry + bytesRead; + int idx = FindMarker(buffer, marker, total); + if (idx >= 0) + { + return streamPos + idx + marker.Length; + } + if (total >= overlap) + { + Buffer.BlockCopy(buffer, total - overlap, buffer, 0, overlap); + carry = overlap; + streamPos += (total - overlap); + } + else + { + carry = total; + } + } + return -1; + } + int FindMarker(byte[] bytes, byte[] marker) { - for (int i = 0; i <= bytes.Length - marker.Length; i++) + return FindMarker(bytes, marker, bytes.Length); + } + + int FindMarker(byte[] bytes, byte[] marker, int length) + { + for (int i = 0; i <= length - marker.Length; i++) { + if (bytes[i] != marker[0]) continue; bool ok = true; - for (int j = 0; j < marker.Length; j++) + for (int j = 1; j < marker.Length; j++) { if (bytes[i + j] != marker[j]) { ok = false; break; } } diff --git a/build/graphics/poker.exe b/build/graphics/poker.exe index 0255642..88cc494 100644 Binary files a/build/graphics/poker.exe and b/build/graphics/poker.exe differ diff --git a/casino3d.inc b/casino3d.inc index 4e6d5c6..4e68cff 100644 --- a/casino3d.inc +++ b/casino3d.inc @@ -171,6 +171,8 @@ include src_graphics\native_dice_data.inc include src_graphics\scratch_data.inc include src_graphics\hell_tower_data.inc include src_graphics\charms_data.inc +include src_graphics\observer_data.inc +include src_graphics\darts_data.inc .data ALIGN 4 @@ -4856,7 +4858,7 @@ check_target_terminal: check_target_observer: cmp eax, INTERACT_OBSERVER jne target_unported - call BeginUnavailable3D + call BeginObserver3D ret target_unported: @@ -5194,6 +5196,8 @@ include src_graphics\unavailable.inc include src_graphics\fortune_dialog.inc include src_graphics\hell_tower.inc include src_graphics\charms.inc +include src_graphics\observer.inc +include src_graphics\darts.inc ; --- Main Loop of the 3D Casino Engine --- StartHellTower3D PROC uses ebx esi edi @@ -5245,6 +5249,15 @@ dispatch_frame: jz @F call ToggleFullscreen3D @@: + cmp obsActive3D,0 + jne engine_observer_frame + cmp engineMode,MODE_OBSERVER + jne engine_observer_skip +engine_observer_frame: + call UpdateMouseLockState + call ObserverFrame3D + jmp present_frame +engine_observer_skip: invoke Pressed3D, 1Bh test eax, eax jz engine_tick @@ -5343,6 +5356,12 @@ check_walk_mode: call RenderMinimap jmp present_frame check_slots_mode: + cmp engineMode,MODE_DARTS + jne check_darts_done + call HandleDarts3D + call RenderDarts3D + jmp present_frame +check_darts_done: cmp engineMode,MODE_FORTUNE jne @F call HandleFortuneDialog3D @@ -5620,6 +5639,7 @@ engine_shutdown: call SaveProgress3D invoke PlayCasinoSound3D, 0, 0, 0 call FreeRussianAssets3D + call FreeDarts3D call FreeStartupAssets3D call FreeCardTableAssets3D call FreeBuckshotAssets3D diff --git a/dist/Assembler Casino.exe b/dist/Assembler Casino.exe index 316ed7f..9e3b676 100644 Binary files a/dist/Assembler Casino.exe and b/dist/Assembler Casino.exe differ diff --git a/dist/Uninstall Assembler Casino.exe b/dist/Uninstall Assembler Casino.exe index 0ef7c14..1cf579d 100644 Binary files a/dist/Uninstall Assembler Casino.exe and b/dist/Uninstall Assembler Casino.exe differ diff --git a/docs/DARTS.md b/docs/DARTS.md new file mode 100644 index 0000000..ea83c54 --- /dev/null +++ b/docs/DARTS.md @@ -0,0 +1,71 @@ +# Darts + +The board is on the first-floor wall near the billiards area. It opens a native +MASM darts match against Max. No Python, browser, external engine or network is +required by the game. + +## Match + +- Both players start at 500. First to reach exactly zero wins. +- Three darts per visit. Visits alternate between the player and Max. +- Any scoring ring can finish. This is a custom 500-point straight-out format, + not tournament 501 double-out. +- A negative remainder is a bust: the entire visit is cancelled and play passes + to the other player. A remainder of one is valid. +- The casino bankroll and saved profile are not changed by this game. + +## Controls + +- Move the mouse to aim; the outlined cross marks the current hit point. +- Hold the left mouse button to draw back, release to throw. +- The hand and cross have the same smooth, mixed-phase sway. Holding the aim + longer than 1.2 seconds gradually increases fatigue, capped after 4 seconds. + Precise mouse motion is retained independently of the hall's 320px UI grid. + There is no extra + random scatter after releasing the button. +- The target sector appears beside the turn indicator (S, D or T). +- Tab opens/closes the live score sheet and pauses the current action. +- R or the lower-right button starts a new match. Escape returns to the hall. + +## Visuals + +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; +he does not reroll a hit or bypass the sway. + +The independent +scene camera follows one immutable parabolic trajectory, passes beside the +dart, returns behind it, pauses at the hit, then returns to the throwing line. +The small fading trail samples the same trajectory. + +Whole-arm raster animation includes the elbow, draw-back, release and empty +follow-through. A single rigid canvas transform aligns the held dart with the +target; individual limb pieces are never stretched or assembled. + +The flying dart uses sixteen detailed camera-relative raster views. Its needle +anchor is registered per view, so the tip meets the scored board coordinate. +It is not drawn from flat-colored geometric triangles. + +The lounge has actual ray-projected front/side/back walls, floor and ceiling. +Chairs and the lamp table occupy fixed world planes, rather than turning to +face the camera. The wall chalkboard is rewritten with real scores and recent +visit history, including when seen from the moving camera. + +## Assets And Checks + +`tools/generators/pack_darts.py` is an offline asset packer. It builds the exact +20-sector board and packs the generated arm, dart views and lounge artwork. +Artwork provenance and prompts are in `assets/sprites/darts/prompts-v1.md`. + +`C:/Python314/python.exe tools/tests/check_darts.py` compiles the actual MASM +scene into a native fixture and checks 67 scoring vectors, 20 complete bot +matches, busts, per-player score isolation, immutable hits, real mouse-release +events, fine high-resolution mouse input, sway/fatigue, board footprint, Tab +behavior, camera motion and profile isolation. It renders native +640x480 framebuffers, a contact sheet and a GIF under `build/darts/`. + +`--parity` compares tested procedure instruction/relocation signatures to the +full shipping assembly object. The hall fixture additionally covers interaction +dispatch and the unchanged stairs, doors, bar and fortune room. diff --git a/docs/FORTUNE_AI.md b/docs/FORTUNE_AI.md index 7680f9a..fc23527 100644 --- a/docs/FORTUNE_AI.md +++ b/docs/FORTUNE_AI.md @@ -1,5 +1,17 @@ # Offline Fortune Teller +## Currently Shelved (2026-10-22) + +The seated visual session no longer starts the worker or generates readings. +The native scene deals and reveals cards, then explicitly says that explanation +is not implemented. The crystal-ball scene is visual only, without academic +grade selection or collection of student identity details. Existing inference +sources and local model files are retained for future work, not required by the +current game. The older AI archive is not an updated visual-session release. +See `FORTUNE_SESSION.md` for the current runtime and verification. + +The following documents the previous experimental neural integration. + The approved C++ exception is limited to the neural worker. MASM still owns the room, interaction, question input, card selection, dialogue rendering, process lifetime and all gameplay. No OpenAI API, Ollama, Python runtime, listening port, diff --git a/docs/FORTUNE_SESSION.md b/docs/FORTUNE_SESSION.md new file mode 100644 index 0000000..ceaac95 --- /dev/null +++ b/docs/FORTUNE_SESSION.md @@ -0,0 +1,58 @@ +# Seated Fortune Session + +The room interaction opens a fixed first-person view across the table. No player +position, camera or bankroll state is modified. Escape or Exit returns to walking. +All runtime input, random card selection, state transitions and rendering are MASM +in `src_graphics/fortune_dialog.inc`; Python only compiles offline image assets. + +## Tarot + +Enter a question (up to 240 CP1251 characters), then Ask or Enter. The question +stays visible under the table for the entire reading and cannot change midway. +Three distinct major arcana are selected in assembly, each with an orientation. +The 22 symbolic illustrations have separately packed 180-degree reversed faces. + +Sequence: shuffle 3.2 s; deal cards individually 1 s each; turn a card over 0.8 s; +show its name, orientation and honest unavailable explanation 3.6 s; repeat for +the next two cards; show the overall-answer placeholder; finish. Continue can +advance a completed speech, never skip a deal or a flip. Again clears the question +only after completion; Selection can return to the choice of scenes at any time. + +Speech is composited beside the teller, not an OS window. Its border grows with +the visible text. Characters reveal every 28 ms with a short upward pop. Rendering +does not advance timers: the session updates from the game clock, with bounded +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. + +## Assets + +Generated source art and production prompts: `assets/sprites/fortune/`. +Offline packer: `tools/generators/pack_fortune_session.py`. +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 +seated view is independent of the walking room's existing asset packs and map. + +## Verification + +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 +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 old multi-gigabyte neural ZIP is not rebuilt by this feature. Distributions +must include the new `assets/animations/fortune/` directory with the updated game. diff --git a/docs/OBSERVER.md b/docs/OBSERVER.md new file mode 100644 index 0000000..6581b09 --- /dev/null +++ b/docs/OBSERVER.md @@ -0,0 +1,49 @@ +# Native AI Observer + +The observer lounge opens a game selector rather than the old unavailable modal. +All gameplay, decisions, rendering and input remain MASM32. No neural model or +network connection is used. + +## Games + +- Heads-up Hold'em: the existing bot decision is shared by both seats. +- Blackjack: an autonomous first seat plays against the normal dealer. +- Durak: both throwing-in and transfer variants use the same legal-move bot. +- Billiards: both pyramid and eight-ball seats use the existing shot planner. + +These are real rounds using the normal shuffled decks, settlement and billiard +physics. The observer does not generate scripted winners. Both poker and Durak +hands are visible to the spectator, but each bot still sees only its own cards +and public information. Blackjack retains the dealer's normal hidden hole card. + +## Controls + +Select a game with the mouse, keys 1-6, or arrows and Enter. During a match: + +- Space or the pause button pauses/resumes decisions and animation. +- Tab or the speed button cycles 1x, 2x and 4x. +- N or restart starts a fresh round without adding an abandoned round to scores. +- Escape or Games returns to the selector. Escape again returns to the hall. + +Completed rounds remain visible for three simulated seconds before another +round starts. Each round uses a fresh virtual stack. Scores reset when choosing +a different watching session. Human gameplay commands are discarded while watching. + +## Profile Isolation + +The observer snapshots the player's balance, RNG state, tournament flag, bets, +game variants and blackjack/billiard opponent settings. These are restored before +leaving. Native save, NPC-contract progress and loss-protection charms have an +observer barrier. Watching does not spend personal funds, charms, grant balance +achievements, progress contracts or write the profile. + +## Verification + +`C:/Python314/python.exe tools/tests/check_observer.py` compiles fresh native game +and spectator procedures against existing rendering helpers. It runs 80 complete +card rounds, checks both billiard seats, pause and profile restoration, compares +runtime files before/after, and captures actual native framebuffers. + +After rebuilding the full game, `--parity` checks that the shipping procedures +match the native-tested object. `test_billiards_module.ps1` additionally exercises +ordinary human/AI billiards with observer mode disabled. diff --git a/poker.exe b/poker.exe index 0255642..88cc494 100644 Binary files a/poker.exe and b/poker.exe differ diff --git a/poker.obj b/poker.obj index 09c9c37..9da99e2 100644 Binary files a/poker.obj and b/poker.obj differ diff --git a/src_graphics/card_tables_render.inc b/src_graphics/card_tables_render.inc index 3706d71..e093265 100644 --- a/src_graphics/card_tables_render.inc +++ b/src_graphics/card_tables_render.inc @@ -40,6 +40,10 @@ DrawTableHeader3D PROC hdrLabel:DWORD,hdrPot:DWORD DrawTableHeader3D ENDP DrawStakeControls3D PROC uses ebx enabled:DWORD + cmp obsActive3D,0 + je @F + ret +@@: mov ebx, 006D7470h cmp enabled, 0 je @F @@ -84,6 +88,8 @@ poker_render_stake_enabled: invoke DrawStakeControls3D, eax mov eax, pkStreet3D invoke DrawString8x8, 180, 6, pkStreetNames3D[eax*4], 00C4B8A0h + cmp obsActive3D,0 + jne poker_observer_chips mov eax, pkRaise3D mov esi, OFFSET tableRaiseText3D cmp pkState3D, PK_IDLE @@ -99,6 +105,7 @@ poker_render_blind: pop eax invoke FormatNumber, eax, OFFSET hudNumBuffer invoke DrawString8x8, 12, 187, OFFSET hudNumBuffer, 00F6F4DDh +poker_observer_chips: invoke DrawNativeChips3D, 278, 196, pkPot3D, 00B14A49h cmp pkChipTimer3D, 320 jae @F @@ -144,6 +151,8 @@ poker_render_chip_path: xor ebx, ebx poker_render_hole: mov hidden, 0 + cmp obsActive3D,0 + jne poker_render_hole_state cmp ebx, 2 jb poker_render_hole_state mov hidden, 1 @@ -237,7 +246,13 @@ poker_render_status: cmp pkState3D, PK_RESULT jne poker_render_ai_status mov eax, pkOutcome3D + cmp obsActive3D,0 + je poker_personal_result + invoke DrawString8x8,108,103,obsResultNames3D[eax*4],00F4DF9Eh + jmp poker_result_class +poker_personal_result: invoke DrawString8x8, 108, 103, pkResults3D[eax*4], 00F4DF9Eh +poker_result_class: cmp pkResult3D, 1 jne poker_render_buttons invoke NativePokerClass3D, pkScore3D @@ -315,9 +330,12 @@ bj_render_stake_enabled: inc eax @@: invoke DrawStakeControls3D, eax + cmp obsActive3D,0 + jne bj_observer_cards invoke DrawString8x8, 8, 36, OFFSET textBet3D, 00DDCFB1h invoke FormatNumber, cbBet3D, OFFSET hudNumBuffer invoke DrawString8x8, 80, 36, OFFSET hudNumBuffer, 00F6F4DDh +bj_observer_cards: cmp cbState3D, CB_IDLE je bj_render_buttons xor edi, edi diff --git a/src_graphics/charms.inc b/src_graphics/charms.inc index c664eb0..a57d7b3 100644 --- a/src_graphics/charms.inc +++ b/src_graphics/charms.inc @@ -7,6 +7,8 @@ ; Output: eax = 1 if saved (bet refunded to bankroll), 0 if not saved ; ------------------------------------------------------------------------------ TriggerCharmLossProtection PROC uses ebx ecx edx, betAmount:DWORD + cmp obsActive3D,0 + jne protection_none ; 1. Check Devil's Contract (50% chance to refund full bet) cmp charmDevilUses, 0 jle check_clover diff --git a/src_graphics/city_bins.inc b/src_graphics/city_bins.inc index d5c804f..d7fc9b6 100644 --- a/src_graphics/city_bins.inc +++ b/src_graphics/city_bins.inc @@ -5,6 +5,11 @@ 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,160 + jne @F + call ResolveDartsSurface3D + ret +@@: cmp eax,132 jb @F cmp eax,159 diff --git a/src_graphics/darts.inc b/src_graphics/darts.inc new file mode 100644 index 0000000..d11165d --- /dev/null +++ b/src_graphics/darts.inc @@ -0,0 +1,890 @@ +; One immutable hit point drives scoring, dart geometry and the follow camera. +.code +LoadDarts3D PROC + invoke LoadAnimPack3D,OFFSET dtBoard3D,OFFSET dtBoardPath + test eax,eax + jz dt_load_fail + cmp dtBoard3D.imageWidth,512 + jne dt_load_fail + cmp dtBoard3D.height,512 + jne dt_load_fail + cmp dtBoard3D.frames,1 + jne dt_load_fail + invoke LoadAnimPack3D,OFFSET dtHands3D,OFFSET dtHandsPath + test eax,eax + jz dt_load_fail + cmp dtHands3D.imageWidth,256 + jne dt_load_fail + cmp dtHands3D.height,256 + jne dt_load_fail + cmp dtHands3D.frames,16 + jne dt_load_fail + invoke LoadAnimPack3D,OFFSET dtDart3D,OFFSET dtDartPath + test eax,eax + jz dt_load_fail + cmp dtDart3D.imageWidth,256 + jne dt_load_fail + cmp dtDart3D.height,256 + jne dt_load_fail + cmp dtDart3D.frames,16 + jne dt_load_fail + invoke LoadAnimPack3D,OFFSET dtWall3D,OFFSET dtWallPath + test eax,eax + jz dt_load_fail + cmp dtWall3D.imageWidth,512 + jne dt_load_fail + cmp dtWall3D.height,512 + jne dt_load_fail + cmp dtWall3D.frames,1 + jne dt_load_fail + invoke LoadAnimPack3D,OFFSET dtChair3D,OFFSET dtChairPath + test eax,eax + jz dt_load_fail + invoke LoadAnimPack3D,OFFSET dtTable3D,OFFSET dtTablePath + test eax,eax + jz dt_load_fail + invoke LoadAnimPack3D,OFFSET dtRoom3D,OFFSET dtRoomPath + test eax,eax + jz dt_load_fail + cmp dtRoom3D.imageWidth,256 + jne dt_load_fail + cmp dtRoom3D.height,256 + jne dt_load_fail + cmp dtRoom3D.frames,12 + jne dt_load_fail + mov eax,1 + ret +dt_load_fail: + call FreeDarts3D + xor eax,eax + ret +LoadDarts3D ENDP + +FreeDarts3D PROC + invoke FreeAnimPack3D,OFFSET dtBoard3D + invoke FreeAnimPack3D,OFFSET dtHands3D + invoke FreeAnimPack3D,OFFSET dtDart3D + invoke FreeAnimPack3D,OFFSET dtWall3D + invoke FreeAnimPack3D,OFFSET dtChair3D + invoke FreeAnimPack3D,OFFSET dtTable3D + invoke FreeAnimPack3D,OFFSET dtRoom3D + ret +FreeDarts3D ENDP + +ResetDarts3D PROC + mov dtState3D,DT_AIM + mov dtTimer3D,0 + mov dtRemaining3D,500 + mov dtVisitStart3D,500 + mov dtScores3D,500 + mov dtScores3D[4],500 + mov dtActor3D,0 + mov dtWinner3D,0 + mov dtBotTimer3D,0 + mov dtBotPrepared3D,0 + mov dtScoreOpen3D,0 + mov dtHistoryRows3D,1 + mov dtHistory3D,500 + mov dtHistory3D[4],500 + mov dtWallDirty3D,1 + mov dtCount3D,0 + mov dtThrows3D,0 + mov dtVisit3D,1 + mov dtLast3D,0 + mov dtBust3D,0 + mov dtHolding3D,0 + mov dtMouseReleased3D,0 + mov dtHoldTime3D,0 + mov dtCamX3D,0 + mov dtCamY3D,0 + mov dtCamZ3D,0 + mov dtLens3D,DT_FOCAL + mov dtLookX3D,0 + mov dtLookY3D,0 + ret +ResetDarts3D ENDP + +BeginDarts3D PROC + call LoadDarts3D + xor eax,1 + mov dtError3D,eax + call ResetDarts3D + mov engineMode,MODE_DARTS + mov paused3D,0 + mov mouseX3D,160 + mov mouseY3D,107 + mov dtCursorX3D,320*256 + mov dtCursorY3D,214*256 + call ClearKeys3D + mov mouseClick3D,0 + ret +BeginDarts3D ENDP + +DartsAim3D PROC uses ebx + local amplitude:DWORD,phase:DWORD,secondary:DWORD + ; Smooth mixed phases and gradual hold fatigue; never scatter after release. + mov amplitude,10 + cmp dtHolding3D,0 + je dt_aim_phase + mov eax,dtHoldTime3D + sub eax,1200 + jle dt_aim_phase + xor edx,edx + mov ecx,350 + div ecx + cmp eax,8 + jbe @F + mov eax,8 +@@: + add amplitude,eax +dt_aim_phase: + mov eax,dtClock3D + xor edx,edx + mov ecx,3 + div ecx + and eax,511 + mov phase,eax + mov ebx,tableSin[eax*4] + imul ebx,amplitude + sar ebx,16 + mov eax,dtClock3D + xor edx,edx + mov ecx,5 + div ecx + add eax,91 + and eax,511 + mov secondary,eax + mov eax,tableSin[eax*4] + imul eax,3 + sar eax,16 + add ebx,eax + mov eax,dtCursorX3D + sub eax,320*256 + imul eax,DT_WALL + cdq + mov ecx,DT_FOCAL*256 + idiv ecx + cmp dtActor3D,0 + je @F + mov eax,dtBotAimX3D +@@: + add eax,ebx + cmp eax,-290 + jge @F + mov eax,-290 +@@: + cmp eax,290 + jle @F + mov eax,290 +@@: + mov dtAimX3D,eax + mov eax,phase + mov ebx,tableCos[eax*4] + mov ecx,amplitude + sub ecx,2 + imul ebx,ecx + sar ebx,16 + mov eax,secondary + sub eax,58 + and eax,511 + mov eax,tableSin[eax*4] + imul eax,3 + sar eax,16 + add ebx,eax + mov eax,dtCursorY3D + mov edx,214*256 + sub edx,eax + imul eax,edx,DT_WALL + cdq + mov ecx,DT_FOCAL*256 + idiv ecx + cmp dtActor3D,0 + je @F + mov eax,dtBotAimY3D +@@: + add eax,ebx + cmp eax,-245 + jge @F + mov eax,-245 +@@: + cmp eax,245 + jle @F + mov eax,245 +@@: + mov dtAimY3D,eax + ret +DartsAim3D ENDP + +; Select a target from the visible score, then commit imperfect aim before release. +DartsBotAim3D PROC uses ebx esi + local ring:DWORD,target:DWORD + mov target,20 + mov ring,103 + mov eax,dtRemaining3D + cmp eax,20 + ja dt_bot_double + mov target,eax + mov ring,53 + jmp dt_bot_sector +dt_bot_double: + cmp eax,40 + ja dt_bot_special + test eax,1 + jnz dt_bot_special + shr eax,1 + mov target,eax + mov ring,166 + jmp dt_bot_sector +dt_bot_special: + cmp eax,50 + jne @F + mov dtBotDesired3D,50 + mov dtBotAimX3D,0 + mov dtBotAimY3D,0 + jmp dt_bot_scatter +@@: + cmp eax,25 + jne @F + mov dtBotDesired3D,25 + mov dtBotAimX3D,0 + mov dtBotAimY3D,11 + jmp dt_bot_scatter +@@: + cmp eax,60 + ja dt_bot_sector + xor edx,edx + mov ecx,3 + div ecx + test edx,edx + jnz dt_bot_single20 + mov target,eax + jmp dt_bot_sector +dt_bot_single20: + mov ring,53 +dt_bot_sector: + mov eax,target + cmp ring,103 + jne @F + imul eax,3 +@@: + cmp ring,166 + jne @F + shl eax,1 +@@: + mov dtBotDesired3D,eax + xor ebx,ebx +@@: + mov eax,dtSector3D[ebx*4] + cmp eax,target + je dt_bot_direction + inc ebx + cmp ebx,20 + jb @B + xor ebx,ebx +dt_bot_direction: + imul eax,ebx,512 + xor edx,edx + mov ecx,20 + div ecx + mov esi,eax + mov eax,tableSin[esi*4] + imul eax,ring + sar eax,16 + mov dtBotAimX3D,eax + mov eax,tableCos[esi*4] + imul eax,ring + sar eax,16 + mov dtBotAimY3D,eax +dt_bot_scatter: + invoke RandomRange3D,17 + sub eax,8 + add dtBotAimX3D,eax + invoke RandomRange3D,17 + sub eax,8 + add dtBotAimY3D,eax + mov dtBotPrepared3D,1 + ret +DartsBotAim3D ENDP + +ThrowDarts3D PROC + cmp dtState3D,DT_AIM + jne dt_throw_done + cmp dtError3D,0 + jne dt_throw_done + mov eax,dtAimX3D + mov dtHitX3D,eax + mov eax,dtAimY3D + mov dtHitY3D,eax + mov dtState3D,DT_RELEASE + mov dtTimer3D,0 + mov dtHolding3D,0 + mov dtMouseReleased3D,0 + inc dtThrows3D +dt_throw_done: + ret +ThrowDarts3D ENDP + +; Board units are millimetres: 6.35/15.9 bull, 99..107 triple, 162..170 double. +DartsScore3D PROC uses ebx x:SDWORD,y:SDWORD + local angle:DWORD,oldControl:WORD,truncate:WORD + cmp x,-170 + jl dt_score_miss + cmp x,170 + jg dt_score_miss + cmp y,-170 + jl dt_score_miss + cmp y,170 + jg dt_score_miss + mov eax,x + imul eax,eax + mov ebx,y + imul ebx,ebx + add ebx,eax + cmp ebx,170*170 + ja dt_score_miss + cmp ebx,40 + jbe dt_score_bull + cmp ebx,253 + jbe dt_score_outer_bull + fnstcw oldControl + mov ax,oldControl + or ax,0C00h + mov truncate,ax + fldcw truncate + fild x + fild y + fpatan + fmul dtAngleFactor3D + fistp angle + fldcw oldControl + mov eax,angle + add eax,65536+1638 + and eax,65535 + imul eax,20 + shr eax,16 + mov eax,dtSector3D[eax*4] + mov edx,1 + cmp ebx,162*162 + jae dt_score_double + cmp ebx,99*99 + jb dt_score_done + cmp ebx,107*107 + ja dt_score_done + mov edx,3 + imul eax,3 + ret +dt_score_double: + mov edx,2 + shl eax,1 + ret +dt_score_bull: + mov eax,50 + mov edx,2 + ret +dt_score_outer_bull: + mov eax,25 + mov edx,1 + ret +dt_score_miss: + xor eax,eax + xor edx,edx +dt_score_done: + ret +DartsScore3D ENDP + +SettleDarts3D PROC uses ebx + cmp dtState3D,DT_FLIGHT + jne dt_settle_done + invoke DartsScore3D,dtHitX3D,dtHitY3D + mov dtLast3D,eax + mov dtMultiplier3D,edx + mov ebx,dtCount3D + cmp ebx,3 + jae dt_settle_done + mov dtMarkScore3D[ebx*4],eax + mov ecx,dtHitX3D + mov dtMarkX3D[ebx*4],ecx + mov ecx,dtHitY3D + mov dtMarkY3D[ebx*4],ecx + inc dtCount3D + mov ecx,dtRemaining3D + sub ecx,eax + js dt_bust +dt_score_accept: + mov dtRemaining3D,ecx + jmp dt_begin_impact +dt_bust: + mov eax,dtVisitStart3D + mov dtRemaining3D,eax + mov dtBust3D,1 +dt_begin_impact: + mov ebx,dtActor3D + mov eax,dtRemaining3D + mov dtScores3D[ebx*4],eax + mov dtWallDirty3D,1 + test eax,eax + jnz @F + mov dtWinner3D,ebx +@@: + cmp dtRemaining3D,0 + je dt_record_visit + cmp dtCount3D,3 + je dt_record_visit + cmp dtBust3D,0 + je dt_no_record +dt_record_visit: + call DartsRecordScore3D +dt_no_record: + mov dtState3D,DT_IMPACT + mov dtTimer3D,0 + invoke PlayCasinoSound3D,OFFSET soundClick,0,1 +dt_settle_done: + ret +SettleDarts3D ENDP + +DartsRecordScore3D PROC uses ebx esi edi + mov ebx,dtHistoryRows3D + cmp ebx,32 + jb @F + lea esi,dtHistory3D[8] + lea edi,dtHistory3D + mov ecx,62 + cld + rep movsd + mov ebx,31 +@@: + mov eax,dtScores3D + mov dtHistory3D[ebx*8],eax + mov eax,dtScores3D[4] + mov dtHistory3D[ebx*8+4],eax + inc ebx + mov dtHistoryRows3D,ebx + mov dtWallDirty3D,1 + ret +DartsRecordScore3D ENDP + +HandleDarts3D PROC + invoke Pressed3D,9 + test eax,eax + jz @F + xor dtScoreOpen3D,1 + mov dtHolding3D,0 + mov dtMouseReleased3D,0 +@@: + cmp dtScoreOpen3D,0 + jne dt_handle_done + invoke Pressed3D,52h + test eax,eax + jz @F + call ResetDarts3D +@@: + cmp mouseClick3D,0 + je dt_handle_game + cmp mouseY3D,218 + jb dt_handle_game + cmp mouseX3D,230 + jb dt_handle_game + call ResetDarts3D + mov mouseClick3D,0 +dt_handle_game: + cmp dtError3D,0 + jne dt_handle_done + mov eax,deltaTime3D + add dtClock3D,eax + cmp dtState3D,DT_AIM + jne dt_animate + cmp dtActor3D,0 + je dt_player_aim + cmp dtBotPrepared3D,0 + jne @F + call DartsBotAim3D +@@: + call DartsAim3D + mov eax,deltaTime3D + add dtBotTimer3D,eax + cmp dtBotTimer3D,900 + jb dt_handle_done + mov dtHolding3D,1 + mov eax,dtBotTimer3D + sub eax,900 + cmp eax,280 + jbe @F + mov eax,280 +@@: + mov dtHoldTime3D,eax + cmp dtBotTimer3D,1220 + jb dt_handle_done + invoke DartsScore3D,dtAimX3D,dtAimY3D + cmp eax,dtBotDesired3D + je dt_bot_commit + cmp dtBotTimer3D,1600 + jb dt_handle_done +dt_bot_commit: + call ThrowDarts3D + jmp dt_handle_done +dt_player_aim: + cmp mouseClick3D,0 + je @F + mov dtHolding3D,1 + mov dtHoldTime3D,0 +@@: + cmp dtHolding3D,0 + je dt_player_hover + mov eax,deltaTime3D + add dtHoldTime3D,eax + cmp dtHoldTime3D,4000 + jbe @F + mov dtHoldTime3D,4000 +@@: +dt_player_hover: + call DartsAim3D + cmp dtHolding3D,0 + jne dt_player_release + mov dtMouseReleased3D,0 + jmp dt_handle_done +dt_player_release: + cmp dtMouseReleased3D,0 + je dt_handle_done + call ThrowDarts3D + jmp dt_handle_done +dt_animate: + mov eax,deltaTime3D + add dtTimer3D,eax + cmp dtState3D,DT_RELEASE + jne dt_animate_flight + cmp dtTimer3D,200 + jb dt_handle_done + sub dtTimer3D,200 + mov dtState3D,DT_FLIGHT + jmp dt_handle_done +dt_animate_flight: + cmp dtState3D,DT_FLIGHT + jne dt_animate_impact + cmp dtTimer3D,DT_THROW_MS + jb dt_handle_done + call SettleDarts3D + jmp dt_handle_done +dt_animate_impact: + cmp dtState3D,DT_IMPACT + jne dt_animate_return + cmp dtTimer3D,1300 + jb dt_handle_done + mov dtState3D,DT_WIN + cmp dtRemaining3D,0 + je dt_handle_done + mov dtState3D,DT_RETURN + mov dtTimer3D,0 + jmp dt_handle_done +dt_animate_return: + cmp dtState3D,DT_RETURN + jne dt_handle_done + cmp dtTimer3D,750 + jb dt_handle_done + cmp dtBust3D,0 + jne dt_next_visit + cmp dtCount3D,3 + jb dt_return_aim +dt_next_visit: + mov dtBust3D,0 + mov dtCount3D,0 + inc dtVisit3D + xor dtActor3D,1 + mov eax,dtActor3D + mov eax,dtScores3D[eax*4] + mov dtRemaining3D,eax + mov dtVisitStart3D,eax +dt_return_aim: + mov dtState3D,DT_AIM + mov dtTimer3D,0 + mov dtMouseReleased3D,0 + mov dtHolding3D,0 + mov dtHoldTime3D,0 + mov dtBotPrepared3D,0 + mov dtBotTimer3D,0 +dt_handle_done: + mov mouseClick3D,0 + ret +HandleDarts3D ENDP + +DartsPath3D PROC uses ebx esi timeMs:DWORD + local progress:DWORD,pathX:DWORD,pathY:DWORD,pathZ:DWORD + mov eax,timeMs + cmp eax,DT_THROW_MS + jbe @F + mov eax,DT_THROW_MS +@@: + shl eax,16 + xor edx,edx + mov ecx,DT_THROW_MS + div ecx + mov progress,eax + mov ebx,eax + imul eax,DT_WALL + sar eax,16 + mov pathZ,eax + mov eax,dtHitX3D + sub eax,70 + imul eax,ebx + sar eax,16 + add eax,70 + mov pathX,eax + mov eax,65536 + sub eax,ebx + imul ebx + shrd eax,edx,16 + imul eax,280 + sar eax,16 + mov esi,eax + mov eax,dtHitY3D + add eax,35 + imul eax,ebx + sar eax,16 + sub eax,35 + add eax,esi + mov edx,eax + mov eax,pathX + mov ecx,pathZ + ret +DartsPath3D ENDP + +; Real orbit: behind -> alongside the dart -> behind the impact, with one camera basis. +DartsCamera3D PROC uses ebx esi + local progress:DWORD,weight:DWORD,timeMs:DWORD,factor:DWORD + local length2:DWORD,span:DWORD,targetZ:DWORD,deltaX:DWORD,deltaZ:DWORD + mov timeMs,0 + cmp dtState3D,DT_FLIGHT + jne @F + mov eax,dtTimer3D + mov timeMs,eax + jmp dt_camera_path +@@: + cmp dtState3D,DT_IMPACT + je dt_camera_end + cmp dtState3D,DT_WIN + je dt_camera_end + cmp dtState3D,DT_RETURN + jne dt_camera_path +dt_camera_end: + mov timeMs,DT_THROW_MS +dt_camera_path: + invoke DartsPath3D,timeMs + mov dtShotX3D,eax + mov dtShotY3D,edx + mov dtShotZ3D,ecx + mov eax,timeMs + cmp eax,DT_THROW_MS + jbe @F + mov eax,DT_THROW_MS +@@: + shl eax,16 + xor edx,edx + mov ecx,DT_THROW_MS + div ecx + mov progress,eax + mov ebx,eax + shr eax,8 + mov eax,tableSin[eax*4] + imul eax + shrd eax,edx,16 + mov weight,eax + imul eax,260 + sar eax,16 + mov ecx,420 + sub ecx,eax + mov eax,dtShotZ3D + sub eax,ecx + jns @F + xor eax,eax +@@: + mov dtCamZ3D,eax + mov eax,weight + imul eax,300 + sar eax,16 + mov ecx,dtShotX3D + sub ecx,70 + sub ecx,eax + mov dtCamX3D,ecx + mov eax,weight + imul eax,80 + sar eax,16 + add eax,dtShotY3D + add eax,35 + mov dtCamY3D,eax + mov eax,dtHitX3D + imul eax,ebx + sar eax,17 + mov esi,eax + mov ecx,dtShotX3D + sub ecx,eax + imul ecx,weight + sar ecx,16 + add ecx,esi + mov dtLookX3D,ecx + mov eax,dtHitY3D + imul eax,ebx + sar eax,17 + mov esi,eax + mov ecx,dtShotY3D + sub ecx,eax + imul ecx,weight + sar ecx,16 + add ecx,esi + mov dtLookY3D,ecx + mov eax,dtShotZ3D + sub eax,DT_WALL + imul eax,weight + sar eax,16 + add eax,DT_WALL + mov targetZ,eax + cmp dtState3D,DT_RETURN + jne dt_camera_basis + mov eax,dtTimer3D + cmp eax,750 + jbe @F + mov eax,750 +@@: + mov ecx,750 + sub ecx,eax + imul eax,ecx,65536 + xor edx,edx + mov ecx,750 + div ecx + mov ebx,eax + mov eax,dtCamX3D + imul eax,ebx + sar eax,16 + mov dtCamX3D,eax + mov eax,dtCamY3D + imul eax,ebx + sar eax,16 + mov dtCamY3D,eax + mov eax,dtCamZ3D + imul eax,ebx + sar eax,16 + mov dtCamZ3D,eax + mov eax,dtLookX3D + imul eax,ebx + sar eax,16 + mov dtLookX3D,eax + mov eax,dtLookY3D + imul eax,ebx + sar eax,16 + mov dtLookY3D,eax +dt_camera_basis: + mov eax,dtCamZ3D + imul eax,550 + xor edx,edx + mov ecx,DT_WALL-420 + div ecx + mov ecx,DT_FOCAL + sub ecx,eax + mov eax,weight + imul eax,160 + sar eax,16 + sub ecx,eax + cmp ecx,350 + jge @F + mov ecx,350 +@@: + mov dtLens3D,ecx + mov eax,dtLookX3D + sub eax,dtCamX3D + mov deltaX,eax + imul eax,eax + mov ebx,targetZ + sub ebx,dtCamZ3D + mov deltaZ,ebx + imul ebx,ebx + add eax,ebx + mov length2,eax + fild length2 + fsqrt + fistp span + cmp span,1 + jge @F + mov span,1 +@@: + mov eax,deltaX + shl eax,16 + cdq + idiv span + mov dtYawSin3D,eax + mov eax,deltaZ + shl eax,16 + cdq + idiv span + mov dtYawCos3D,eax + mov eax,dtLookY3D + sub eax,dtCamY3D + imul eax,dtLens3D + cdq + idiv span + add eax,214 + mov dtScreenY3D,eax + mov dtScreenX3D,320 + ret +DartsCamera3D ENDP + +DartsProject3D PROC uses ebx x:SDWORD,y:SDWORD,z:SDWORD + local deltaX:DWORD,deltaZ:DWORD,depth:DWORD,side:DWORD + mov eax,x + sub eax,dtCamX3D + mov deltaX,eax + mov ebx,z + sub ebx,dtCamZ3D + mov deltaZ,ebx + imul eax,dtYawCos3D + imul ebx,dtYawSin3D + sub eax,ebx + sar eax,16 + mov side,eax + mov eax,deltaX + imul eax,dtYawSin3D + mov ebx,deltaZ + imul ebx,dtYawCos3D + add eax,ebx + sar eax,16 + mov depth,eax + cmp eax,16 + jg @F + mov eax,16 +@@: + mov ecx,eax + mov eax,side + imul eax,dtLens3D + cdq + idiv ecx + add eax,320 + push eax + mov eax,y + sub eax,dtCamY3D + imul eax,dtLens3D + cdq + idiv ecx + mov edx,dtScreenY3D + sub edx,eax + pop eax + mov ecx,depth + ret +DartsProject3D ENDP + +ResolveDartsSurface3D PROC + cmp dtBoard3D.pixels,0 + jne @F + invoke LoadAnimPack3D,OFFSET dtBoard3D,OFFSET dtBoardPath +@@: + mov eax,dtBoard3D.pixels + mov ecx,512 + mov edx,512 + ret +ResolveDartsSurface3D ENDP + +DrawHallDarts3D PROC + cmp hallLevel3D,0 + jne @F + invoke DrawCityVertical3D,0,FP_1*3098/100,FP_1*275/10,FP_1*285/10,FP_1*115/100,FP_1*215/100,160 +@@: + ret +DrawHallDarts3D ENDP + +include src_graphics\darts_render.inc diff --git a/src_graphics/darts_art.inc b/src_graphics/darts_art.inc new file mode 100644 index 0000000..83d8718 --- /dev/null +++ b/src_graphics/darts_art.inc @@ -0,0 +1,3 @@ +; Generated per-view needle anchors in the 256px source canvas. +dtDartTipX3D dd 94,70,41,24,28,21,11,3,15,9,8,11,17,10,9,7 +dtDartTipY3D dd 142,139,139,139,135,135,134,133,132,131,130,130,116,118,123,128 diff --git a/src_graphics/darts_data.inc b/src_graphics/darts_data.inc new file mode 100644 index 0000000..bd6a8dd --- /dev/null +++ b/src_graphics/darts_data.inc @@ -0,0 +1,145 @@ +; All game and camera logic is native MASM; offline tooling only packs artwork. +MODE_DARTS equ 27 +INTERACT_DARTS equ 47 +DT_AIM equ 0 +DT_RELEASE equ 1 +DT_FLIGHT equ 2 +DT_IMPACT equ 3 +DT_RETURN equ 4 +DT_WIN equ 5 +DT_WALL equ 2700 +DT_FOCAL equ 950 +DT_THROW_MS equ 1200 +BeginDarts3D PROTO +HandleDarts3D PROTO +RenderDarts3D PROTO +LoadDarts3D PROTO +FreeDarts3D PROTO +ResetDarts3D PROTO +DartsAim3D PROTO +ThrowDarts3D PROTO +DartsCamera3D PROTO +DartsPath3D PROTO :DWORD +PrepareDartsRays3D PROTO +DrawDartsTrail3D PROTO +DrawDartsLine3D PROTO :SDWORD,:SDWORD,:SDWORD,:SDWORD,:DWORD +DartsBotAim3D PROTO +DartsRecordScore3D PROTO +UpdateDartsWall3D PROTO +DartsWallText3D PROTO :DWORD,:DWORD,:DWORD +DrawDartsScoreboard3D PROTO +DrawDartsProps3D PROTO +DrawDartsArm3D PROTO :DWORD,:SDWORD,:SDWORD +DartsScore3D PROTO :SDWORD,:SDWORD +SettleDarts3D PROTO +DrawDartsRoom3D PROTO +DrawDartsModel3D PROTO :SDWORD,:SDWORD,:SDWORD +DartsProject3D PROTO :SDWORD,:SDWORD,:SDWORD +DrawHallDarts3D PROTO +ResolveDartsSurface3D PROTO +.data +dtBoard3D AnimPack3D <> +dtHands3D AnimPack3D <> +dtDart3D AnimPack3D <> +dtWall3D AnimPack3D <> +dtChair3D AnimPack3D <> +dtTable3D AnimPack3D <> +dtRoom3D AnimPack3D <> +dtBoardPath db "assets\animations\darts\board-v1.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 +dtChairPath db "assets\animations\hall\lounge-chair-v5.bin",0 +dtTablePath db "assets\animations\hall\lamp-table-v5.bin",0 +dtRoomPath db "assets\animations\hall\room-materials-hd.bin",0 +dtState3D dd DT_AIM +dtTimer3D dd 0 +dtClock3D dd 0 +dtRemaining3D dd 500 +dtVisitStart3D dd 500 +dtScores3D dd 500,500 +dtActor3D dd 0 +dtWinner3D dd 0 +dtBotTimer3D dd 0 +dtBotPrepared3D dd 0 +dtBotAimX3D dd 0 +dtBotAimY3D dd 0 +dtBotDesired3D dd 60 +dtCursorX3D dd 320*256 +dtCursorY3D dd 214*256 +dtScoreOpen3D dd 0 +dtHistoryRows3D dd 1 +dtHistory3D dd 64 dup(0) +dtWallDirty3D dd 1 +dtVisit3D dd 1 +dtCount3D dd 0 +dtThrows3D dd 0 +dtLast3D dd 0 +dtMultiplier3D dd 0 +dtBust3D dd 0 +dtError3D dd 0 +dtHolding3D dd 0 +dtHoldTime3D dd 0 +dtMouseReleased3D dd 0 +dtAimX3D dd 0 +dtAimY3D dd 0 +dtHitX3D dd 0 +dtHitY3D dd 0 +dtMarkX3D dd 3 dup(0) +dtMarkY3D dd 3 dup(0) +dtMarkScore3D dd 3 dup(0) +dtShotX3D dd 0 +dtShotY3D dd 0 +dtShotZ3D dd 0 +dtCamX3D dd 0 +dtCamY3D dd 0 +dtCamZ3D dd 0 +dtLens3D dd DT_FOCAL +dtYawSin3D dd 0 +dtYawCos3D dd 65536 +dtLookX3D dd 0 +dtLookY3D dd 0 +dtScreenX3D dd 320 +dtScreenY3D dd 214 +include src_graphics\darts_art.inc +dtSector3D dd 20,1,18,4,13,6,10,15,2,17,3,19,7,16,8,11,14,9,12,5 +dtAngleFactor3D REAL8 10430.37835047045 +dtTitle db 196,192,208,210,209,32,53,48,48,0 +dtYou db 194,219,0 +dtBot db 204,192,202,209,0 +dtYourTurn db 194,192,216,32,213,206,196,0 +dtBotTurn db 213,206,196,32,204,192,202,209,192,0 +dtYouWin db 194,219,32,207,206,193,197,196,200,203,200,0 +dtBotWin db 204,192,202,209,32,207,206,193,197,196,200,203,0 +dtScoreTitle db 209,215,197,210,32,204,192,210,215,192,0 +; Fixed wall-axis furniture planes: axis, fixed coordinate, range, bottom/top, pack. +dtProps3D dd 0,2150,-1800,-1000,-1730,-380,OFFSET dtChair3D + dd 1,1950,1050,1850,-1730,-380,OFFSET dtChair3D + dd 1,1950,100,900,-1730,-380,OFFSET dtChair3D + dd 1,1780,850,1200,-1730,-680,OFFSET dtTable3D +dtThrow db 193,208,206,209,206,202,0 +dtLeft db 206,209,210,192,203,206,209,220,0 +dtVisit db 207,206,196,213,206,196,0 +dtWin db 195,206,210,206,194,206,33,32,208,206,194,205,206,32,205,206,203,220,0 +dtBust db 207,197,208,197,193,206,208,0 +dtMiss db 204,200,204,206,0 +dtNext db 196,192,203,220,216,197,0 +dtAgain db 209,205,206,194,192,0 +dtExit db 194,32,213,206,203,203,0 +dtPrompt db 69,58,32,196,192,208,210,209,0 +dtLoading db 205,197,32,211,196,192,203,206,209,220,32,199,192,195,208,211,199,200,210,220,32,196,192,208,210,209,0 +dtHold db 203,202,204,0 +dtBull db 66,85,76,76,0 +dtDouble db 68,0 +dtTriple db 84,0 +dtSingle db 83,0 +dtTarget db 214,197,203,220,0 +.data? +dtRayX3D dd 640 dup(?) +dtRayZ3D dd 640 dup(?) +dtRayDepth3D dd 640 dup(?) +dtRayU3D dd 640 dup(?) +dtRayFront3D dd 640 dup(?) +dtRayHitX3D dd 640 dup(?) +.code + diff --git a/src_graphics/darts_environment.inc b/src_graphics/darts_environment.inc new file mode 100644 index 0000000..366d56b --- /dev/null +++ b/src_graphics/darts_environment.inc @@ -0,0 +1,330 @@ +; 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 + local deltaX:DWORD,deltaY:DWORD,sourceX:DWORD,framePtr:DWORD + mov eax,pose + shl eax,18 + add eax,dtHands3D.pixels + mov framePtr,eax + mov eax,targetX + sub eax,10 + mov originX,eax + mov left,eax + add eax,363 + mov right,eax + mov eax,targetY + add eax,49 + mov originY,eax + add eax,182 + cmp eax,400 + jle @F + mov eax,400 +@@: + mov bottom,eax + mov eax,originY + sub eax,182 + cmp eax,32 + jge @F + mov eax,32 +@@: + mov row,eax + cmp left,0 + jge @F + mov left,0 +@@: + cmp right,640 + jle dt_arm_row + mov right,640 +dt_arm_row: + mov eax,row + cmp eax,bottom + jge dt_arm_done + sub eax,originY + mov deltaY,eax + mov eax,left + mov column,eax +dt_arm_column: + mov eax,column + cmp eax,right + jge dt_arm_next_row + sub eax,originX + mov deltaX,eax + sub eax,deltaY + imul eax,46341 + sar eax,16 + cmp eax,256 + jae dt_arm_skip + mov sourceX,eax + mov eax,deltaX + add eax,deltaY + imul eax,46341 + sar eax,16 + cmp eax,256 + jae dt_arm_skip + shl eax,8 + add eax,sourceX + mov esi,framePtr + mov eax,[esi+eax*4] + test eax,80000000h + jz dt_arm_skip + and eax,00FFFFFFh + mov edx,row + imul edx,640 + add edx,column + mov screenBuffer[edx*4],eax +dt_arm_skip: + inc column + jmp dt_arm_column +dt_arm_next_row: + inc row + jmp dt_arm_row +dt_arm_done: + ret +DrawDartsArm3D ENDP + +; Furniture is attached to fixed world planes, never camera-facing billboards. +DrawDartsProps3D PROC uses ebx esi edi + local item:DWORD,column:DWORD,depth:DWORD,sourceU:DWORD,packPtr:DWORD + local firstY:DWORD,lastY:DWORD,row:DWORD,sourceY:DWORD,stepY:DWORD,span:DWORD + mov item,0 +dt_prop_item: + imul eax,item,28 + lea esi,dtProps3D[eax] + mov eax,[esi+24] + mov packPtr,eax + mov column,0 +dt_prop_column: + mov ebx,column + cmp dword ptr [esi],0 + jne dt_prop_side + mov ecx,dtRayZ3D[ebx*4] + mov eax,[esi+4] + sub eax,dtCamZ3D + jmp dt_prop_distance +dt_prop_side: + mov ecx,dtRayX3D[ebx*4] + mov eax,[esi+4] + sub eax,dtCamX3D +dt_prop_distance: + test ecx,ecx + jz dt_prop_next_column + shl eax,16 + cdq + idiv ecx + cmp eax,40 + jl dt_prop_next_column + cmp eax,dtRayDepth3D[ebx*4] + jge dt_prop_next_column + mov depth,eax + mov eax,dtRayX3D[ebx*4] + mov edi,dtCamX3D + cmp dword ptr [esi],0 + je @F + mov eax,dtRayZ3D[ebx*4] + mov edi,dtCamZ3D +@@: + imul depth + shrd eax,edx,16 + add eax,edi + sub eax,[esi+8] + js dt_prop_next_column + mov ecx,[esi+12] + sub ecx,[esi+8] + cmp eax,ecx + jge dt_prop_next_column + mov edi,packPtr + imul eax,[edi].AnimPack3D.imageWidth + cdq + idiv ecx + mov sourceU,eax + mov eax,[esi+20] + sub eax,dtCamY3D + imul eax,dtLens3D + cdq + idiv depth + mov ecx,dtScreenY3D + sub ecx,eax + mov firstY,ecx + mov eax,[esi+16] + sub eax,dtCamY3D + imul eax,dtLens3D + cdq + idiv depth + mov ecx,dtScreenY3D + sub ecx,eax + mov lastY,ecx + sub ecx,firstY + jle dt_prop_next_column + mov span,ecx + mov eax,[edi].AnimPack3D.height + shl eax,16 + xor edx,edx + div span + mov stepY,eax + mov eax,firstY + cmp eax,32 + jge @F + mov eax,32 +@@: + mov row,eax + sub eax,firstY + imul eax,stepY + mov sourceY,eax + cmp lastY,400 + jle dt_prop_row + mov lastY,400 +dt_prop_row: + mov eax,row + cmp eax,lastY + jge dt_prop_next_column + mov eax,sourceY + shr eax,16 + cmp eax,[edi].AnimPack3D.height + jae dt_prop_next_column + imul eax,[edi].AnimPack3D.imageWidth + add eax,sourceU + mov edx,[edi].AnimPack3D.pixels + mov eax,[edx+eax*4] + test eax,80000000h + jz @F + mov edx,row + imul edx,640 + add edx,column + and eax,00FFFFFFh + mov screenBuffer[edx*4],eax +@@: + mov eax,stepY + add sourceY,eax + inc row + jmp dt_prop_row +dt_prop_next_column: + inc column + cmp column,640 + jb dt_prop_column + inc item + cmp item,4 + jb dt_prop_item + ret +DrawDartsProps3D ENDP + +; Write chalk glyphs directly into the wall texture, so the score follows perspective. +DartsWallText3D PROC uses ebx esi edi px:DWORD,py:DWORD,textPtr:DWORD + local row:DWORD,column:DWORD,pen:DWORD,glyph:DWORD + mov esi,textPtr + mov eax,px + mov pen,eax +dt_chalk_char: + movzx eax,byte ptr [esi] + test eax,eax + jz dt_chalk_done + lea eax,font8x8[eax*8] + mov glyph,eax + mov row,0 +dt_chalk_row: + mov eax,glyph + mov edx,row + movzx ebx,byte ptr [eax+edx] + mov column,0 +dt_chalk_pixel: + test ebx,80h + jz @F + mov eax,row + add eax,py + shl eax,9 + add eax,pen + add eax,column + mov edi,dtWall3D.pixels + mov dword ptr [edi+eax*4],0FFD8D9C2h +@@: + shl ebx,1 + inc column + cmp column,8 + jb dt_chalk_pixel + inc row + cmp row,8 + jb dt_chalk_row + add pen,8 + inc esi + jmp dt_chalk_char +dt_chalk_done: + ret +DartsWallText3D ENDP + +UpdateDartsWall3D PROC uses ebx esi edi + cmp dtWallDirty3D,0 + je dt_wall_score_done + cmp dtWall3D.pixels,0 + je dt_wall_score_done + mov ebx,104 +dt_chalk_clear: + mov eax,ebx + shl eax,9 + add eax,32 + mov edi,dtWall3D.pixels + lea edi,[edi+eax*4] + mov ecx,102 + mov eax,0FF202623h + rep stosd + inc ebx + cmp ebx,282 + jb dt_chalk_clear + invoke DartsWallText3D,40,108,OFFSET dtYou + invoke DartsWallText3D,86,108,OFFSET dtBot + invoke FormatNumber,dtScores3D,OFFSET hudNumBuffer + invoke DartsWallText3D,40,126,OFFSET hudNumBuffer + invoke FormatNumber,dtScores3D[4],OFFSET hudNumBuffer + invoke DartsWallText3D,86,126,OFFSET hudNumBuffer + mov ebx,dtHistoryRows3D + sub ebx,7 + jns @F + xor ebx,ebx +@@: + mov esi,150 +dt_chalk_history: + cmp ebx,dtHistoryRows3D + jae dt_chalk_clean + invoke FormatNumber,dtHistory3D[ebx*8],OFFSET hudNumBuffer + invoke DartsWallText3D,40,esi,OFFSET hudNumBuffer + invoke FormatNumber,dtHistory3D[ebx*8+4],OFFSET hudNumBuffer + invoke DartsWallText3D,86,esi,OFFSET hudNumBuffer + add esi,17 + inc ebx + jmp dt_chalk_history +dt_chalk_clean: + mov dtWallDirty3D,0 +dt_wall_score_done: + ret +UpdateDartsWall3D ENDP + +DrawDartsScoreboard3D PROC uses ebx esi edi + invoke DrawBoxBorder,54,23,212,174,001A2420h,00B0A16Eh,2 + invoke DrawString8x8,70,32,OFFSET dtScoreTitle,00E9DEBAh + invoke DrawString8x8,118,49,OFFSET dtYou,0090CDB9h + invoke DrawString8x8,198,49,OFFSET dtBot,00E8D7ADh + mov ebx,dtHistoryRows3D + sub ebx,10 + jns @F + xor ebx,ebx +@@: + mov esi,65 +dt_scoreboard_row: + cmp ebx,dtHistoryRows3D + jae dt_scoreboard_current + lea eax,[ebx+1] + invoke FormatNumber,eax,OFFSET hudNumBuffer + invoke DrawString8x8,70,esi,OFFSET hudNumBuffer,008A9C91h + invoke FormatNumber,dtHistory3D[ebx*8],OFFSET hudNumBuffer + invoke DrawString8x8,118,esi,OFFSET hudNumBuffer,00E2E2CFh + invoke FormatNumber,dtHistory3D[ebx*8+4],OFFSET hudNumBuffer + invoke DrawString8x8,198,esi,OFFSET hudNumBuffer,00E2E2CFh + add esi,10 + inc ebx + jmp dt_scoreboard_row +dt_scoreboard_current: + invoke DrawBox,66,170,188,1,00758772h + invoke FormatNumber,dtScores3D,OFFSET hudNumBuffer + invoke DrawString8x8,118,180,OFFSET hudNumBuffer,00FFFFFFh + invoke FormatNumber,dtScores3D[4],OFFSET hudNumBuffer + invoke DrawString8x8,198,180,OFFSET hudNumBuffer,00FFFFFFh + ret +DrawDartsScoreboard3D ENDP diff --git a/src_graphics/darts_render.inc b/src_graphics/darts_render.inc new file mode 100644 index 0000000..5f115ff --- /dev/null +++ b/src_graphics/darts_render.inc @@ -0,0 +1,657 @@ +include src_graphics\darts_environment.inc + +; One-pixel, alpha-blended trajectory, clipped to the 3D playfield. +DrawDartsLine3D PROC uses ebx esi edi x0:SDWORD,y0:SDWORD,x1:SDWORD,y1:SDWORD,opacity:DWORD + local stepX:DWORD,stepY:DWORD,steps:DWORD,px:DWORD,py:DWORD,paint:DWORD + mov eax,x1 + sub eax,x0 + cdq + xor eax,edx + sub eax,edx + mov ebx,eax + mov eax,y1 + sub eax,y0 + cdq + xor eax,edx + sub eax,edx + cmp eax,ebx + jbe @F + mov ebx,eax +@@: + cmp ebx,2048 + ja dt_line_done + test ebx,ebx + jnz @F + inc ebx +@@: + mov steps,ebx + mov eax,x1 + sub eax,x0 + shl eax,16 + cdq + idiv ebx + mov stepX,eax + mov eax,y1 + sub eax,y0 + shl eax,16 + cdq + idiv ebx + mov stepY,eax + mov eax,x0 + shl eax,16 + mov px,eax + mov eax,y0 + shl eax,16 + mov py,eax + inc steps +dt_line_pixel: + mov eax,px + sar eax,16 + cmp eax,640 + jae dt_line_skip + mov edx,py + sar edx,16 + cmp edx,32 + jl dt_line_skip + cmp edx,400 + jge dt_line_skip + imul edx,640 + add edx,eax + lea edi,screenBuffer[edx*4] + mov esi,[edi] + movzx eax,byte ptr [edi] + mov ecx,219 + sub ecx,eax + imul ecx,opacity + sar ecx,8 + add eax,ecx + mov paint,eax + mov eax,esi + shr eax,8 + and eax,255 + mov ecx,210 + sub ecx,eax + imul ecx,opacity + sar ecx,8 + add eax,ecx + shl eax,8 + or paint,eax + mov eax,esi + shr eax,16 + and eax,255 + mov ecx,180 + sub ecx,eax + imul ecx,opacity + sar ecx,8 + add eax,ecx + shl eax,16 + or eax,paint + mov [edi],eax +dt_line_skip: + mov eax,stepX + add px,eax + mov eax,stepY + add py,eax + dec steps + jnz dt_line_pixel +dt_line_done: + ret +DrawDartsLine3D ENDP + +DrawDartsTrail3D PROC uses ebx esi edi + local timeMs:DWORD,lastX:DWORD,lastY:DWORD,valid:DWORD,fade:DWORD,alpha:DWORD + mov eax,dtTimer3D + mov fade,256 + cmp dtState3D,DT_FLIGHT + je dt_trail_start + cmp dtState3D,DT_IMPACT + jne dt_trail_done + cmp eax,300 + jae dt_trail_done + imul eax,256 + xor edx,edx + mov ecx,300 + div ecx + mov ecx,256 + sub ecx,eax + mov fade,ecx + mov eax,DT_THROW_MS +dt_trail_start: + mov timeMs,eax + mov valid,0 + mov ebx,12 +dt_trail_segment: + imul eax,ebx,20 + mov ecx,timeMs + sub ecx,eax + js dt_trail_next + invoke DartsPath3D,ecx + invoke DartsProject3D,eax,edx,ecx + cmp ecx,60 + jl dt_trail_invalid + mov esi,eax + mov edi,edx + cmp valid,0 + je dt_trail_remember + mov eax,13 + sub eax,ebx + imul eax,9 + imul eax,fade + shr eax,8 + mov alpha,eax + invoke DrawDartsLine3D,lastX,lastY,esi,edi,alpha +dt_trail_remember: + mov lastX,esi + mov lastY,edi + mov valid,1 + jmp dt_trail_next +dt_trail_invalid: + mov valid,0 +dt_trail_next: + dec ebx + jns dt_trail_segment +dt_trail_done: + ret +DrawDartsTrail3D ENDP + +; Independent scene camera. Hall player coordinates are never borrowed or modified. +PrepareDartsRays3D PROC uses ebx esi edi + local cameraRay:DWORD,wallType:DWORD,wallT:DWORD,sideT:DWORD + xor ebx,ebx +dt_ray_column: + mov eax,ebx + sub eax,320 + shl eax,16 + cdq + idiv dtLens3D + mov cameraRay,eax + imul dtYawCos3D + shrd eax,edx,16 + add eax,dtYawSin3D + mov dtRayX3D[ebx*4],eax + mov eax,cameraRay + imul dtYawSin3D + shrd eax,edx,16 + neg eax + add eax,dtYawCos3D + mov dtRayZ3D[ebx*4],eax + mov wallT,7FFFFFFFh + mov wallType,0 + test eax,eax + jz dt_ray_side + mov ecx,eax + mov eax,DT_WALL + test ecx,ecx + jns @F + mov eax,-2000 + mov wallType,2 +@@: + sub eax,dtCamZ3D + shl eax,16 + cdq + idiv ecx + test eax,eax + jle dt_ray_side + mov wallT,eax +dt_ray_side: + mov ecx,dtRayX3D[ebx*4] + test ecx,ecx + jz dt_ray_hit + mov eax,2600 + test ecx,ecx + jns @F + mov eax,-2600 +@@: + sub eax,dtCamX3D + shl eax,16 + cdq + idiv ecx + test eax,eax + jle dt_ray_hit + cmp eax,wallT + jge dt_ray_hit + mov wallT,eax + mov wallType,1 +dt_ray_hit: + mov eax,wallT + cmp eax,1 + jge @F + mov eax,1 +@@: + mov dtRayDepth3D[ebx*4],eax + mov esi,eax + mov eax,dtRayX3D[ebx*4] + imul esi + shrd eax,edx,16 + add eax,dtCamX3D + mov dtRayHitX3D[ebx*4],eax + cmp wallType,1 + jne @F + mov eax,dtRayZ3D[ebx*4] + imul esi + shrd eax,edx,16 + add eax,dtCamZ3D +@@: + imul eax,256 + cdq + mov ecx,500 + idiv ecx + and eax,255 + mov dtRayU3D[ebx*4],eax + mov eax,wallType + mov dtRayFront3D[ebx*4],eax + inc ebx + cmp ebx,640 + jb dt_ray_column + ret +PrepareDartsRays3D ENDP + +; True inverse projection onto front/side/back walls, floor and ceiling. +DrawDartsRoom3D PROC uses ebx esi edi + local row:DWORD,rayY:DWORD,worldY:DWORD,depth:DWORD,sourceU:DWORD,sourceV:DWORD + call PrepareDartsRays3D + mov row,32 +dt_room_row: + mov eax,dtScreenY3D + sub eax,row + shl eax,16 + cdq + idiv dtLens3D + mov rayY,eax + mov eax,row + imul eax,640*4 + lea edi,screenBuffer[eax] + xor ebx,ebx +dt_room_pixel: + mov eax,rayY + imul dtRayDepth3D[ebx*4] + shrd eax,edx,16 + add eax,dtCamY3D + mov worldY,eax + cmp eax,-1730 + jl dt_room_floor + cmp eax,1300 + jg dt_room_ceiling + cmp dtRayFront3D[ebx*4],0 + jne dt_room_wall + mov eax,dtRayHitX3D[ebx*4] + add eax,256 + cmp eax,512 + jae dt_room_wall + mov sourceU,eax + mov eax,256 + sub eax,worldY + cmp eax,512 + jae dt_room_wall + shl eax,9 + add eax,sourceU + mov esi,dtBoard3D.pixels + mov eax,[esi+eax*4] + test eax,80000000h + jnz dt_room_store +dt_room_wall: + mov eax,570 + sub eax,worldY + imul eax,512 + cdq + mov ecx,1800 + idiv ecx + test eax,eax + jns @F + xor eax,eax +@@: + cmp eax,511 + jbe @F + mov eax,511 +@@: + shl eax,9 + mov sourceV,eax + mov eax,dtRayU3D[ebx*4] + and eax,15 + add eax,190 + cmp dtRayFront3D[ebx*4],0 + jne dt_room_wall_sample + mov ecx,dtRayHitX3D[ebx*4] + add ecx,850 + cmp ecx,1700 + jae dt_room_wall_sample + imul eax,ecx,512 + cdq + mov ecx,1700 + idiv ecx +dt_room_wall_sample: + add eax,sourceV + mov esi,dtWall3D.pixels + mov eax,[esi+eax*4] + jmp dt_room_store +dt_room_floor: + mov eax,-1730 + jmp dt_room_horizontal +dt_room_ceiling: + mov eax,1300 +dt_room_horizontal: + sub eax,dtCamY3D + shl eax,16 + cdq + idiv rayY + mov depth,eax + mov eax,dtRayX3D[ebx*4] + imul depth + shrd eax,edx,16 + add eax,dtCamX3D + imul eax,256 + cdq + mov ecx,500 + idiv ecx + and eax,255 + mov sourceU,eax + mov eax,dtRayZ3D[ebx*4] + imul depth + shrd eax,edx,16 + add eax,dtCamZ3D + imul eax,256 + cdq + mov ecx,500 + idiv ecx + and eax,255 + shl eax,8 + add eax,sourceU + mov esi,dtRoom3D.pixels + add esi,5*256*256*4 + mov eax,[esi+eax*4] + cmp rayY,0 + jle dt_room_store + mov eax,00444943h +dt_room_store: + and eax,00FFFFFFh + stosd + inc ebx + cmp ebx,640 + jb dt_room_pixel + inc row + cmp row,400 + jb dt_room_row + ret +DrawDartsRoom3D ENDP + +; Camera-relative raster views retain the generated metal and flight detail. +DrawDartsModel3D PROC uses ebx esi edi x:SDWORD,y:SDWORD,z:SDWORD + local deltaX:DWORD,deltaZ:DWORD,angle:DWORD,pose:DWORD + local centerX:DWORD,centerY:DWORD,depth:DWORD,span:DWORD,worldX:DWORD,worldY:DWORD + mov eax,x + sub eax,dtCamX3D + mov deltaX,eax + mov eax,z + sub eax,dtCamZ3D + mov deltaZ,eax + fild deltaX + fild deltaZ + fpatan + fabs + fmul dtAngleFactor3D + fistp angle + imul eax,angle,60 + add eax,32768 + shr eax,16 + cmp eax,15 + jbe @F + mov eax,15 +@@: + mov pose,eax + mov eax,x + sub eax,70 + imul eax,64 + cdq + mov ecx,DT_WALL + idiv ecx + mov edx,x + sub edx,eax + mov worldX,edx + mov eax,y + sub eax,280 + cmp z,DT_WALL-8 + jae @F + mov eax,dtTimer3D + imul eax,560 + xor edx,edx + mov ecx,DT_THROW_MS + div ecx + mov edx,y + add edx,280 + sub edx,eax + mov eax,edx +@@: + imul eax,64 + cdq + mov ecx,DT_WALL + idiv ecx + mov edx,y + sub edx,eax + mov worldY,edx + mov eax,z + sub eax,64 + invoke DartsProject3D,worldX,worldY,eax + cmp ecx,80 + jl dt_model_done + mov centerX,eax + mov centerY,edx + mov depth,ecx + imul eax,dtLens3D,160 + xor edx,edx + div depth + cmp eax,240 + jbe @F + mov eax,240 +@@: + cmp eax,12 + jae @F + mov eax,12 +@@: + mov span,eax + invoke DartsProject3D,x,y,z + mov centerX,eax + mov centerY,edx + mov ebx,pose + mov eax,dtDartTipX3D[ebx*4] + imul eax,span + shr eax,8 + sub centerX,eax + mov eax,dtDartTipY3D[ebx*4] + imul eax,span + shr eax,8 + sub centerY,eax + invoke DrawAnimSprite3D,OFFSET dtDart3D,pose,centerX,centerY,span,span +dt_model_done: + ret +DrawDartsModel3D ENDP + +RenderDarts3D PROC uses ebx esi edi + local bx0:DWORD,by0:DWORD,boardSpan:DWORD,handX:DWORD,handY:DWORD,pose:DWORD + cmp dtError3D,0 + jne dt_render_error + call DartsCamera3D + call UpdateDartsWall3D + call DrawDartsRoom3D + call DrawDartsProps3D + call DrawDartsTrail3D + xor ebx,ebx +dt_render_marks: + cmp ebx,dtCount3D + jae dt_render_flight + invoke DrawDartsModel3D,dtMarkX3D[ebx*4],dtMarkY3D[ebx*4],DT_WALL-4 + inc ebx + jmp dt_render_marks +dt_render_flight: + cmp dtState3D,DT_FLIGHT + jne dt_render_hand + invoke DartsProject3D,dtShotX3D,dtShotY3D,dtShotZ3D + cmp ecx,140 + jl dt_render_hand + invoke DrawDartsModel3D,dtShotX3D,dtShotY3D,dtShotZ3D +dt_render_hand: + mov pose,0 + cmp dtState3D,DT_AIM + je dt_render_aim_hand + cmp dtState3D,DT_RELEASE + je dt_render_release_hand + cmp dtState3D,DT_FLIGHT + jne dt_render_hud + cmp dtTimer3D,250 + jae dt_render_hud + mov eax,dtTimer3D + xor edx,edx + mov ecx,50 + div ecx + add eax,12 + cmp eax,15 + jbe dt_render_pose + mov eax,15 + jmp dt_render_pose +dt_render_release_hand: + mov eax,dtTimer3D + imul eax,3 + xor edx,edx + mov ecx,200 + div ecx + add eax,8 + cmp eax,10 + jne dt_render_pose + mov eax,11 + jmp dt_render_pose +dt_render_aim_hand: + mov eax,0 + cmp dtHolding3D,0 + je dt_render_pose + mov eax,dtHoldTime3D + imul eax,4 + xor edx,edx + mov ecx,280 + div ecx + cmp eax,3 + jbe @F + mov eax,3 +@@: + add eax,4 +dt_render_pose: + mov pose,eax + invoke DartsProject3D,dtAimX3D,dtAimY3D,DT_WALL + mov bx0,eax + mov by0,edx + add eax,16 + mov handX,eax + mov eax,edx + sub eax,110 + mov handY,eax + 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 +dt_render_hud: + invoke DrawBox,0,0,320,16,00151D20h + invoke DrawString8x8,8,4,OFFSET dtTitle,00E8D7ADh + invoke DrawString8x8,112,4,OFFSET dtYou,0090CDB9h + invoke FormatNumber,dtScores3D,OFFSET hudNumBuffer + invoke DrawString8x8,140,4,OFFSET hudNumBuffer,00FFFFFFh + invoke DrawString8x8,204,4,OFFSET dtBot,00E8D7ADh + invoke FormatNumber,dtScores3D[4],OFFSET hudNumBuffer + invoke DrawString8x8,248,4,OFFSET hudNumBuffer,00FFFFFFh + invoke DrawBox,0,200,320,40,00151D20h + mov ebx,OFFSET dtYourTurn + cmp dtActor3D,0 + je @F + mov ebx,OFFSET dtBotTurn +@@: + cmp dtState3D,DT_WIN + jne dt_hud_actor + mov ebx,OFFSET dtYouWin + cmp dtWinner3D,0 + je dt_hud_actor + mov ebx,OFFSET dtBotWin +dt_hud_actor: + invoke DrawString8x8,8,206,ebx,00E8D7ADh + xor ebx,ebx +dt_render_scores: + imul esi,ebx,52 + add esi,8 + invoke DrawBoxBorder,esi,220,46,16,00293436h,00728689h,1 + cmp ebx,dtCount3D + jae @F + invoke FormatNumber,dtMarkScore3D[ebx*4],OFFSET hudNumBuffer + add esi,7 + invoke DrawString8x8,esi,224,OFFSET hudNumBuffer,00FFFFFFh +@@: + inc ebx + cmp ebx,3 + jb dt_render_scores + invoke DrawBoxBorder,230,220,82,16,00293436h,00728689h,1 + invoke DrawString8x8,248,224,OFFSET dtAgain,00FFFFFFh + cmp dtState3D,DT_AIM + jne @F + invoke DartsScore3D,dtAimX3D,dtAimY3D + mov esi,OFFSET dtSingle + cmp edx,3 + jne dt_preview_double + mov esi,OFFSET dtTriple + jmp dt_preview_sector +dt_preview_double: + cmp edx,2 + jne dt_preview_sector + mov esi,OFFSET dtDouble +dt_preview_sector: + test edx,edx + jz dt_preview_value + mov ecx,edx + xor edx,edx + div ecx +dt_preview_value: + mov edi,eax + invoke DrawString8x8,176,206,OFFSET dtTarget,00AABDB6h + invoke DrawString8x8,224,206,esi,00F6E6ADh + invoke FormatNumber,edi,OFFSET hudNumBuffer + invoke DrawString8x8,236,206,OFFSET hudNumBuffer,00FFFFFFh + jmp dt_render_done +@@: + cmp dtState3D,DT_RELEASE + je dt_render_done + cmp dtState3D,DT_FLIGHT + je dt_render_done + cmp dtState3D,DT_RETURN + je dt_render_done + mov ebx,OFFSET dtWin + cmp dtRemaining3D,0 + je dt_render_done + mov ebx,OFFSET dtBust + cmp dtBust3D,0 + jne dt_render_banner + mov ebx,OFFSET dtMiss + cmp dtLast3D,0 + je dt_render_banner + mov ebx,OFFSET dtThrow +dt_render_banner: + invoke DrawString8x8,192,206,ebx,00E8D7ADh +dt_render_done: + cmp dtScoreOpen3D,0 + je @F + call DrawDartsScoreboard3D +@@: + ret +dt_render_error: + invoke DrawBox,0,0,320,240,00151D20h + invoke DrawString8x8,12,104,OFFSET dtLoading,00E8D7ADh + ret +RenderDarts3D ENDP diff --git a/src_graphics/durak_render.inc b/src_graphics/durak_render.inc index 7349c53..766e79d 100644 --- a/src_graphics/durak_render.inc +++ b/src_graphics/durak_render.inc @@ -85,6 +85,11 @@ dk_render_ai_card: shr edx, 1 add eax, edx mov posX, eax + mov hidden,1 + cmp obsActive3D,0 + je @F + mov hidden,0 +@@: cmp dkState3D, DK_DEAL jne @F mov eax, ebx @@ -94,10 +99,10 @@ dk_render_ai_card: mov edx, dkTimer3D sub edx, eax js dk_render_next_ai - invoke DrawSlidingCard3D, posX, 48, durakAIHand[ebx*4], 1, edx + invoke DrawSlidingCard3D, posX, 48, durakAIHand[ebx*4], hidden, edx jmp dk_render_next_ai @@: - invoke DrawNativeCard3D, posX, 48, durakAIHand[ebx*4], 1, 34 + invoke DrawNativeCard3D, posX, 48, durakAIHand[ebx*4], hidden, 34 dk_render_next_ai: inc ebx jmp dk_render_ai_card diff --git a/src_graphics/eightball.inc b/src_graphics/eightball.inc index 718c538..e7e170a 100644 --- a/src_graphics/eightball.inc +++ b/src_graphics/eightball.inc @@ -300,10 +300,13 @@ eight_finish_done: EightFinish3D ENDP EightHand3D PROC uses ebx esi + cmp obsActive3D,0 + jne eight_hand_bot cmp poolPlayer3D,1 jne eight_hand_human cmp poolAI3D,0 je eight_hand_human +eight_hand_bot: mov esi,55*FP_1 eight_hand_row: mov ebx,40*FP_1 diff --git a/src_graphics/fortune_dialog.inc b/src_graphics/fortune_dialog.inc index e53b7b8..c5e2ca2 100644 --- a/src_graphics/fortune_dialog.inc +++ b/src_graphics/fortune_dialog.inc @@ -1,975 +1,778 @@ -; ============================================================================== -; FORTUNE DIALOG - MASM32 / CP1251 -; Interactive Tarot Divination & Teacher Grade Oracle -; ============================================================================== - -include src_graphics\fortune_mind.inc - -BeginFortuneDialog3D PROC uses esi edi - call StopFortuneWorker3D - lea esi,screenBuffer - lea edi,fortuneBackground3D - mov ecx,SCREEN_SIZE - rep movsd - mov engineMode,MODE_FORTUNE - mov paused3D,0 - mov fortuneQuestionLength3D,0 - mov fortuneQuestion3D[0],0 - mov fortuneAnswer3D[0],0 - mov fortunePage3D,0 - mov fortuneShort3D,0 - mov fortuneReceiveLength3D,-1 - call ClearKeys3D - mov mouseClick3D,0 - - ; Initialize Mind state (submode menu, student defaults) - call InitFortuneMindState3D - mov fortuneWorkerState3D,2 ; Instant Mind ready by default - - ; Try optional worker launch (non-blocking, pipes) - invoke CreatePipe,OFFSET fortuneChildRead3D,OFFSET fortuneWrite3D,OFFSET fortunePipeSecurity3D,16384 - test eax,eax - jz fortune_worker_skip - invoke SetHandleInformation,fortuneWrite3D,1,0 - test eax,eax - jz fortune_worker_skip - invoke CreatePipe,OFFSET fortuneRead3D,OFFSET fortuneChildWrite3D,OFFSET fortunePipeSecurity3D,16384 - test eax,eax - jz fortune_worker_skip - invoke SetHandleInformation,fortuneRead3D,1,0 - test eax,eax - jz fortune_worker_skip - invoke CreateFileA,OFFSET fortuneNullName3D,40000000h,3,OFFSET fortunePipeSecurity3D,3,80h,0 - cmp eax,-1 - je fortune_worker_skip - mov fortuneNull3D,eax - lea edi,fortuneStartup3D - xor eax,eax - mov ecx,17 - rep stosd - mov fortuneStartup3D,68 - mov fortuneStartup3D[44],100h - mov eax,fortuneChildRead3D - mov fortuneStartup3D[56],eax - mov eax,fortuneChildWrite3D - mov fortuneStartup3D[60],eax - mov eax,fortuneNull3D - mov fortuneStartup3D[64],eax - invoke CreateProcessA,OFFSET fortuneWorkerPath3D,OFFSET fortuneWorkerCommand3D,0,0,1,08000000h,0,0,OFFSET fortuneStartup3D,OFFSET fortuneProcess3D - test eax,eax - jz fortune_worker_skip - invoke CloseHandle,fortuneProcess3D[4] - mov fortuneProcess3D[4],0 - invoke CloseHandle,fortuneChildRead3D - mov fortuneChildRead3D,0 - invoke CloseHandle,fortuneChildWrite3D - mov fortuneChildWrite3D,0 - invoke CloseHandle,fortuneNull3D - mov fortuneNull3D,0 - invoke GetTickCount - mov fortuneWorkerTick3D,eax - ret -fortune_worker_skip: - ; Native Mind is always ready - mov fortuneWorkerState3D,2 - ret -BeginFortuneDialog3D ENDP - -StopFortuneWorker3D PROC uses esi - cmp fortuneProcess3D,0 - je @F - invoke TerminateProcess,fortuneProcess3D,0 - invoke CloseHandle,fortuneProcess3D - mov fortuneProcess3D,0 -@@: - lea esi,fortuneRead3D - mov ecx,5 -fortune_close_handle: - push ecx - cmp DWORD PTR [esi],0 - je @F - invoke CloseHandle,DWORD PTR [esi] - mov DWORD PTR [esi],0 -@@: - pop ecx - add esi,4 - loop fortune_close_handle - mov fortuneReceiveLength3D,-1 - ret -StopFortuneWorker3D ENDP - -PollFortuneWorker3D PROC - cmp fortuneProcess3D,0 - je fortune_poll_done - cmp fortuneWorkerState3D,1 - je fortune_poll - cmp fortuneWorkerState3D,3 - jne fortune_poll_done -fortune_poll: - invoke PeekNamedPipe,fortuneRead3D,0,0,0,OFFSET fortuneAvailable3D,0 - test eax,eax - jz fortune_poll_done - cmp fortuneReceiveLength3D,-1 - jne fortune_poll_body - cmp fortuneAvailable3D,12 - jb fortune_poll_timeout - invoke ReadFile,fortuneRead3D,OFFSET fortuneReceiveHeader3D,12,OFFSET fortuneIo3D,0 - test eax,eax - jz fortune_poll_done - cmp fortuneIo3D,12 - jne fortune_poll_done - cmp fortuneReceiveHeader3D,31524346h - jne fortune_poll_done - cmp fortuneReceiveHeader3D[8],8192 - ja fortune_poll_done - mov eax,fortuneReceiveHeader3D[8] - mov fortuneReceiveLength3D,eax - sub fortuneAvailable3D,12 -fortune_poll_body: - mov eax,fortuneReceiveLength3D - cmp fortuneAvailable3D,eax - jb fortune_poll_timeout - test eax,eax - jz fortune_poll_status - invoke ReadFile,fortuneRead3D,OFFSET fortuneAnswer3D,eax,OFFSET fortuneIo3D,0 - test eax,eax - jz fortune_poll_done - mov eax,fortuneReceiveLength3D - cmp fortuneIo3D,eax - jne fortune_poll_done - mov fortuneAnswer3D[eax],0 -fortune_poll_status: - mov fortuneReceiveLength3D,-1 - cmp fortuneWorkerState3D,1 - jne fortune_poll_answer - cmp fortuneReceiveHeader3D[4],0 - jne fortune_poll_done - mov fortuneWorkerState3D,2 - ret -fortune_poll_answer: - cmp fortuneReceiveHeader3D[4],1 - je fortune_poll_answer_ready - cmp fortuneReceiveHeader3D[4],3 - jne fortune_poll_done - mov fortuneShort3D,1 -fortune_poll_answer_ready: - cmp fortuneReceiveHeader3D[8],0 - je fortune_poll_done - mov fortuneWorkerState3D,2 - mov fortunePage3D,0 - ret -fortune_poll_timeout: - invoke GetTickCount - sub eax,fortuneWorkerTick3D - cmp eax,180000 - ja fortune_poll_timeout_reset -fortune_poll_done: - ret -fortune_poll_timeout_reset: - mov fortuneWorkerState3D,2 - ret -PollFortuneWorker3D ENDP - -FortuneChar3D PROC character:DWORD - cmp engineMode,MODE_FORTUNE - jne fortune_char_done - - ; --- Mode 0: MENU --- - cmp fortuneSubMode3D,FORTUNE_SUBMODE_MENU - jne @F - mov eax,character - cmp eax,'1' - je fortune_menu_goto_tarot - cmp eax,'2' - je fortune_menu_goto_student - cmp eax,'0' - je fortune_menu_goto_exit - cmp eax,1Bh - je fortune_menu_goto_exit - ret -fortune_menu_goto_tarot: - mov fortuneSubMode3D,FORTUNE_SUBMODE_TAROT - mov fortuneWorkerState3D,2 - mov fortunePage3D,0 - ret -fortune_menu_goto_student: - mov fortuneSubMode3D,FORTUNE_SUBMODE_STUDENT - mov studentField3D,0 - mov fortunePage3D,0 - ret -fortune_menu_goto_exit: - mov engineMode,MODE_WALK - call ClearKeys3D - ret -@@: - - ; --- Mode 1: TAROT --- - cmp fortuneSubMode3D,FORTUNE_SUBMODE_TAROT - jne @F - mov eax,character - cmp eax,8 - je fortune_tarot_backspace - cmp eax,32 - jb fortune_char_done - cmp eax,255 - ja fortune_char_done - cmp eax,127 - je fortune_char_done - mov ecx,fortuneQuestionLength3D - cmp ecx,240 - jae fortune_char_done - mov fortuneQuestion3D[ecx],al - inc ecx - mov fortuneQuestionLength3D,ecx - mov fortuneQuestion3D[ecx],0 - ret -fortune_tarot_backspace: - mov ecx,fortuneQuestionLength3D - test ecx,ecx - jz fortune_char_done - dec ecx - mov fortuneQuestionLength3D,ecx - mov fortuneQuestion3D[ecx],0 - ret -@@: - - ; --- Mode 2: STUDENT --- - cmp fortuneSubMode3D,FORTUNE_SUBMODE_STUDENT - jne fortune_char_done - cmp studentDone3D,1 - je fortune_stud_done_keys - - ; Tab switches fields - mov eax,character - cmp eax,9 - jne @F - inc studentField3D - cmp studentField3D,3 - jb fortune_char_done - mov studentField3D,0 - ret -@@: - cmp studentField3D,0 - je fortune_stud_edit_name - cmp studentField3D,1 - je fortune_stud_edit_year - cmp studentField3D,2 - je fortune_stud_edit_choice - ret - -fortune_stud_edit_name: - mov eax,character - cmp eax,8 - je fortune_stud_name_bs - cmp eax,32 - jb fortune_char_done - cmp eax,255 - ja fortune_char_done - cmp eax,127 - je fortune_char_done - mov ecx,studentNameLen3D - cmp ecx,50 - jae fortune_char_done - mov studentName3D[ecx],al - inc ecx - mov studentNameLen3D,ecx - mov studentName3D[ecx],0 - ret -fortune_stud_name_bs: - mov ecx,studentNameLen3D - test ecx,ecx - jz fortune_char_done - dec ecx - mov studentNameLen3D,ecx - mov studentName3D[ecx],0 - ret - -fortune_stud_edit_year: - mov eax,character - cmp eax,8 - je fortune_stud_year_bs - cmp eax,'0' - jb fortune_char_done - cmp eax,'9' - ja fortune_char_done - mov ecx,studentYearLen3D - cmp ecx,6 - jae fortune_char_done - mov studentYear3D[ecx],al - inc ecx - mov studentYearLen3D,ecx - mov studentYear3D[ecx],0 - ret -fortune_stud_year_bs: - mov ecx,studentYearLen3D - test ecx,ecx - jz fortune_char_done - dec ecx - mov studentYearLen3D,ecx - mov studentYear3D[ecx],0 - ret - -fortune_stud_edit_choice: - mov eax,character - cmp eax,'1' - jb fortune_char_done - cmp eax,'5' - ja fortune_char_done - sub eax,'1' - mov studentChoice3D,eax - ret - -fortune_stud_done_keys: - mov eax,character - cmp eax,'1' - je fortune_stud_reset_next - cmp eax,1Bh - je fortune_stud_to_menu - ret -fortune_stud_reset_next: - mov studentDone3D,0 - mov fortuneCardsDrawn3D,0 - ret -fortune_stud_to_menu: - mov fortuneSubMode3D,FORTUNE_SUBMODE_MENU - ret - -fortune_char_done: - ret -FortuneChar3D ENDP - -SubmitFortuneQuestion3D PROC uses ebx esi - ; Draw 3 unique cards - xor esi,esi -fortune_draw_loop: - invoke RandomRange3D,22 - mov ebx,eax - xor ecx,ecx -fortune_check_unique: - cmp ecx,esi - jae fortune_apply_orientation - mov eax,fortuneDrawCards3D[ecx*4] - xor edx,edx - push ecx - mov ecx,22 - div ecx - pop ecx - cmp ebx,edx - je fortune_draw_loop - inc ecx - jmp fortune_check_unique -fortune_apply_orientation: - invoke RandomRange3D,2 - imul eax,22 - add eax,ebx - mov fortuneDrawCards3D[esi*4],eax - mov fortuneRequest3D[esi*4+8],eax - inc esi - cmp esi,3 - jb fortune_draw_loop - - mov fortuneCardsDrawn3D,1 - - ; Native Mind generates instant reading - call GenerateTarotReadingMind3D - mov fortuneWorkerState3D,2 - mov fortunePage3D,0 - mov fortuneShort3D,0 - - ; Optional: notify external child worker if running - cmp fortuneWrite3D,0 - je fortune_submit_done - mov eax,fortuneQuestionLength3D - test eax,eax - jz fortune_submit_done - mov fortuneRequest3D[4],eax - invoke WriteFile,fortuneWrite3D,OFFSET fortuneRequest3D,20,OFFSET fortuneIo3D,0 - invoke WriteFile,fortuneWrite3D,OFFSET fortuneQuestion3D,fortuneQuestionLength3D,OFFSET fortuneIo3D,0 -fortune_submit_done: - ret -SubmitFortuneQuestion3D ENDP - -SubmitStudentGrade3D PROC uses ebx esi - ; Draw 3 cards for student - xor esi,esi -fortune_stud_draw_loop: - invoke RandomRange3D,22 - mov ebx,eax - xor ecx,ecx -fortune_stud_check_unique: - cmp ecx,esi - jae fortune_stud_apply_orientation - mov eax,fortuneDrawCards3D[ecx*4] - xor edx,edx - push ecx - mov ecx,22 - div ecx - pop ecx - cmp ebx,edx - je fortune_stud_draw_loop - inc ecx - jmp fortune_stud_check_unique -fortune_stud_apply_orientation: - invoke RandomRange3D,2 - imul eax,22 - add eax,ebx - mov fortuneDrawCards3D[esi*4],eax - inc esi - cmp esi,3 - jb fortune_stud_draw_loop - - mov fortuneCardsDrawn3D,1 - call GenerateStudentGradeMind3D - mov studentDone3D,1 - mov fortuneWorkerState3D,2 - mov fortunePage3D,0 - ret -SubmitStudentGrade3D ENDP - -HandleFortuneDialog3D PROC - call PollFortuneWorker3D - - ; Check Escape (1Bh) - invoke Pressed3D,1Bh - test eax,eax - jz @F - cmp fortuneSubMode3D,FORTUNE_SUBMODE_MENU - je fortune_handle_exit - mov fortuneSubMode3D,FORTUNE_SUBMODE_MENU - ret -@@: - - ; Check Enter (0Dh) - invoke Pressed3D,0Dh - test eax,eax - jz fortune_handle_mouse - cmp fortuneSubMode3D,FORTUNE_SUBMODE_MENU - je fortune_handle_menu_enter - cmp fortuneSubMode3D,FORTUNE_SUBMODE_TAROT - je fortune_handle_tarot_enter - cmp fortuneSubMode3D,FORTUNE_SUBMODE_STUDENT - je fortune_handle_stud_enter - ret - -fortune_handle_menu_enter: - mov fortuneSubMode3D,FORTUNE_SUBMODE_TAROT - ret -fortune_handle_tarot_enter: - call SubmitFortuneQuestion3D - ret -fortune_handle_stud_enter: - cmp studentDone3D,1 - je fortune_stud_reset_enter - call SubmitStudentGrade3D - ret -fortune_stud_reset_enter: - mov studentDone3D,0 - mov fortuneCardsDrawn3D,0 - ret - -fortune_handle_mouse: - cmp mouseClick3D,0 - je fortune_handle_done - mov mouseClick3D,0 - - ; --- Mode 0: MENU CLICKS --- - cmp fortuneSubMode3D,FORTUNE_SUBMODE_MENU - jne @F - ; Option 1: Tarot (Y: 96..124, X: 20..300) - cmp mouseY3D,96 - jb fortune_handle_menu_chk2 - cmp mouseY3D,124 - ja fortune_handle_menu_chk2 - cmp mouseX3D,20 - jb fortune_handle_done - cmp mouseX3D,300 - ja fortune_handle_done - mov fortuneSubMode3D,FORTUNE_SUBMODE_TAROT - ret -fortune_handle_menu_chk2: - ; Option 2: Student (Y: 130..158, X: 20..300) - cmp mouseY3D,130 - jb fortune_handle_menu_chk0 - cmp mouseY3D,158 - ja fortune_handle_menu_chk0 - cmp mouseX3D,20 - jb fortune_handle_done - cmp mouseX3D,300 - ja fortune_handle_done - mov fortuneSubMode3D,FORTUNE_SUBMODE_STUDENT - ret -fortune_handle_menu_chk0: - ; Option 0 / Exit (Y: 164..192, X: 20..300) - cmp mouseY3D,164 - jb fortune_handle_menu_bottom - cmp mouseY3D,192 - ja fortune_handle_menu_bottom - cmp mouseX3D,20 - jb fortune_handle_done - cmp mouseX3D,300 - ja fortune_handle_done - jmp fortune_handle_exit -fortune_handle_menu_bottom: - ; Bottom Exit button - cmp mouseY3D,219 - jb fortune_handle_done - cmp mouseY3D,235 - jae fortune_handle_done - cmp mouseX3D,240 - jae fortune_handle_exit - ret -@@: - - ; --- Mode 1: TAROT CLICKS --- - cmp fortuneSubMode3D,FORTUNE_SUBMODE_TAROT - jne @F - cmp mouseY3D,219 - jb fortune_handle_done - cmp mouseY3D,235 - jae fortune_handle_done - ; [ÑÏÐÎÑÈÒÜ] at (8..86) - cmp mouseX3D,8 - jb fortune_handle_done - cmp mouseX3D,86 - jb fortune_handle_tarot_enter - ; [ÍÎÂÛÉ] at (90..180) - cmp mouseX3D,90 - jb fortune_handle_done - cmp mouseX3D,180 - jb fortune_tarot_reset - ; [ÌÅÍÞ] at (186..236) - cmp mouseX3D,186 - jb fortune_handle_done - cmp mouseX3D,236 - jb fortune_to_menu - ; [ÂÛÕÎÄ] at (242..310) - cmp mouseX3D,242 - jae fortune_handle_exit - ret -fortune_tarot_reset: - mov fortuneCardsDrawn3D,0 - mov fortuneQuestionLength3D,0 - mov fortuneQuestion3D[0],0 - mov fortuneAnswer3D[0],0 - ret -fortune_to_menu: - mov fortuneSubMode3D,FORTUNE_SUBMODE_MENU - ret -@@: - - ; --- Mode 2: STUDENT CLICKS --- - cmp fortuneSubMode3D,FORTUNE_SUBMODE_STUDENT - jne fortune_handle_done - cmp studentDone3D,1 - je fortune_stud_done_clicks - ; Check field clicks - ; Name box (Y: 44..66, X: 90..290) - cmp mouseY3D,44 - jb @F - cmp mouseY3D,66 - ja @F - cmp mouseX3D,90 - jb @F - cmp mouseX3D,290 - ja @F - mov studentField3D,0 - ret -@@: - ; Year box (Y: 74..96, X: 90..200) - cmp mouseY3D,74 - jb @F - cmp mouseY3D,96 - ja @F - cmp mouseX3D,90 - jb @F - cmp mouseX3D,200 - ja @F - mov studentField3D,1 - ret -@@: - ; Choices (Y: 104..128, X: 10..300) - cmp mouseY3D,104 - jb @F - cmp mouseY3D,128 - ja @F - mov studentField3D,2 - ; Click segments for 5 choices: ~58 px each - mov eax,mouseX3D - sub eax,10 - cmp eax,290 - jae @F - xor edx,edx - mov ecx,58 - div ecx - cmp eax,5 - jae @F - mov studentChoice3D,eax - ret -@@: - ; Bottom buttons (Y: 219..235) - cmp mouseY3D,219 - jb fortune_handle_done - cmp mouseY3D,235 - jae fortune_handle_done - ; [ÐÀÑÑÓÄÈÒÜ] at (8..104) - cmp mouseX3D,8 - jb fortune_handle_done - cmp mouseX3D,104 - jb fortune_handle_stud_enter - ; [ÌÅÍÞ] at (110..170) - cmp mouseX3D,110 - jb fortune_handle_done - cmp mouseX3D,170 - jb fortune_to_menu - ; [ÂÛÕÎÄ] at (242..310) - cmp mouseX3D,242 - jae fortune_handle_exit - ret - -fortune_stud_done_clicks: - cmp mouseY3D,219 - jb fortune_handle_done - cmp mouseY3D,235 - jae fortune_handle_done - ; [ÑËÅÄÓÞÙÈÉ ÑÒÓÄÅÍÒ] at (8..150) - cmp mouseX3D,8 - jb fortune_handle_done - cmp mouseX3D,150 - jb fortune_stud_reset_enter - ; [ÌÅÍÞ] at (154..214) - cmp mouseX3D,154 - jb fortune_handle_done - cmp mouseX3D,214 - jb fortune_to_menu - ; [ÂÛÕÎÄ] at (242..310) - cmp mouseX3D,242 - jae fortune_handle_exit - ret - -fortune_handle_exit: - call StopFortuneWorker3D - mov engineMode,MODE_WALK - call ClearKeys3D -fortune_handle_done: - ret -HandleFortuneDialog3D ENDP - -; Word wrapping at 72 native pixels-wide glyph columns, with bounded paging -FortuneWrap3D PROC uses ebx esi edi source:DWORD,top:DWORD,lines:DWORD,skip:DWORD,ink:DWORD - local count:DWORD,lastSpace:DWORD,lineNumber:DWORD - mov esi,source - mov lineNumber,0 -fortune_wrap_line: - cmp BYTE PTR [esi],0 - je fortune_wrap_end - xor ebx,ebx - mov lastSpace,0 -fortune_wrap_measure: - mov al,[esi+ebx] - test al,al - jz fortune_wrap_measured - cmp al,10 - je fortune_wrap_measured - cmp al,32 - jne @F - mov lastSpace,ebx -@@: - inc ebx - cmp ebx,72 - jb fortune_wrap_measure - cmp BYTE PTR [esi+ebx],0 - je fortune_wrap_measured - cmp lastSpace,0 - je fortune_wrap_measured - mov ebx,lastSpace -fortune_wrap_measured: - mov count,ebx - mov eax,lineNumber - cmp eax,skip - jb fortune_wrap_advance - sub eax,skip - cmp eax,lines - jae fortune_wrap_more - imul eax,11 - add eax,top - push eax - lea edi,fortuneLine3D - mov ecx,ebx - push esi - rep movsb - mov BYTE PTR [edi],0 - pop esi - pop eax - mov fortuneCaretY3D,eax - mov edx,count - imul edx,8 - add edx,24 - mov fortuneCaretX3D,edx - invoke DrawBuckshotText3D,24,eax,OFFSET fortuneLine3D,ink -fortune_wrap_advance: - add esi,count - cmp BYTE PTR [esi],10 - je fortune_wrap_skip_separator - cmp BYTE PTR [esi],32 - jne fortune_wrap_next -fortune_wrap_skip_separator: - inc esi -fortune_wrap_next: - inc lineNumber - jmp fortune_wrap_line -fortune_wrap_more: - mov eax,1 - ret -fortune_wrap_end: - xor eax,eax - ret -FortuneWrap3D ENDP - -; Draw a stylized visual Tarot Card box on screen -DrawFortuneCardBox3D PROC uses ebx esi edi cxVal:DWORD,cyVal:DWORD,cwVal:DWORD,chVal:DWORD,cardIdx:DWORD,roleStr:DWORD - local cNum:DWORD,isRev:DWORD,bgCol:DWORD,brdCol:DWORD - ; Split card number & orientation - mov eax,cardIdx - xor edx,edx - mov ecx,22 - div ecx - mov cNum,edx - cmp cardIdx,22 - setae al - movzx eax,al - mov isRev,eax - - ; Palette: upright=mystic teal/gold, reversed=dark purple/amber - cmp isRev,0 - jne fortune_card_rev_pal - mov bgCol,000F2236h - mov brdCol,004CA0C8h - jmp fortune_card_draw_bg -fortune_card_rev_pal: - mov bgCol,002A1226h - mov brdCol,00C86488h - -fortune_card_draw_bg: - invoke DrawBoxBorder,cxVal,cyVal,cwVal,chVal,bgCol,brdCol,1 - - ; Coordinates in 640x480 space - mov eax,cxVal - shl eax,1 - add eax,8 ; X offset - mov ebx,cyVal - shl ebx,1 - add ebx,6 ; Y offset - - ; Role line (e.g. "1. ÑÈÒÓÀÖÈß" or "1. ÊÀÐÌÀ Ó×ÅÁÛ") - invoke DrawBuckshotText3D,eax,ebx,roleStr,00FFD700h - - ; Card name line (e.g. "XVI Áàøíÿ") - add ebx,14 - mov edx,cNum - mov edx,cardNamesTable[edx*4] - invoke DrawBuckshotText3D,eax,ebx,edx,00FFFFFFh - - ; Orientation indicator - add ebx,14 - cmp isRev,0 - jne @F - invoke DrawBuckshotText3D,eax,ebx,OFFSET strCardUpright,0088FFAAh - ret -@@: - invoke DrawBuckshotText3D,eax,ebx,OFFSET strCardReversed,00FF88AAh - ret -DrawFortuneCardBox3D ENDP - -RenderFortuneDialog3D PROC uses esi edi - lea esi,fortuneBackground3D - lea edi,screenBuffer - mov ecx,SCREEN_SIZE - rep movsd - - ; Outer Border - invoke DrawBoxBorder,6,5,308,230,000A1726h,004C8CAAh,1 - - ; --- Submode 0: SELECTION MENU --- - cmp fortuneSubMode3D,FORTUNE_SUBMODE_MENU - jne @F - invoke DrawBuckshotText3D,24,18,OFFSET fortuneMenuTitle3D,00D0EEFFh - invoke DrawBuckshotText3D,24,38,OFFSET fortuneDisclaimer3D,0087A8BDh - invoke DrawBoxBorder,9,30,302,175,000C1C30h,002C4D68h,1 - - invoke DrawBuckshotText3D,24,76,OFFSET fortuneMenuGreeting1_3D,00BEDCEEh - invoke DrawBuckshotText3D,24,96,OFFSET fortuneMenuGreeting2_3D,00BEDCEEh - - ; Option 1: Tarot - invoke DrawBoxBorder,20,96,280,26,00132337h,00416A87h,1 - invoke DrawBuckshotText3D,48,200,OFFSET fortuneMenuOpt1_3D,00D0EEFFh - - ; Option 2: Student - invoke DrawBoxBorder,20,130,280,26,00132337h,00416A87h,1 - invoke DrawBuckshotText3D,48,268,OFFSET fortuneMenuOpt2_3D,00D0EEFFh - - ; Option 0: Exit - invoke DrawBoxBorder,20,164,280,26,00132337h,00416A87h,1 - invoke DrawBuckshotText3D,48,336,OFFSET fortuneMenuOpt0_3D,00D0EEFFh - - ; Bottom Exit button - invoke DrawBoxBorder,240,219,70,15,001D3A50h,0076B0CDh,1 - invoke DrawBuckshotText3D,518,445,OFFSET fortuneExit3D,00D0EEFFh - ret -@@: - - ; --- Submode 1: TAROT DIVINATION --- - cmp fortuneSubMode3D,FORTUNE_SUBMODE_TAROT - jne @F - invoke DrawBuckshotText3D,24,18,OFFSET fortuneTitle3D,00D0EEFFh - invoke DrawBuckshotText3D,24,38,OFFSET fortuneDisclaimer3D,0087A8BDh - - cmp fortuneCardsDrawn3D,1 - je fortune_render_tarot_drawn - - ; No cards drawn yet: intro prompt - invoke DrawBoxBorder,9,30,302,145,000C1C30h,002C4D68h,1 - invoke DrawBuckshotText3D,24,76,OFFSET fortuneReady3D,00BEDCEEh - invoke DrawBuckshotText3D,24,106,OFFSET fortuneMenuOpt1_3D,009CCEE8h - invoke DrawBuckshotText3D,24,136,OFFSET strAskPrompt,00D0EEFFh - jmp fortune_render_tarot_input - -fortune_render_tarot_drawn: - ; Draw 3 Tarot Cards at top (Y=28, H=44) - invoke DrawFortuneCardBox3D,12,28,94,44,fortuneDrawCards3D[0],OFFSET strTarotRole1 - invoke DrawFortuneCardBox3D,113,28,94,44,fortuneDrawCards3D[4],OFFSET strTarotRole2 - invoke DrawFortuneCardBox3D,214,28,94,44,fortuneDrawCards3D[8],OFFSET strTarotRole3 - - ; Reading text box below cards (Y=75, H=100) - invoke DrawBoxBorder,9,75,302,100,000C1C30h,002C4D68h,1 - invoke FortuneWrap3D,OFFSET fortuneAnswer3D,160,17,fortunePage3D,00DCEBF4h - mov fortuneMore3D,eax - -fortune_render_tarot_input: - ; Question input box (Y=179, H=35) - invoke DrawBoxBorder,9,179,302,35,00132337h,00416A87h,1 - mov fortuneCaretX3D,24 - mov fortuneCaretY3D,366 - invoke FortuneWrap3D,OFFSET fortuneQuestion3D,366,5,0,00FFFFFFh - - ; Caret animation - test animationClock3D,200h - jnz @F - invoke DrawBuckshotText3D,fortuneCaretX3D,fortuneCaretY3D,OFFSET fortuneCaret3D,00FFFFFFh -@@: - ; Buttons (Y=219) - invoke DrawBoxBorder,8,219,76,15,001D3A50h,0076B0CDh,1 - invoke DrawBuckshotText3D,20,445,OFFSET fortuneAsk3D,00D0EEFFh - - invoke DrawBoxBorder,88,219,90,15,001D3A50h,0076B0CDh,1 - invoke DrawBuckshotText3D,182,445,OFFSET strBtnNextStudent,00D0EEFFh - - invoke DrawBoxBorder,182,219,54,15,001D3A50h,0076B0CDh,1 - invoke DrawBuckshotText3D,374,445,OFFSET strBtnMenu,00D0EEFFh - - invoke DrawBoxBorder,240,219,70,15,001D3A50h,0076B0CDh,1 - invoke DrawBuckshotText3D,518,445,OFFSET fortuneExit3D,00D0EEFFh - ret -@@: - - ; --- Submode 2: STUDENT GRADE DIVINATION --- - cmp fortuneSubMode3D,FORTUNE_SUBMODE_STUDENT - jne fortune_render_done - invoke DrawBuckshotText3D,24,18,OFFSET strStudentTitle,00FFD700h - invoke DrawBuckshotText3D,24,38,OFFSET fortuneDisclaimer3D,0087A8BDh - - cmp studentDone3D,1 - je fortune_render_stud_verdict - - ; Input Form Box (Y=30, H=175) - invoke DrawBoxBorder,9,30,302,175,000C1C30h,002C4D68h,1 - - ; Prompt 1: Student Name - invoke DrawBuckshotText3D,24,76,OFFSET strStudentPromptName,00BEDCEEh - cmp studentField3D,0 - jne @F - invoke DrawBoxBorder,85,34,215,16,001A364Eh,0066CCFFh,1 - jmp fortune_render_stud_name_text -@@: - invoke DrawBoxBorder,85,34,215,16,00132337h,00416A87h,1 -fortune_render_stud_name_text: - invoke DrawBuckshotText3D,176,76,OFFSET studentName3D,00FFFFFFh - cmp studentField3D,0 - jne @F - test animationClock3D,200h - jnz @F - mov eax,studentNameLen3D - shl eax,3 - add eax,176 - invoke DrawBuckshotText3D,eax,76,OFFSET fortuneCaret3D,00FFFFFFh -@@: - - ; Prompt 2: Birth Year - invoke DrawBuckshotText3D,24,116,OFFSET strStudentPromptYear,00BEDCEEh - cmp studentField3D,1 - jne @F - invoke DrawBoxBorder,85,54,100,16,001A364Eh,0066CCFFh,1 - jmp fortune_render_stud_year_text -@@: - invoke DrawBoxBorder,85,54,100,16,00132337h,00416A87h,1 -fortune_render_stud_year_text: - invoke DrawBuckshotText3D,176,116,OFFSET studentYear3D,00FFFFFFh - cmp studentField3D,1 - jne @F - test animationClock3D,200h - jnz @F - mov eax,studentYearLen3D - shl eax,3 - add eax,176 - invoke DrawBuckshotText3D,eax,116,OFFSET fortuneCaret3D,00FFFFFFh -@@: - - ; Prompt 3: Grade Choices - invoke DrawBuckshotText3D,24,156,OFFSET strStudentPromptChoice,00BEDCEEh - ; Highlight chosen grade - mov eax,studentChoice3D - imul eax,116 - add eax,48 - cmp studentField3D,2 - jne @F - invoke DrawBuckshotText3D,eax,176,OFFSET strCardUpright,00FFD700h -@@: - - ; Action instructions - invoke DrawBuckshotText3D,24,360,OFFSET strStudentAction,0087A8BDh - - ; Buttons (Y=219) - invoke DrawBoxBorder,8,219,96,15,001D3A50h,0076B0CDh,1 - invoke DrawBuckshotText3D,20,445,OFFSET strStudentTitle,00D0EEFFh - - invoke DrawBoxBorder,110,219,60,15,001D3A50h,0076B0CDh,1 - invoke DrawBuckshotText3D,230,445,OFFSET strBtnMenu,00D0EEFFh - - invoke DrawBoxBorder,240,219,70,15,001D3A50h,0076B0CDh,1 - invoke DrawBuckshotText3D,518,445,OFFSET fortuneExit3D,00D0EEFFh - ret - -fortune_render_stud_verdict: - ; Top 3 Destiny Cards (Y=28, H=44) - invoke DrawFortuneCardBox3D,12,28,94,44,fortuneDrawCards3D[0],OFFSET strStudRole1 - invoke DrawFortuneCardBox3D,113,28,94,44,fortuneDrawCards3D[4],OFFSET strStudRole2 - invoke DrawFortuneCardBox3D,214,28,94,44,fortuneDrawCards3D[8],OFFSET strStudRole3 - - ; Divination Verdict Text Box (Y=75, H=138) - invoke DrawBoxBorder,9,75,302,138,000C1C30h,002C4D68h,1 - invoke FortuneWrap3D,OFFSET fortuneAnswer3D,160,25,fortunePage3D,00DCEBF4h - mov fortuneMore3D,eax - - ; Bottom Buttons - invoke DrawBoxBorder,8,219,140,15,001D3A50h,0076B0CDh,1 - invoke DrawBuckshotText3D,20,445,OFFSET strBtnNextStudent,00D0EEFFh - - invoke DrawBoxBorder,154,219,60,15,001D3A50h,0076B0CDh,1 - invoke DrawBuckshotText3D,320,445,OFFSET strBtnMenu,00D0EEFFh - - invoke DrawBoxBorder,240,219,70,15,001D3A50h,0076B0CDh,1 - invoke DrawBuckshotText3D,518,445,OFFSET fortuneExit3D,00D0EEFFh - ret - -fortune_render_done: - ret -RenderFortuneDialog3D ENDP +; Seated fortune session. All timing, input, dealing and rendering are native MASM. +BeginFortuneDialog3D PROC uses ebx esi edi + call StopFortuneWorker3D + lea esi,screenBuffer + lea edi,fortuneBackground3D + mov ecx,SCREEN_SIZE + rep movsd + xor ebx,ebx + lea esi,fortuneSessionPacks3D +fortune_session_load: + invoke LoadAnimPack3D,esi,fortuneSessionPaths3D[ebx*4] + add esi,SIZEOF AnimPack3D + inc ebx + cmp ebx,7 + jb fortune_session_load + mov engineMode,MODE_FORTUNE + mov paused3D,0 + mov fortuneSubMode3D,0 + mov fortunePhase3D,0 + mov fortunePhaseTime3D,0 + mov fortuneDealt3D,0 + mov fortuneRevealed3D,0 + mov fortuneCardsDrawn3D,0 + mov fortuneQuestionLength3D,0 + mov fortuneQuestion3D,0 + mov fortuneWorkerState3D,0 + mov eax,animationClock3D + mov fortuneSessionClock3D,eax + invoke SetFortuneSpeech3D,OFFSET fortuneWelcome3D + call ClearKeys3D + mov mouseClick3D,0 + ret +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 +fortune_session_free: + invoke FreeAnimPack3D,esi + add esi,SIZEOF AnimPack3D + dec ebx + jnz fortune_session_free + mov fortuneWorkerState3D,0 + ret +StopFortuneWorker3D ENDP + +PollFortuneWorker3D PROC + ret +PollFortuneWorker3D ENDP + +SetFortuneSpeech3D PROC uses esi edi text:DWORD + mov esi,text + lea edi,fortuneSpeech3D + xor ecx,ecx +fortune_speech_copy: + mov al,[esi+ecx] + mov [edi+ecx],al + test al,al + jz fortune_speech_copied + inc ecx + cmp ecx,255 + jb fortune_speech_copy + mov BYTE PTR [edi+255],0 +fortune_speech_copied: + mov fortuneSpeechLength3D,ecx + mov fortuneSpeechTime3D,0 + mov fortuneSpeechVisible3D,0 + ret +SetFortuneSpeech3D ENDP + +FortuneCardSpeech3D PROC uses ebx esi edi cardSlot:DWORD + mov eax,cardSlot + mov ebx,fortuneDrawCards3D[eax*4] + mov eax,ebx + xor edx,edx + mov ecx,22 + div ecx + mov esi,fortuneCardNames3D[edx*4] + lea edi,fortuneSpeech3D +fortune_card_name_copy: + lodsb + test al,al + jz fortune_card_position + stosb + jmp fortune_card_name_copy +fortune_card_position: + mov al,10 + stosb + mov esi,OFFSET fortuneUpright3D + cmp ebx,22 + jb @F + mov esi,OFFSET fortuneReversed3D +@@: + lodsb + test al,al + jz fortune_card_placeholder + stosb + jmp @B +fortune_card_placeholder: + mov al,10 + stosb + mov esi,OFFSET fortuneUnavailable3D +fortune_card_placeholder_copy: + lodsb + stosb + test al,al + jnz fortune_card_placeholder_copy + sub edi,OFFSET fortuneSpeech3D+1 + mov fortuneSpeechLength3D,edi + mov fortuneSpeechTime3D,0 + mov fortuneSpeechVisible3D,0 + ret +FortuneCardSpeech3D ENDP + +FortuneChar3D PROC character:DWORD + cmp engineMode,MODE_FORTUNE + jne fortune_char_done + cmp fortuneSubMode3D,FORTUNE_SUBMODE_MENU + jne fortune_char_question + cmp character,'1' + je fortune_char_tarot + cmp character,'2' + je fortune_char_orb + ret +fortune_char_tarot: + mov fortuneSubMode3D,FORTUNE_SUBMODE_TAROT + invoke SetFortuneSpeech3D,OFFSET fortuneReady3D + ret +fortune_char_orb: + mov fortuneSubMode3D,FORTUNE_SUBMODE_ORB + mov fortunePhase3D,1 + mov fortunePhaseTime3D,0 + invoke SetFortuneSpeech3D,OFFSET fortuneOrbStatus3D + ret +fortune_char_question: + cmp fortuneSubMode3D,FORTUNE_SUBMODE_TAROT + jne fortune_char_done + cmp fortunePhase3D,0 + jne fortune_char_done + mov eax,character + cmp eax,8 + je fortune_char_backspace + cmp eax,32 + jb fortune_char_done + cmp eax,255 + ja fortune_char_done + cmp eax,127 + je fortune_char_done + mov ecx,fortuneQuestionLength3D + cmp ecx,240 + jae fortune_char_done + mov fortuneQuestion3D[ecx],al + inc ecx + mov fortuneQuestionLength3D,ecx + mov fortuneQuestion3D[ecx],0 + ret +fortune_char_backspace: + mov ecx,fortuneQuestionLength3D + test ecx,ecx + jz fortune_char_done + dec ecx + mov fortuneQuestionLength3D,ecx + mov fortuneQuestion3D[ecx],0 +fortune_char_done: + ret +FortuneChar3D ENDP + +SubmitFortuneQuestion3D PROC uses ebx esi + cmp fortuneSubMode3D,FORTUNE_SUBMODE_TAROT + jne fortune_submit_done + cmp fortunePhase3D,0 + jne fortune_submit_done + cmp fortuneQuestionLength3D,0 + je fortune_submit_done + xor esi,esi +fortune_draw_loop: + invoke RandomRange3D,22 + mov ebx,eax + xor ecx,ecx +fortune_check_unique: + cmp ecx,esi + jae fortune_apply_orientation + mov eax,fortuneDrawCards3D[ecx*4] + xor edx,edx + push ecx + mov ecx,22 + div ecx + pop ecx + cmp ebx,edx + je fortune_draw_loop + inc ecx + jmp fortune_check_unique +fortune_apply_orientation: + invoke RandomRange3D,2 + imul eax,22 + add eax,ebx + mov fortuneDrawCards3D[esi*4],eax + inc esi + cmp esi,3 + jb fortune_draw_loop + mov fortunePhase3D,1 + mov fortunePhaseTime3D,0 + mov fortuneDealt3D,0 + mov fortuneRevealed3D,0 + mov fortuneCardsDrawn3D,1 + mov eax,animationClock3D + mov fortuneSessionClock3D,eax + invoke SetFortuneSpeech3D,OFFSET fortuneShuffle3D +fortune_submit_done: + ret +SubmitFortuneQuestion3D ENDP + +; Elapsed clock, not render count: repeated renders cannot accelerate a session. +UpdateFortuneSession3D PROC uses ebx + mov eax,animationClock3D + mov edx,eax + sub eax,fortuneSessionClock3D + mov fortuneSessionClock3D,edx + cmp eax,100 + jbe @F + mov eax,100 +@@: + add fortuneSpeechTime3D,eax + add fortunePhaseTime3D,eax + cmp fortuneSubMode3D,FORTUNE_SUBMODE_TAROT + jne fortune_update_orb + mov ebx,fortunePhase3D + test ebx,ebx + jz fortune_update_done + cmp ebx,12 + jae fortune_update_done + mov eax,fortuneDurations3D[ebx*4] + cmp fortunePhaseTime3D,eax + jb fortune_update_done + inc ebx + mov fortunePhase3D,ebx + mov fortunePhaseTime3D,0 + cmp ebx,2 + jb fortune_update_done + cmp ebx,5 + ja fortune_update_reveal + mov eax,ebx + sub eax,2 + mov fortuneDealt3D,eax + invoke SetFortuneSpeech3D,OFFSET fortuneDeal3D + jmp fortune_update_done +fortune_update_reveal: + cmp ebx,6 + je fortune_explain_one + cmp ebx,8 + je fortune_explain_two + cmp ebx,10 + je fortune_explain_three + cmp ebx,11 + jne fortune_update_done + invoke SetFortuneSpeech3D,OFFSET fortuneSummary3D + jmp fortune_update_done +fortune_explain_one: + mov fortuneRevealed3D,1 + invoke FortuneCardSpeech3D,0 + jmp fortune_update_done +fortune_explain_two: + mov fortuneRevealed3D,2 + invoke FortuneCardSpeech3D,1 + jmp fortune_update_done +fortune_explain_three: + mov fortuneRevealed3D,3 + invoke FortuneCardSpeech3D,2 + jmp fortune_update_done +fortune_update_orb: + cmp fortuneSubMode3D,FORTUNE_SUBMODE_ORB + jne fortune_update_done + cmp fortunePhase3D,1 + jne fortune_orb_swirl + cmp fortunePhaseTime3D,1400 + jb fortune_update_done + mov fortunePhase3D,2 + mov fortunePhaseTime3D,0 + jmp fortune_update_done +fortune_orb_swirl: + cmp fortunePhase3D,2 + jne fortune_update_done + cmp fortunePhaseTime3D,5000 + jb fortune_update_done + mov fortunePhase3D,3 + mov fortunePhaseTime3D,0 + invoke SetFortuneSpeech3D,OFFSET fortuneOrbText3D +fortune_update_done: + ret +UpdateFortuneSession3D ENDP + +HandleFortuneDialog3D PROC + call UpdateFortuneSession3D + invoke Pressed3D,1Bh + test eax,eax + jnz fortune_handle_exit + invoke Pressed3D,0Dh + test eax,eax + jnz fortune_handle_action + cmp mouseClick3D,0 + je fortune_handle_done + mov mouseClick3D,0 + cmp mouseY3D,227 + jb fortune_handle_menu_click + cmp mouseY3D,239 + ja fortune_handle_done + cmp mouseX3D,240 + jae fortune_handle_exit + cmp fortuneSubMode3D,FORTUNE_SUBMODE_MENU + je fortune_handle_done + cmp mouseX3D,172 + jae fortune_handle_menu + cmp mouseX3D,88 + jae fortune_handle_reset + jmp fortune_handle_action +fortune_handle_menu_click: + cmp fortuneSubMode3D,FORTUNE_SUBMODE_MENU + jne fortune_handle_done + cmp mouseY3D,196 + jb fortune_handle_done + cmp mouseY3D,214 + ja fortune_handle_done + cmp mouseX3D,160 + jae fortune_handle_orb +fortune_handle_tarot: + invoke FortuneChar3D,'1' + ret +fortune_handle_orb: + invoke FortuneChar3D,'2' + ret +fortune_handle_action: + cmp fortuneSubMode3D,FORTUNE_SUBMODE_MENU + je fortune_handle_tarot + cmp fortuneSubMode3D,FORTUNE_SUBMODE_TAROT + jne fortune_handle_done + cmp fortunePhase3D,0 + je fortune_handle_submit + ; Continue only after this speech is fully visible. Never skip dealing/flips. + mov eax,fortuneSpeechLength3D + imul eax,28 + cmp fortuneSpeechTime3D,eax + jb fortune_handle_done + mov eax,fortunePhase3D + cmp eax,6 + je fortune_handle_advance + cmp eax,8 + je fortune_handle_advance + cmp eax,10 + je fortune_handle_advance + cmp eax,11 + jne fortune_handle_done +fortune_handle_advance: + mov eax,fortuneDurations3D[eax*4] + mov fortunePhaseTime3D,eax + ret +fortune_handle_submit: + call SubmitFortuneQuestion3D + ret +fortune_handle_menu: + mov fortuneSubMode3D,FORTUNE_SUBMODE_MENU + mov fortunePhase3D,0 + mov fortuneDealt3D,0 + mov fortuneRevealed3D,0 + invoke SetFortuneSpeech3D,OFFSET fortuneWelcome3D + ret +fortune_handle_reset: + cmp fortuneSubMode3D,FORTUNE_SUBMODE_ORB + jne @F + mov fortunePhase3D,1 + mov fortunePhaseTime3D,0 + invoke SetFortuneSpeech3D,OFFSET fortuneOrbStatus3D + ret +@@: + cmp fortunePhase3D,12 + jne fortune_handle_done + mov fortunePhase3D,0 + mov fortuneDealt3D,0 + mov fortuneRevealed3D,0 + mov fortuneQuestionLength3D,0 + mov fortuneQuestion3D,0 + invoke SetFortuneSpeech3D,OFFSET fortuneReady3D + ret +fortune_handle_exit: + call StopFortuneWorker3D + mov engineMode,MODE_WALK + call ClearKeys3D +fortune_handle_done: + ret +HandleFortuneDialog3D ENDP + +FortuneWrap3D PROC uses ebx esi edi source:DWORD,top:DWORD,lines:DWORD,skip:DWORD,ink:DWORD + local count:DWORD,lastSpace:DWORD,lineNumber:DWORD + mov esi,source + mov lineNumber,0 +fortune_wrap_line: + cmp BYTE PTR [esi],0 + je fortune_wrap_end + xor ebx,ebx + mov lastSpace,0 +fortune_wrap_measure: + mov al,[esi+ebx] + test al,al + jz fortune_wrap_measured + cmp al,10 + je fortune_wrap_measured + cmp al,32 + jne @F + mov lastSpace,ebx +@@: + inc ebx + cmp ebx,72 + jb fortune_wrap_measure + cmp BYTE PTR [esi+ebx],0 + je fortune_wrap_measured + cmp lastSpace,0 + je fortune_wrap_measured + mov ebx,lastSpace +fortune_wrap_measured: + mov count,ebx + mov eax,lineNumber + cmp eax,skip + jb fortune_wrap_advance + sub eax,skip + cmp eax,lines + jae fortune_wrap_more + imul eax,11 + add eax,top + push eax + lea edi,fortuneLine3D + mov ecx,ebx + push esi + rep movsb + mov BYTE PTR [edi],0 + pop esi + pop eax + mov fortuneCaretY3D,eax + mov edx,count + imul edx,8 + add edx,24 + mov fortuneCaretX3D,edx + invoke DrawBuckshotText3D,24,eax,OFFSET fortuneLine3D,ink +fortune_wrap_advance: + add esi,count + cmp BYTE PTR [esi],10 + je fortune_wrap_skip_separator + cmp BYTE PTR [esi],32 + jne fortune_wrap_next +fortune_wrap_skip_separator: + inc esi +fortune_wrap_next: + inc lineNumber + jmp fortune_wrap_line +fortune_wrap_more: + mov eax,1 + ret +fortune_wrap_end: + xor eax,eax + ret +FortuneWrap3D ENDP + +; The bubble grows with visible glyphs, rather than allocating a full-screen panel. +DrawFortuneSpeech3D PROC uses ebx esi edi + local column:DWORD,row:DWORD,maxColumn:DWORD,visible:DWORD,glyphX:DWORD,glyphY:DWORD + mov eax,fortuneSpeechTime3D + xor edx,edx + mov ecx,28 + div ecx + cmp eax,fortuneSpeechLength3D + jbe @F + mov eax,fortuneSpeechLength3D +@@: + mov visible,eax + mov fortuneSpeechVisible3D,eax + test eax,eax + jz fortune_bubble_done + mov column,0 + mov row,0 + mov maxColumn,0 + xor esi,esi +fortune_bubble_measure: + cmp esi,visible + jae fortune_bubble_box + cmp fortuneSpeech3D[esi],10 + je fortune_bubble_newline + inc column + mov eax,column + cmp eax,maxColumn + jbe @F + mov maxColumn,eax +@@: + cmp column,23 + jb fortune_bubble_measure_next +fortune_bubble_newline: + mov column,0 + inc row +fortune_bubble_measure_next: + inc esi + jmp fortune_bubble_measure +fortune_bubble_box: + mov eax,maxColumn + imul eax,8 + add eax,20 + mov fortuneSpeechWidth3D,eax + shr eax,1 + mov ebx,eax + mov eax,row + inc eax + imul eax,12 + add eax,20 + mov fortuneSpeechHeight3D,eax + shr eax,1 + invoke DrawBoxBorder,208,30,ebx,eax,000A1829h,006E9DB8h,1 + mov column,0 + mov row,0 + xor esi,esi +fortune_bubble_glyph: + cmp esi,visible + jae fortune_bubble_done + mov al,fortuneSpeech3D[esi] + cmp al,10 + je fortune_bubble_glyph_newline + mov fortuneGlyph3D,al + mov eax,column + imul eax,8 + add eax,426 + mov glyphX,eax + mov ebx,row + imul ebx,12 + add ebx,70 + mov eax,esi + inc eax + imul eax,28 + mov edx,fortuneSpeechTime3D + sub edx,eax + cmp edx,84 + jae @F + mov eax,84 + sub eax,edx + shr eax,5 + sub ebx,eax +@@: + mov glyphY,ebx + invoke DrawBuckshotText3D,glyphX,ebx,OFFSET fortuneGlyph3D,00D2E8F2h + inc column + cmp column,23 + jb fortune_bubble_glyph_next +fortune_bubble_glyph_newline: + mov column,0 + inc row +fortune_bubble_glyph_next: + inc esi + jmp fortune_bubble_glyph +fortune_bubble_done: + ret +DrawFortuneSpeech3D ENDP + +DrawFortuneSessionCard3D PROC uses ebx esi edi slot:DWORD + local px:DWORD,py:DWORD,cw:DWORD,cardHeight:DWORD,face:DWORD,amount:DWORD + mov esi,slot + mov eax,esi + imul eax,100 + add eax,198 + mov px,eax + mov py,302 + mov cw,72 + mov cardHeight,96 + mov face,44 + cmp esi,fortuneRevealed3D + jae @F + mov eax,fortuneDrawCards3D[esi*4] + mov face,eax +@@: + cmp esi,fortuneDealt3D + jb fortune_card_flip_check + mov eax,fortunePhase3D + sub eax,2 + cmp eax,esi + jne fortune_card_done + ; Release at 40%, then carry the card from the hand to its table slot. + mov ebx,fortunePhaseTime3D + sub ebx,400 + js fortune_card_done + cmp ebx,600 + jbe @F + mov ebx,600 +@@: + mov eax,px + sub eax,220 + imul eax,ebx + cdq + mov ecx,600 + idiv ecx + add eax,220 + mov px,eax + mov eax,ebx + imul eax,40 + xor edx,edx + div ecx + add eax,262 + mov py,eax + jmp fortune_card_draw +fortune_card_flip_check: + mov eax,fortunePhase3D + sub eax,5 + js fortune_card_draw + test eax,1 + jnz fortune_card_draw + shr eax,1 + cmp eax,esi + jne fortune_card_draw + cmp fortunePhase3D,9 + ja fortune_card_draw + mov ebx,fortunePhaseTime3D + cmp ebx,400 + jb fortune_card_flip_back + mov eax,fortuneDrawCards3D[esi*4] + mov face,eax + sub ebx,400 + jmp fortune_card_flip_width +fortune_card_flip_back: + mov eax,400 + sub eax,ebx + mov ebx,eax +fortune_card_flip_width: + imul ebx,72 + mov eax,ebx + xor edx,edx + mov ecx,400 + div ecx + cmp eax,2 + jae @F + mov eax,2 +@@: + mov cw,eax + mov ebx,72 + sub ebx,eax + shr ebx,1 + add px,ebx +fortune_card_draw: + invoke DrawAnimSprite3D,OFFSET fortuneSessionCards3D,face,px,py,cw,cardHeight +fortune_card_done: + ret +DrawFortuneSessionCard3D ENDP + +RenderFortuneDialog3D PROC uses ebx esi edi + lea esi,fortuneBackground3D + lea edi,screenBuffer + mov ecx,SCREEN_SIZE + rep movsd + invoke DrawAnimSprite3D,OFFSET fortuneSessionRoom3D,0,0,0,640,480 + cmp fortuneSessionRoom3D.pixels,0 + jne @F + invoke DrawBoxBorder,4,4,312,36,000A1829h,006E9DB8h,1 + invoke DrawBuckshotText3D,24,24,OFFSET fortuneMissing3D,00FFFFFFh +@@: + mov ebx,OFFSET fortuneSessionShuffle3D + xor esi,esi + cmp fortuneSubMode3D,FORTUNE_SUBMODE_ORB + je fortune_render_orb_hands + mov eax,fortunePhase3D + cmp eax,1 + jne fortune_render_hand_deal + mov eax,fortunePhaseTime3D + xor edx,edx + mov ecx,25 + div ecx + and eax,63 + mov esi,eax + jmp fortune_render_hands +fortune_render_hand_deal: + cmp eax,2 + jb fortune_render_hands + cmp eax,4 + ja fortune_render_hand_gesture + mov ebx,OFFSET fortuneSessionDeal3D + mov eax,fortunePhaseTime3D + xor edx,edx + mov ecx,32 + div ecx + cmp eax,31 + jbe @F + mov eax,31 +@@: + mov esi,eax + jmp fortune_render_hands +fortune_render_hand_gesture: + mov ebx,OFFSET fortuneSessionGesture3D + mov eax,fortunePhaseTime3D + shr eax,5 + and eax,31 + mov esi,eax + jmp fortune_render_hands +fortune_render_orb_hands: + mov ebx,OFFSET fortuneSessionOrbHands3D + mov eax,fortunePhaseTime3D + shr eax,5 + and eax,31 + mov esi,eax +fortune_render_hands: + invoke DrawAnimSprite3D,ebx,esi,134,32,280,250 + cmp fortuneSubMode3D,FORTUNE_SUBMODE_MENU + je fortune_render_menu + cmp fortuneSubMode3D,FORTUNE_SUBMODE_ORB + je fortune_render_orb + cmp fortunePhase3D,0 + je fortune_render_input + invoke DrawAnimSprite3D,OFFSET fortuneSessionCards3D,44,142,300,30,40 + xor esi,esi +fortune_render_cards: + invoke DrawFortuneSessionCard3D,esi + cmp esi,fortuneRevealed3D + jae @F + mov eax,esi + imul eax,100 + add eax,198 + invoke DrawBuckshotText3D,eax,288,fortuneRoleTexts3D[esi*4],00B9D4E7h +@@: + inc esi + cmp esi,3 + jb fortune_render_cards +fortune_render_input: + invoke DrawBoxBorder,8,199,304,24,00081322h,0046637Fh,1 + mov fortuneCaretX3D,24 + mov fortuneCaretY3D,402 + invoke FortuneWrap3D,OFFSET fortuneQuestion3D,402,4,0,00E8EFF4h + cmp fortunePhase3D,0 + jne fortune_render_footer + test animationClock3D,200h + jnz fortune_render_footer + invoke DrawBuckshotText3D,fortuneCaretX3D,fortuneCaretY3D,OFFSET fortuneCaret3D,00FFFFFFh + jmp fortune_render_footer +fortune_render_menu: + invoke DrawBoxBorder,8,196,148,18,000D2136h,005D86A0h,1 + invoke DrawBuckshotText3D,100,404,OFFSET fortuneTarot3D,00D2E8F2h + invoke DrawBoxBorder,164,196,148,18,000D2136h,005D86A0h,1 + 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 + jne @F + mov eax,fortunePhaseTime3D + imul eax,135 + xor edx,edx + mov ecx,1400 + div ecx + mov ebx,330 + sub ebx,eax +@@: + invoke DrawAnimSprite3D,OFFSET fortuneSessionMist3D,esi,214,ebx,128,128 +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 + invoke DrawBoxBorder,8,227,76,12,000B1E30h,00445F75h,1 + mov esi,OFFSET fortuneAsk3D + cmp fortunePhase3D,0 + je @F + mov esi,OFFSET fortuneContinue3D +@@: + invoke DrawBuckshotText3D,24,458,esi,00CCE4F4h +fortune_render_again_button: + invoke DrawBoxBorder,88,227,76,12,000B1E30h,00445F75h,1 + invoke DrawBuckshotText3D,192,458,OFFSET fortuneAgain3D,0091A8BCh + invoke DrawBoxBorder,172,227,64,12,000B1E30h,00445F75h,1 + invoke DrawBuckshotText3D,368,458,OFFSET fortuneMenu3D,00CCE4F4h +fortune_render_exit_button: + invoke DrawBoxBorder,240,227,72,12,000B1E30h,00445F75h,1 + invoke DrawBuckshotText3D,516,458,OFFSET fortuneExit3D,00CCE4F4h + ret +RenderFortuneDialog3D ENDP diff --git a/src_graphics/fortune_dialog_data.inc b/src_graphics/fortune_dialog_data.inc index dc802a1..b743d0c 100644 --- a/src_graphics/fortune_dialog_data.inc +++ b/src_graphics/fortune_dialog_data.inc @@ -1,4 +1,7 @@ MODE_FORTUNE equ 25 +FORTUNE_SUBMODE_MENU equ 0 +FORTUNE_SUBMODE_TAROT equ 1 +FORTUNE_SUBMODE_ORB equ 2 BeginFortuneDialog3D PROTO HandleFortuneDialog3D PROTO RenderFortuneDialog3D PROTO @@ -7,61 +10,116 @@ StopFortuneWorker3D PROTO PollFortuneWorker3D PROTO SubmitFortuneQuestion3D PROTO FortuneWrap3D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD -InitFortuneMindState3D PROTO -GenerateTarotReadingMind3D PROTO -GenerateStudentGradeMind3D PROTO -DetectTopic3D PROTO :DWORD -FortuneStrLen PROTO :DWORD -FortuneStrCat PROTO :DWORD,:DWORD -SubmitStudentGrade3D PROTO -DrawFortuneCardBox3D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD -CreatePipe PROTO :DWORD,:DWORD,:DWORD,:DWORD -SetHandleInformation PROTO :DWORD,:DWORD,:DWORD -CreateProcessA PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD -PeekNamedPipe PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD -TerminateProcess PROTO :DWORD,:DWORD -GetExitCodeProcess PROTO :DWORD,:DWORD -include src_graphics\fortune_mind_data.inc +UpdateFortuneSession3D PROTO +SetFortuneSpeech3D PROTO :DWORD +FortuneCardSpeech3D PROTO :DWORD +DrawFortuneSpeech3D PROTO +DrawFortuneSessionCard3D PROTO :DWORD .data -fortuneWorkerPath3D db "ai\fortune-engine.exe",0 -fortuneWorkerCommand3D db "ai\fortune-engine.exe",0 -fortuneNullName3D db "NUL",0 -fortunePipeSecurity3D dd 12,0,1 -fortuneStartup3D dd 17 dup(0) +fortuneSessionPacks3D LABEL BYTE +fortuneSessionRoom3D AnimPack3D <> +fortuneSessionShuffle3D AnimPack3D <> +fortuneSessionDeal3D AnimPack3D <> +fortuneSessionGesture3D AnimPack3D <> +fortuneSessionOrbHands3D AnimPack3D <> +fortuneSessionMist3D AnimPack3D <> +fortuneSessionCards3D 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 +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 +fortuneSessionPaths3D dd OFFSET fortuneSessionPath0,OFFSET fortuneSessionPath1,OFFSET fortuneSessionPath2,OFFSET fortuneSessionPath3 + dd OFFSET fortuneSessionPath4,OFFSET fortuneSessionPath5,OFFSET fortuneSessionPath6 +fortuneSubMode3D dd 0 +fortunePhase3D dd 0 +fortunePhaseTime3D dd 0 +fortuneSessionClock3D dd 0 +fortuneSpeechTime3D dd 0 +fortuneSpeechLength3D dd 0 +fortuneSpeechVisible3D dd 0 +fortuneSpeechWidth3D dd 0 +fortuneSpeechHeight3D dd 0 +fortuneQuestionLength3D dd 0 +fortuneQuestion3D db 241 dup(0) +fortuneDrawCards3D dd 3 dup(0) +fortuneCardsDrawn3D dd 0 +fortuneDealt3D dd 0 +fortuneRevealed3D dd 0 +fortuneWorkerState3D dd 0 fortuneProcess3D dd 4 dup(0) fortuneRead3D dd 0 fortuneWrite3D dd 0 -fortuneChildRead3D dd 0 -fortuneChildWrite3D dd 0 -fortuneNull3D dd 0 -fortuneWorkerState3D dd 0 ; 0=off, 1=loading, 2=ready, 3=generating, 4=error -fortuneWorkerTick3D dd 0 -fortuneReceiveLength3D dd -1 -fortuneReceiveHeader3D dd 3 dup(0) -fortuneRequest3D dd 31514346h,0,0,0,0 -fortuneIo3D dd 0 -fortuneAvailable3D dd 0 -fortuneQuestionLength3D dd 0 -fortuneQuestion3D db 241 dup(0) -fortuneAnswer3D db 8193 dup(0) -fortuneBackground3D dd SCREEN_SIZE dup(0) +fortuneSpeech3D db 256 dup(0) +fortuneLine3D db 73 dup(0) +fortuneGlyph3D db 0,0 +fortuneCaret3D db "_",0 +fortuneCaretX3D dd 24 +fortuneCaretY3D dd 402 fortunePage3D dd 0 fortuneMore3D dd 0 fortuneShort3D dd 0 -fortuneLine3D db 73 dup(0) -fortuneCaretX3D dd 24 -fortuneCaretY3D dd 366 -fortuneCaret3D db "_",0 +fortuneDurations3D dd 0,3200,1000,1000,1000,800,3600,800,3600,800,3600,3600,0 +fortuneRoleTexts3D dd OFFSET fortuneFirst3D,OFFSET fortuneSecond3D,OFFSET fortuneThird3D fortuneTitle3D db 195,192,196,192,203,202,192,0 -fortuneLoading3D db 207,240,238,225,243,230,228,224,254,32,234,224,240,242,251,46,46,46,0 -fortuneThinking3D db 202,224,240,242,251,32,238,242,226,229,247,224,254,242,46,46,46,0 -fortuneReady3D db 202,224,234,238,233,32,226,238,239,240,238,241,32,242,229,225,255,32,239,240,232,226,184,235,63,0 -fortuneError3D db 205,229,32,243,228,224,235,238,241,252,32,231,224,239,243,241,242,232,242,252,32,237,229,233,240,238,241,229,242,252,46,32 - db 207,240,238,226,229,240,252,32,234,238,236,239,235,229,234,242,32,232,227,240,251,46,0 -fortuneAsk3D db 209,207,208,206,209,200,210,220,0 fortuneExit3D db 194,219,201,210,200,0 -fortuneDisclaimer3D db 208,224,241,234,235,224,228,32,228,235,255,32,240,224,231,236,251,248,235,229,237,232,255,44,32 - db 237,229,32,228,238,241,242,238,226,229,240,237,238,229,32,239,240,229,228,241,234,224,231,224,237,232,229,46,0 -fortunePartial3D db 206,242,226,229,242,32,241,238,234,240,224,249,184,237,0 -fortuneLeft3D db "<",0 -fortuneRight3D db ">",0 +fortuneAsk3D db 209,207,208,206,209,200,210,220,0 +fortuneAgain3D db 197,217,168,32,208,192,199,0 +fortuneMenu3D db 194,219,193,206,208,0 +fortuneTarot3D db 195,192,196,192,205,200,197,0 +fortuneFate3D db 208,197,216,197,205,200,197,32,209,211,196,220,193,219,0 +fortuneWelcome3D db 215,242,238,32,242,229,225,255,32,239,240,232,226,229,235,238,63,0 +fortuneReady3D db 202,224,234,238,233,32,226,238,239,240,238,241,10,242,229,225,255,32,239,240,232,226,184,235 + db 63,0 +fortuneShuffle3D db 210,192,209,206,194,192,205,200,197,0 +fortuneDeal3D db 208,192,209,202,203,192,196,0 +fortuneReveal3D db 202,192,208,210,219,32,206,210,202,208,219,194,192,222,210,209,223,0 +fortuneSummary3D db 194,241,229,32,242,240,232,32,234,224,240,242,251,32,238,242,234,240,251,242,251,46,10,206 + db 225,249,232,233,32,238,242,226,229,242,32,237,224,32,226,238,239,240,238,241,10,239,238,234 + db 224,32,237,229,32,240,229,224,235,232,231,238,226,224,237,46,0 +fortuneUnavailable3D db 207,238,234,224,32,237,229,32,236,238,227,243,32,238,225,250,255,241,237,232,242,252,10,231 + db 237,224,247,229,237,232,229,32,253,242,238,233,32,234,224,240,242,251,46,0 +fortuneOrbText3D db 216,224,240,32,236,238,235,247,232,242,46,10,208,229,248,229,237,232,229,32,239,238,234,224 + db 32,237,229,32,240,229,224,235,232,231,238,226,224,237,238,46,0 +fortuneOrbStatus3D db 213,208,211,209,210,192,203,220,205,219,201,32,216,192,208,0 +fortuneMissing3D db 205,229,32,243,228,224,235,238,241,252,32,231,224,227,240,243,231,232,242,252,32,232,231,238,225,240,224,230,229,237,232,255,46,0 +fortuneContinue3D db 196,192,203,220,216,197,0 +fortuneQuestionLabel3D db 210,194,206,201,32,194,206,207,208,206,209,0 +fortuneFirst3D db 207,197,208,194,192,223,32,202,192,208,210,192,0 +fortuneSecond3D db 194,210,206,208,192,223,32,202,192,208,210,192,0 +fortuneThird3D db 210,208,197,210,220,223,32,202,192,208,210,192,0 +fortuneUpright3D db 207,240,255,236,238,229,32,239,238,235,238,230,229,237,232,229,0 +fortuneReversed3D db 207,229,240,229,226,184,240,237,243,242,224,255,32,234,224,240,242,224,0 +fortuneCardName0 db 216,243,242,0 +fortuneCardName1 db 204,224,227,0 +fortuneCardName2 db 194,229,240,245,238,226,237,224,255,32,230,240,232,246,224,0 +fortuneCardName3 db 200,236,239,229,240,224,242,240,232,246,224,0 +fortuneCardName4 db 200,236,239,229,240,224,242,238,240,0 +fortuneCardName5 db 200,229,240,238,244,224,237,242,0 +fortuneCardName6 db 194,235,254,225,235,184,237,237,251,229,0 +fortuneCardName7 db 202,238,235,229,241,237,232,246,224,0 +fortuneCardName8 db 209,232,235,224,0 +fortuneCardName9 db 206,242,248,229,235,252,237,232,234,0 +fortuneCardName10 db 202,238,235,229,241,238,32,244,238,240,242,243,237,251,0 +fortuneCardName11 db 209,239,240,224,226,229,228,235,232,226,238,241,242,252,0 +fortuneCardName12 db 207,238,226,229,248,229,237,237,251,233,0 +fortuneCardName13 db 209,236,229,240,242,252,0 +fortuneCardName14 db 211,236,229,240,229,237,237,238,241,242,252,0 +fortuneCardName15 db 196,252,255,226,238,235,0 +fortuneCardName16 db 193,224,248,237,255,0 +fortuneCardName17 db 199,226,229,231,228,224,0 +fortuneCardName18 db 203,243,237,224,0 +fortuneCardName19 db 209,238,235,237,246,229,0 +fortuneCardName20 db 209,243,228,0 +fortuneCardName21 db 204,232,240,0 +fortuneCardNames3D dd OFFSET fortuneCardName0,OFFSET fortuneCardName1,OFFSET fortuneCardName2,OFFSET fortuneCardName3 + dd OFFSET fortuneCardName4,OFFSET fortuneCardName5,OFFSET fortuneCardName6,OFFSET fortuneCardName7 + dd OFFSET fortuneCardName8,OFFSET fortuneCardName9,OFFSET fortuneCardName10,OFFSET fortuneCardName11 + dd OFFSET fortuneCardName12,OFFSET fortuneCardName13,OFFSET fortuneCardName14,OFFSET fortuneCardName15 + dd OFFSET fortuneCardName16,OFFSET fortuneCardName17,OFFSET fortuneCardName18,OFFSET fortuneCardName19 + dd OFFSET fortuneCardName20,OFFSET fortuneCardName21 +.data? +fortuneBackground3D dd SCREEN_SIZE dup(?) +.data diff --git a/src_graphics/hall_scene.inc b/src_graphics/hall_scene.inc index 80cdaed..8056a6c 100644 --- a/src_graphics/hall_scene.inc +++ b/src_graphics/hall_scene.inc @@ -781,6 +781,7 @@ CheckHallInteractions3D PROC ; A point on the serving edge remains in front even when standing close. invoke HallTarget3D, playerX,23*FP_1+FP_HALF,INTERACT_BAR,OFFSET hallBarPrompt3D hall_check_other: + invoke HallTarget3D,FP_1*305/10,28*FP_1,INTERACT_DARTS,OFFSET dtPrompt invoke HallTarget3D,16*FP_1,FP_1*73/10,INTERACT_CABARET_SEAT,OFFSET cabaretSeat3D invoke HallTarget3D,scrStation3D.posX,scrStation3D.posY,INTERACT_SCRATCH,OFFSET scratchHall3D invoke HallTarget3D,FP_1*285/10,FP_1*105/10,INTERACT_BASEMENT,OFFSET hallBunkerPrompt3D @@ -797,6 +798,12 @@ hall_check_bunker: CheckHallInteractions3D ENDP TriggerHallInteraction3D PROC + cmp interactTargetId,INTERACT_DARTS + jne hall_not_darts + call BeginDarts3D + mov eax,1 + ret +hall_not_darts: call TryFortuneInteraction3D test eax,eax jnz hall_trigger_handled diff --git a/src_graphics/native_blackjack_party_render.inc b/src_graphics/native_blackjack_party_render.inc index 3008710..9c56d74 100644 --- a/src_graphics/native_blackjack_party_render.inc +++ b/src_graphics/native_blackjack_party_render.inc @@ -1,4 +1,8 @@ DrawBJPartySelector3D PROC uses ebx esi edi + cmp obsActive3D,0 + je @F + ret +@@: xor edi,edi cmp cbState3D,CB_IDLE je @F diff --git a/src_graphics/native_durak.inc b/src_graphics/native_durak.inc index e72ca2f..8777efd 100644 --- a/src_graphics/native_durak.inc +++ b/src_graphics/native_durak.inc @@ -573,33 +573,39 @@ dk_find_done: DurakFindMove3D ENDP DurakAITurn3D PROC - cmp dkAttacker3D, 1 - je dk_ai_attack - invoke DurakFindMove3D, 1, DK_TRANSFER - cmp eax, 0FFFFFFFFh - je @F - invoke DurakPlay3D, 1, eax, DK_TRANSFER - ret -@@: - invoke DurakFindMove3D, 1, DK_DEFEND - cmp eax, 0FFFFFFFFh - je dk_ai_take - invoke DurakPlay3D, 1, eax, DK_DEFEND - ret -dk_ai_take: - invoke DurakTake3D, 1 - ret -dk_ai_attack: - invoke DurakFindMove3D, 1, DK_ATTACK - cmp eax, 0FFFFFFFFh - je dk_ai_finish - invoke DurakPlay3D, 1, eax, DK_ATTACK - ret -dk_ai_finish: - invoke DurakFinish3D, 1 + invoke DurakBotTurn3D,1 ret DurakAITurn3D ENDP +DurakBotTurn3D PROC actor:DWORD + mov eax,actor + cmp dkAttacker3D,eax + je dk_ai_attack + invoke DurakFindMove3D, actor, DK_TRANSFER + cmp eax, 0FFFFFFFFh + je @F + invoke DurakPlay3D, actor, eax, DK_TRANSFER + ret +@@: + invoke DurakFindMove3D, actor, DK_DEFEND + cmp eax, 0FFFFFFFFh + je dk_ai_take + invoke DurakPlay3D, actor, eax, DK_DEFEND + ret +dk_ai_take: + invoke DurakTake3D, actor + ret +dk_ai_attack: + invoke DurakFindMove3D, actor, DK_ATTACK + cmp eax, 0FFFFFFFFh + je dk_ai_finish + invoke DurakPlay3D, actor, eax, DK_ATTACK + ret +dk_ai_finish: + invoke DurakFinish3D, actor + ret +DurakBotTurn3D ENDP + DurakMouse3D PROC uses ebx cmp mouseClick3D, 0 je dk_mouse_done diff --git a/src_graphics/native_poker.inc b/src_graphics/native_poker.inc index 5dce089..4ffead1 100644 --- a/src_graphics/native_poker.inc +++ b/src_graphics/native_poker.inc @@ -382,19 +382,27 @@ poker_showdown_done: PokerShowdown3D ENDP ; The AI sees only its two cards and the already exposed board. No player cards/future deck. -PokerAIDecide3D PROC uses ebx esi edi +PokerAIDecide3D PROC + invoke PokerBotAct3D,1 + ret +PokerAIDecide3D ENDP + +PokerBotAct3D PROC uses ebx esi edi actor:DWORD local strength:DWORD,owed:DWORD mov eax, pkCurrent3D - sub eax, pkPaid3D[4] + mov ecx,actor + sub eax, pkPaid3D[ecx*4] mov owed, eax cmp pkBoardCount3D, 3 jae poker_ai_postflop - mov eax, pkHole3D[8] + mov ebx,actor + shl ebx,3 + mov eax, pkHole3D[ebx] xor edx, edx mov ecx, 13 div ecx mov esi, edx - mov eax, pkHole3D[12] + mov eax, pkHole3D[ebx+4] xor edx, edx div ecx mov strength, 0 @@ -416,7 +424,8 @@ poker_ai_pair: jmp poker_ai_odds poker_ai_postflop: lea edi, pkEvalHand3D - lea esi, pkHole3D[8] + mov eax,actor + lea esi, pkHole3D[eax*8] movsd movsd lea esi, pkBoard3D @@ -452,7 +461,12 @@ poker_ai_consider_raise: je poker_ai_call cmp pkRaisedCount3D, 2 jae poker_ai_call - cmp bankroll, 0 + mov eax,bankroll + cmp actor,1 + je @F + mov eax,pkStack3D +@@: + cmp eax, 0 je poker_ai_call cmp strength, 3 jae poker_ai_strong_raise @@ -464,11 +478,11 @@ poker_ai_strong_raise: jae poker_ai_call poker_ai_raise: mov pkAiMessage3D, 3 - invoke PokerRaise3D, 1, pkMinRaise3D + invoke PokerRaise3D, actor, pkMinRaise3D ret poker_ai_fold: mov pkAiMessage3D, 4 - invoke PokerFold3D, 1 + invoke PokerFold3D, actor ret poker_ai_call: mov pkAiMessage3D, 1 @@ -476,9 +490,9 @@ poker_ai_call: je @F mov pkAiMessage3D, 2 @@: - invoke PokerCall3D, 1 + invoke PokerCall3D, actor ret -PokerAIDecide3D ENDP +PokerBotAct3D ENDP HandleNativePoker3D PROC uses ebx call HandleCardExit3D diff --git a/src_graphics/npc_contracts.inc b/src_graphics/npc_contracts.inc index 580dfe8..392e823 100644 --- a/src_graphics/npc_contracts.inc +++ b/src_graphics/npc_contracts.inc @@ -190,6 +190,8 @@ npc_accept_done: NpcAcceptContract3D ENDP NpcRecordRound3D PROC uses ebx esi edi game:DWORD,won:DWORD + cmp obsActive3D,0 + jne npc_event_done call NpcEnsureJournal3D test eax,eax jz npc_event_done diff --git a/src_graphics/observer.inc b/src_graphics/observer.inc new file mode 100644 index 0000000..ba0341b --- /dev/null +++ b/src_graphics/observer.inc @@ -0,0 +1,494 @@ +; Spectator control supplies decisions to the real native games, never simulated results. +.code +BeginObserver3D PROC uses esi edi + lea esi,screenBuffer + lea edi,obsBackground3D + mov ecx,SCREEN_SIZE + rep movsd + mov engineMode,MODE_OBSERVER + mov obsError3D,0 + mov paused3D,0 + call ClearKeys3D + mov mouseClick3D,0 + ret +BeginObserver3D ENDP + +StartObserver3D PROC uses ebx esi + cmp obsActive3D,0 + jne obs_start_done + cmp obsSelection3D,OBS_GAME_COUNT + jae obs_start_done + xor esi,esi +obs_save_profile: + mov ebx,obsProfilePointers3D[esi*4] + mov eax,[ebx] + mov obsSavedProfile3D[esi*4],eax + inc esi + cmp esi,LENGTHOF obsProfilePointers3D + jb obs_save_profile + mov obsActive3D,1 + mov obsPaused3D,0 + mov obsRound3D,0 + mov obsWins3D[0],0 + mov obsWins3D[4],0 + mov obsWins3D[8],0 + mov tournamentMode,0 + mov cardExitActive3D,0 + mov obsError3D,0 + mov eax,obsSelection3D + mov eax,obsModes3D[eax*4] + mov engineMode,eax + cmp eax,MODE_POOL + je obs_init_pool + cmp eax,MODE_DURAK + je obs_init_durak + cmp eax,MODE_BLACKJACK + je obs_init_blackjack + call InitNativePoker3D + jmp obs_check_assets +obs_init_durak: + call InitNativeDurak3D + mov eax,obsSelection3D + sub eax,2 + mov dkVariant3D,eax + jmp obs_check_assets +obs_init_blackjack: + call InitNativeBlackjack3D + mov cbBotCount3D,0 +obs_check_assets: + cmp cardTablePixels3D,0 + jne obs_start_round + call StopObserver3D + mov obsError3D,1 + ret +obs_init_pool: + mov poolAI3D,1 + mov eax,obsSelection3D + sub eax,4 + invoke StartBilliards3D,eax +obs_start_round: + call ObserverNewRound3D +obs_start_done: + ret +StartObserver3D ENDP + +StopObserver3D PROC uses esi ebx + cmp obsActive3D,0 + je obs_stop_menu + ; The save barrier remains active until every personal value has been restored. + xor esi,esi +obs_restore_profile: + mov ebx,obsProfilePointers3D[esi*4] + mov eax,obsSavedProfile3D[esi*4] + mov [ebx],eax + inc esi + cmp esi,LENGTHOF obsProfilePointers3D + jb obs_restore_profile + mov obsActive3D,0 +obs_stop_menu: + mov engineMode,MODE_OBSERVER + mov paused3D,0 + mov cardExitActive3D,0 + call ClearKeys3D + mov mouseClick3D,0 + invoke PlayCasinoSound3D,0,0,0 + ret +StopObserver3D ENDP + +ObserverNewRound3D PROC + mov obsTimer3D,0 + mov obsResultTimer3D,0 + mov obsOutcome3D,0 + inc obsRound3D + mov bankroll,10000 + cmp engineMode,MODE_POOL + jne @F + call ResetPool3D + ret +@@: + cmp engineMode,MODE_DURAK + jne @F + mov dkBet3D,100 + mov dkState3D,DK_IDLE + call StartNativeDurak3D + ret +@@: + cmp engineMode,MODE_BLACKJACK + jne @F + mov cbBet3D,100 + mov cbState3D,CB_IDLE + call StartNativeBlackjack3D + ret +@@: + mov pkBlind3D,50 + mov pkState3D,PK_IDLE + call StartNativePoker3D + ret +ObserverNewRound3D ENDP + +ObserverOutcome3D PROC + cmp engineMode,MODE_POOL + jne @F + cmp poolState3D,POOL_RESULT + jne obs_no_outcome + mov eax,poolWinner3D + inc eax + ret +@@: + cmp engineMode,MODE_DURAK + jne @F + cmp dkState3D,DK_RESULT + jne obs_no_outcome + mov eax,dkOutcome3D + inc eax + ret +@@: + cmp engineMode,MODE_BLACKJACK + jne @F + cmp cbState3D,CB_RESULT + jne obs_no_outcome + mov eax,cbOutcome3D + cmp eax,3 + jne obs_number_outcome + mov eax,1 + ret +@@: + cmp pkState3D,PK_RESULT + jne obs_no_outcome + mov eax,pkOutcome3D +obs_number_outcome: + inc eax + ret +obs_no_outcome: + xor eax,eax + ret +ObserverOutcome3D ENDP + +ObserverBlackjack3D PROC + cmp cbState3D,CB_PLAYER + jne obs_bj_update + cmp obsTimer3D,700 + jb obs_bj_update + mov obsTimer3D,0 + ; Only our hand and the dealer's exposed up-card enter the decision. + cmp cbPlayerScore3D,17 + jae obs_bj_stand + cmp cbPlayerScore3D,12 + jb obs_bj_hit + mov eax,cbDealer3D[0] + xor edx,edx + mov ecx,13 + div ecx + cmp edx,5 + jae obs_bj_hit +obs_bj_stand: + invoke FinishBJPlayer3D,-1 + jmp obs_bj_update +obs_bj_hit: + call NativeBJHit3D +obs_bj_update: + call HandleNativeBlackjack3D + ret +ObserverBlackjack3D ENDP + +ObserverDurak3D PROC + cmp dkState3D,DK_TURN + jne obs_durak_update + cmp dkActor3D,0 + jne obs_durak_update + cmp obsTimer3D,700 + jb obs_durak_update + mov obsTimer3D,0 + invoke DurakBotTurn3D,0 +obs_durak_update: + call HandleNativeDurak3D + ret +ObserverDurak3D ENDP + +ObserverTick3D PROC uses ebx + cmp obsActive3D,0 + je obs_tick_done + call ObserverOutcome3D + test eax,eax + jz obs_tick_game + cmp obsOutcome3D,0 + jne obs_result_wait + mov obsOutcome3D,eax + inc obsWins3D[eax*4-4] + mov obsResultTimer3D,0 +obs_result_wait: + mov eax,deltaTime3D + add obsResultTimer3D,eax + cmp obsResultTimer3D,3000 + jb obs_tick_done + call ObserverNewRound3D + ret +obs_tick_game: + mov eax,deltaTime3D + add obsTimer3D,eax + cmp engineMode,MODE_POOL + jne @F + call HandlePool3D + ret +@@: + cmp engineMode,MODE_DURAK + jne @F + call ObserverDurak3D + ret +@@: + cmp engineMode,MODE_BLACKJACK + jne @F + call ObserverBlackjack3D + ret +@@: + cmp pkState3D,PK_PLAYER + jne obs_poker_update + cmp obsTimer3D,700 + jb obs_poker_update + mov obsTimer3D,0 + invoke PokerBotAct3D,0 +obs_poker_update: + call HandleNativePoker3D +obs_tick_done: + ret +ObserverTick3D ENDP + +ObserverFrame3D PROC uses ebx esi + invoke Pressed3D,1Bh + test eax,eax + jz obs_frame_input + cmp obsActive3D,0 + je obs_leave_hall + call StopObserver3D + jmp obs_frame_draw +obs_leave_hall: + mov engineMode,MODE_WALK + call ClearKeys3D + mov mouseClick3D,0 + jmp obs_frame_draw +obs_frame_input: + cmp obsActive3D,0 + jne obs_watch_input + xor esi,esi +obs_menu_digit: + lea eax,[esi+31h] + invoke Pressed3D,eax + test eax,eax + jnz obs_menu_pick + inc esi + cmp esi,OBS_GAME_COUNT + jb obs_menu_digit + invoke Pressed3D,26h + test eax,eax + jz @F + cmp obsSelection3D,0 + jne obs_menu_up + mov obsSelection3D,OBS_GAME_COUNT +obs_menu_up: + dec obsSelection3D +@@: + invoke Pressed3D,28h + test eax,eax + jz @F + inc obsSelection3D + cmp obsSelection3D,OBS_GAME_COUNT + jb @F + mov obsSelection3D,0 +@@: + invoke Pressed3D,0Dh + test eax,eax + jnz obs_menu_start + cmp mouseClick3D,0 + je obs_frame_draw + cmp mouseX3D,16 + jb obs_frame_draw + cmp mouseX3D,304 + jae obs_frame_draw + mov eax,mouseY3D + sub eax,52 + js obs_frame_draw + xor edx,edx + mov ecx,23 + div ecx + cmp eax,OBS_GAME_COUNT + jae obs_menu_close_click + cmp edx,20 + jae obs_frame_draw + mov esi,eax +obs_menu_pick: + mov obsSelection3D,esi +obs_menu_start: + call StartObserver3D + jmp obs_frame_draw +obs_menu_close_click: + cmp mouseY3D,212 + jb obs_frame_draw + jmp obs_leave_hall +obs_watch_input: + invoke Pressed3D,20h + test eax,eax + jnz obs_watch_pause + invoke Pressed3D,9 + test eax,eax + jnz obs_watch_speed + invoke Pressed3D,4Eh + test eax,eax + jnz obs_watch_restart + cmp mouseClick3D,0 + je obs_watch_tick + cmp mouseY3D,222 + jb obs_watch_tick + cmp mouseY3D,239 + jae obs_watch_tick + mov eax,mouseX3D + cmp eax,74 + jb obs_watch_pause + cmp eax,134 + jb obs_watch_speed + cmp eax,226 + jb obs_watch_restart + call StopObserver3D + jmp obs_frame_draw +obs_watch_pause: + xor obsPaused3D,1 + jmp obs_watch_tick +obs_watch_speed: + shl obsSpeed3D,1 + cmp obsSpeed3D,4 + jbe obs_watch_tick + mov obsSpeed3D,1 + jmp obs_watch_tick +obs_watch_restart: + call ObserverNewRound3D +obs_watch_tick: + ; Human game commands never reach the autonomous seats. + call ClearKeys3D + mov mouseClick3D,0 + cmp obsPaused3D,0 + jne obs_frame_draw + mov ebx,deltaTime3D + mov eax,ebx + imul eax,obsSpeed3D + mov deltaTime3D,eax + add animationClock3D,eax + call ObserverTick3D + mov deltaTime3D,ebx +obs_frame_draw: + call RenderObserver3D + mov mouseClick3D,0 + ret +ObserverFrame3D ENDP + +RenderObserver3D PROC uses ebx esi edi + cmp obsActive3D,0 + jne obs_render_game + lea esi,obsBackground3D + lea edi,screenBuffer + mov ecx,SCREEN_SIZE + rep movsd + invoke DrawBox,0,0,320,240,00151D20h + invoke DrawString8x8,16,18,OFFSET obsTitle,00E8D7ADh + xor ebx,ebx +obs_render_row: + imul esi,ebx,23 + add esi,52 + mov edi,00728689h + cmp ebx,obsSelection3D + jne @F + mov edi,00E8D7ADh +@@: + invoke DrawBoxBorder,16,esi,288,20,00293436h,edi,1 + add esi,6 + lea eax,[ebx+1] + invoke FormatNumber,eax,OFFSET hudNumBuffer + invoke DrawString8x8,24,esi,OFFSET hudNumBuffer,edi + invoke DrawString8x8,48,esi,obsNames3D[ebx*4],edi + inc ebx + cmp ebx,OBS_GAME_COUNT + jb obs_render_row + invoke DrawBoxBorder,16,212,288,20,00293436h,00728689h,1 + invoke DrawString8x8,136,218,OFFSET obsClose,00E8D7ADh + cmp obsError3D,0 + je obs_render_done + invoke DrawString8x8,16,198,OFFSET obsFailed,00DB8B83h + ret +obs_render_game: + cmp engineMode,MODE_POOL + jne @F + call RenderPool3D + jmp obs_render_hud +@@: + cmp engineMode,MODE_DURAK + jne @F + call RenderNativeDurak3D + jmp obs_render_hud +@@: + cmp engineMode,MODE_BLACKJACK + jne @F + call RenderNativeBlackjack3D + jmp obs_render_hud +@@: + call RenderNativePoker3D +obs_render_hud: + invoke DrawBox,0,0,320,32,00151D20h + mov ebx,obsSelection3D + invoke DrawString8x8,8,5,obsNames3D[ebx*4],00E8D7ADh + invoke DrawString8x8,8,20,OFFSET obsFirst,0090CDB2h + invoke FormatNumber,obsWins3D[0],OFFSET hudNumBuffer + invoke DrawString8x8,48,20,OFFSET hudNumBuffer,0090CDB2h + invoke DrawString8x8,88,20,OFFSET obsSecond,00DEA0A0h + invoke FormatNumber,obsWins3D[4],OFFSET hudNumBuffer + invoke DrawString8x8,128,20,OFFSET hudNumBuffer,00DEA0A0h + invoke DrawString8x8,190,20,OFFSET obsRound,00E8D7ADh + invoke FormatNumber,obsRound3D,OFFSET hudNumBuffer + invoke DrawString8x8,254,20,OFFSET hudNumBuffer,00FFFFFFh + invoke DrawBox,0,215,320,25,00151D20h + cmp obsOutcome3D,0 + je obs_render_turn + mov ebx,OFFSET obsWin1 + cmp obsOutcome3D,1 + je @F + mov ebx,OFFSET obsWin2 + cmp obsOutcome3D,2 + je @F + mov ebx,OFFSET obsDraw +@@: + invoke DrawString8x8,200,5,ebx,00E8D7ADh + jmp obs_render_controls +obs_render_turn: + mov eax,pkTurn3D + cmp engineMode,MODE_POKER + je obs_render_actor + mov eax,dkActor3D + cmp engineMode,MODE_DURAK + je obs_render_actor + mov eax,poolPlayer3D + cmp engineMode,MODE_POOL + je obs_render_actor + xor eax,eax + cmp cbState3D,CB_DEALER + jne obs_render_actor + inc eax +obs_render_actor: + and eax,1 + invoke DrawString8x8,232,5,obsTurnNames3D[eax*4],00A7C7C5h +obs_render_controls: + mov ebx,OFFSET obsPause + cmp obsPaused3D,0 + je @F + mov ebx,OFFSET obsPlay +@@: + invoke DrawBoxBorder,4,222,66,16,00293436h,00728689h,1 + invoke DrawString8x8,14,226,ebx,00FFFFFFh + mov eax,obsSpeed3D + shr eax,1 + mov ebx,obsSpeedNames3D[eax*4] + invoke DrawBoxBorder,78,222,48,16,00293436h,00728689h,1 + invoke DrawString8x8,94,226,ebx,00FFFFFFh + invoke DrawBoxBorder,138,222,80,16,00293436h,00728689h,1 + invoke DrawString8x8,154,226,OFFSET obsRestart,00FFFFFFh + invoke DrawBoxBorder,230,222,86,16,00293436h,00728689h,1 + invoke DrawString8x8,254,226,OFFSET obsGames,00FFFFFFh +obs_render_done: + ret +RenderObserver3D ENDP diff --git a/src_graphics/observer_data.inc b/src_graphics/observer_data.inc new file mode 100644 index 0000000..1617b2d --- /dev/null +++ b/src_graphics/observer_data.inc @@ -0,0 +1,64 @@ +; Native observer uses the existing games; no alternate rules engine. +MODE_OBSERVER equ 26 +OBS_GAME_COUNT equ 6 +BeginObserver3D PROTO +ObserverFrame3D PROTO +StartObserver3D PROTO +StopObserver3D PROTO +ObserverNewRound3D PROTO +ObserverTick3D PROTO +ObserverOutcome3D PROTO +RenderObserver3D PROTO +ObserverBlackjack3D PROTO +ObserverDurak3D PROTO +PokerBotAct3D PROTO :DWORD +DurakBotTurn3D PROTO :DWORD +.data +obsActive3D dd 0 +obsSelection3D dd 0 +obsPaused3D dd 0 +obsSpeed3D dd 1 +obsTimer3D dd 0 +obsResultTimer3D dd 0 +obsOutcome3D dd 0 +obsRound3D dd 0 +obsWins3D dd 0,0,0 +obsError3D dd 0 +obsModes3D dd MODE_POKER,MODE_BLACKJACK,MODE_DURAK,MODE_DURAK,MODE_POOL,MODE_POOL +obsNames3D dd OFFSET obsPoker,OFFSET obsBlackjack,OFFSET obsDurak,OFFSET obsTransfer,OFFSET obsPyramid,OFFSET obsEight +obsProfilePointers3D dd OFFSET bankroll,OFFSET rngSeed3D,OFFSET tournamentMode + dd OFFSET pkBlind3D,OFFSET cbBet3D,OFFSET dkBet3D,OFFSET dkVariant3D + dd OFFSET poolVariant3D,OFFSET poolAI3D,OFFSET cbBotCount3D +obsSavedProfile3D dd 10 dup(0) +obsSpeedNames3D dd OFFSET obsSpeed1,OFFSET obsSpeed2,OFFSET obsSpeed4 +obsSpeed1 db "1X",0 +obsSpeed2 db "2X",0 +obsSpeed4 db "4X",0 +obsSeparator db ":",0 +obsResultNames3D dd OFFSET obsWin1,OFFSET obsWin2,OFFSET obsDraw +obsTurnNames3D dd OFFSET obsTurn1,OFFSET obsTurn2 +obsTurn1 db 213,206,196,32,200,200,32,49,0 +obsTurn2 db 213,206,196,32,200,200,32,50,0 +obsTitle db 205,192,193,203,222,196,197,205,200,197,0 +obsPoker db 207,206,202,197,208,0 +obsBlackjack db 50,49,0 +obsDurak db 196,211,208,192,202,58,32,207,206,196,202,200,196,205,206,201,0 +obsTransfer db 196,211,208,192,202,58,32,207,197,208,197,194,206,196,205,206,201,0 +obsPyramid db 193,200,203,220,223,208,196,58,32,207,200,208,192,204,200,196,192,0 +obsEight db 193,200,203,220,223,208,196,58,32,194,206,209,220,204,197,208,202,192,0 +obsFirst db 200,200,32,49,0 +obsSecond db 200,200,32,50,0 +obsDraw db 205,200,215,220,223,0 +obsWin1 db 207,206,193,197,196,192,32,200,200,32,49,0 +obsWin2 db 207,206,193,197,196,192,32,200,200,32,50,0 +obsPause db 207,192,211,199,192,0 +obsPlay db 62,0 +obsRestart db 199,192,205,206,194,206,0 +obsGames db 200,195,208,219,0 +obsRound db 207,192,208,210,200,223,0 +obsClose db 194,32,213,206,203,203,0 +obsFailed db 205,197,32,211,196,192,203,206,209,220,32,206,210,202,208,219,210,220,32,209,210,206,203,0 +.data? +obsBackground3D dd SCREEN_SIZE dup(?) +.code + diff --git a/src_graphics/platform_code.inc b/src_graphics/platform_code.inc index 89d6d81..da3fc0f 100644 --- a/src_graphics/platform_code.inc +++ b/src_graphics/platform_code.inc @@ -13,6 +13,13 @@ WindowEvents3D PROC uses ebx esi edi hwnd:DWORD, uMsg:DWORD, wp:DWORD, lp:DWORD invoke FortuneChar3D, wp xor eax, eax ret +@@: + cmp uMsg,202h + jne @F + cmp engineMode,MODE_DARTS + jne event_default + mov dtMouseReleased3D,1 + jmp event_mouse_down @@: cmp uMsg, 201h je event_mouse_down @@ -24,6 +31,8 @@ WindowEvents3D PROC uses ebx esi edi hwnd:DWORD, uMsg:DWORD, wp:DWORD, lp:DWORD @@: cmp uMsg, 200h jne @F + cmp engineMode,MODE_DARTS + je event_mouse_down cmp engineMode, MODE_POOL jne event_default test wp, 1 @@ -61,6 +70,15 @@ event_mouse_down: sub eax, viewLeft3D cmp eax, viewWidth3D jae event_done + cmp engineMode,MODE_DARTS + jne event_coarse_x + push eax + imul eax,640*256 + xor edx,edx + div viewWidth3D + mov dtCursorX3D,eax + pop eax +event_coarse_x: imul eax, 320 xor edx, edx div viewWidth3D @@ -69,6 +87,15 @@ event_mouse_down: sub eax, viewTop3D cmp eax, viewHeight3D jae event_done + cmp engineMode,MODE_DARTS + jne event_coarse_y + push eax + imul eax,480*256 + xor edx,edx + div viewHeight3D + mov dtCursorY3D,eax + pop eax +event_coarse_y: imul eax, 240 xor edx, edx div viewHeight3D @@ -87,6 +114,13 @@ event_mouse_down: pyr_event_pick: mov pyrPickClick3D,1 pyr_event_pick_done: + cmp uMsg,202h + je event_done + cmp uMsg,200h + jne @F + cmp engineMode,MODE_DARTS + je event_done +@@: mov mouseClick3D, 1 jmp event_done event_key_down: @@ -110,6 +144,8 @@ event_fullscreen: call ToggleFullscreen3D jmp event_done event_focus_lost: + mov dtHolding3D,0 + mov dtMouseReleased3D,0 call ClearKeys3D jmp event_done event_erase: @@ -572,6 +608,8 @@ progress_loaded: LoadProgress3D ENDP SaveProgress3D PROC uses ebx + cmp obsActive3D,0 + jne progress_saved IFDEF CASINO_ACTION_TEST ret ENDIF diff --git a/src_graphics/pyramid_play.inc b/src_graphics/pyramid_play.inc index 187c1d5..33539e8 100644 --- a/src_graphics/pyramid_play.inc +++ b/src_graphics/pyramid_play.inc @@ -537,7 +537,11 @@ pyr_actor: ret @@: cmp poolPlayer3D,1 - jne pyr_human + je pyr_actor_ai_check + cmp obsActive3D,0 + jne pyr_actor_ai_check + jmp pyr_human +pyr_actor_ai_check: cmp poolAI3D,0 je pyr_human cmp poolTime3D,1000 diff --git a/src_graphics/world_dressing.inc b/src_graphics/world_dressing.inc index be7712f..ac4f275 100644 --- a/src_graphics/world_dressing.inc +++ b/src_graphics/world_dressing.inc @@ -115,6 +115,7 @@ RenderHallDressing3D PROC uses ebx esi edi cmp hallLevel3D,0 jne hall_dressing_done call RenderTerminalCabinets3D + call DrawHallDarts3D lea esi,hallWallArt3D mov edi,WORLD_WALL_ART_COUNT hall_wall_painting: diff --git a/tools/build_setup.py b/tools/build_setup.py new file mode 100644 index 0000000..412687c --- /dev/null +++ b/tools/build_setup.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python3 +"""Build and package the complete modern Assembler Casino into PokerGameSetup.exe.""" + +import hashlib +import os +import re +import subprocess +import sys +import zipfile +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +MARKER = b"<>" + + +def get_sha256(data_or_path): + if isinstance(data_or_path, (str, Path)): + h = hashlib.sha256() + with open(data_or_path, "rb") as f: + while chunk := f.read(65536): + h.update(chunk) + return h.hexdigest().upper() + return hashlib.sha256(data_or_path).hexdigest().upper() + + +def collect_payload_files(): + entries = [] + + # Executables + for src, dst in [ + (ROOT / "dist/Assembler Casino.exe", "Assembler Casino.exe"), + (ROOT / "dist/Uninstall Assembler Casino.exe", "Uninstall Assembler Casino.exe"), + (ROOT / "bin/poker.exe", "bin/poker.exe"), + (ROOT / "poker.exe", "poker.exe"), + ]: + if not src.exists(): + raise FileNotFoundError(f"Required binary not found: {src}") + entries.append((src, dst)) + + # Assets (excluding raw sprites used only for asset generation) + assets_dir = ROOT / "assets" + for r, dirs, files in os.walk(assets_dir): + if "sprites" in Path(r).parts: + continue + for f in sorted(files): + fp = Path(r) / f + rel = fp.relative_to(ROOT).as_posix() + entries.append((fp, rel)) + + # Docs + docs_dir = ROOT / "docs" + for r, dirs, files in os.walk(docs_dir): + for f in sorted(files): + if f.endswith((".txt", ".md")): + fp = Path(r) / f + rel = fp.relative_to(ROOT).as_posix() + entries.append((fp, rel)) + + # Tools + tool_files = [ + "tools/previews/preview_dbd_hook.bat", + "tools/previews/preview_hakari.bat", + "tools/previews/preview_intro.bat", + "tools/previews/preview_casino3d.bat", + "tools/tests/achievement_popup.bat", + "tools/tests/release_smoke_check.ps1", + ] + for rel in tool_files: + fp = ROOT / rel + if fp.exists(): + entries.append((fp, rel)) + + # Backwards compatibility paths for tests / older scripts + if (ROOT / "tools/previews/preview_dbd_hook.bat").exists(): + entries.append((ROOT / "tools/previews/preview_dbd_hook.bat", "tools/preview_dbd_hook.bat")) + if (ROOT / "tools/tests/release_smoke_check.ps1").exists(): + entries.append((ROOT / "tools/tests/release_smoke_check.ps1", "tools/release_smoke_check.ps1")) + + # Runtime configuration and state + for rel in ["runtime/casino3d-progress.dat", "runtime/display.cfg"]: + fp = ROOT / rel + if fp.exists(): + entries.append((fp, rel)) + + # AI worker & metadata (without the 2.5 GB model per docs/FORTUNE_AI.md) + ai_dir = ROOT / "ai" + if ai_dir.exists(): + for r, dirs, files in os.walk(ai_dir): + if "models" in Path(r).parts: + continue + for f in sorted(files): + fp = Path(r) / f + rel = fp.relative_to(ROOT).as_posix() + entries.append((fp, rel)) + + # Root documentation + for rel in ["AGENTS.md", "README.md"]: + fp = ROOT / rel + if fp.exists(): + entries.append((fp, rel)) + + return entries + + +def build_payload_zip(zip_path, entries): + print(f"Creating payload archive: {zip_path} ({len(entries)} files)...") + zip_path.parent.mkdir(parents=True, exist_ok=True) + with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=6) as zf: + for src, dst in entries: + zf.write(src, dst) + size_mb = zip_path.stat().st_size / (1024 * 1024) + print(f"Payload archive created: {size_mb:.2f} MB") + + +def compile_setup_base(source_path, base_exe_path): + csc = Path(os.environ.get("WINDIR", r"C:\Windows")) / r"Microsoft.NET\Framework\v4.0.30319\csc.exe" + if not csc.exists(): + raise RuntimeError(f"csc.exe not found at {csc}") + + icon = ROOT / "assets/icons/casino.ico" + cmd = [ + str(csc), + "/nologo", + "/target:winexe", + "/platform:x86", + "/optimize+", + "/reference:System.Windows.Forms.dll", + "/reference:System.Drawing.dll", + "/reference:System.IO.Compression.dll", + "/reference:System.IO.Compression.FileSystem.dll", + "/reference:System.Core.dll", + f"/win32icon:{icon}", + f"/out:{base_exe_path}", + str(source_path), + ] + print(f"Compiling installer base: {' '.join(cmd)}") + res = subprocess.run(cmd, capture_output=True, text=True) + if res.returncode != 0: + raise RuntimeError(f"Compilation failed:\n{res.stdout}\n{res.stderr}") + print(f"Compiled: {base_exe_path} ({base_exe_path.stat().st_size} bytes)") + + +def main(): + print("=== Building Assembler Casino PokerGameSetup ===") + + # 1. Collect files + entries = collect_payload_files() + payload_zip = ROOT / "build/payload_release_final.zip" + build_payload_zip(payload_zip, entries) + + # 2. Hash payload + payload_hash = get_sha256(payload_zip) + print(f"Payload SHA-256: {payload_hash}") + + # 3. Update PokerGameSetupGui.cs with new payload hash + setup_cs = ROOT / "build/PokerGameSetupGui.cs" + cs_content = setup_cs.read_text(encoding="utf-8") + hash_pattern = r'const string ExpectedPayloadSha256 = "([A-Fa-f0-9]{64}|TO_BE_FILLED_BY_RELEASE_BUILD)";' + if not re.search(hash_pattern, cs_content): + raise ValueError("Could not find ExpectedPayloadSha256 constant in PokerGameSetupGui.cs") + new_cs_content = re.sub( + hash_pattern, + f'const string ExpectedPayloadSha256 = "{payload_hash}";', + cs_content, + count=1 + ) + setup_cs.write_text(new_cs_content, encoding="utf-8") + print("Updated ExpectedPayloadSha256 in PokerGameSetupGui.cs") + + # 4. Compile base setup executable + base_exe = ROOT / "build/PokerGameSetup_base.exe" + compile_setup_base(setup_cs, base_exe) + + # 5. Assemble final PokerGameSetup.exe + setup_exe = ROOT / "PokerGameSetup.exe" + print(f"Assembling {setup_exe}...") + with open(setup_exe, "wb") as out_f: + with open(base_exe, "rb") as in_base: + out_f.write(in_base.read()) + out_f.write(MARKER) + with open(payload_zip, "rb") as in_zip: + while chunk := in_zip.read(65536): + out_f.write(chunk) + + total_mb = setup_exe.stat().st_size / (1024 * 1024) + print(f"=== Successfully built {setup_exe} ({total_mb:.2f} MB) ===") + + # 6. Verify built setup + with open(setup_exe, "rb") as f: + head = f.read(4 * 1024 * 1024) + idx = head.find(MARKER) + assert idx != -1, "Marker not found in generated setup!" + f.seek(idx + len(MARKER)) + h = hashlib.sha256() + while chunk := f.read(65536): + h.update(chunk) + actual_hash = h.hexdigest().upper() + assert actual_hash == payload_hash, f"Hash mismatch: {actual_hash} != {payload_hash}" + print(f"[VERIFIED] Marker and SHA-256 match perfectly ({actual_hash})") + + +if __name__ == "__main__": + main() diff --git a/tools/generators/darts_cp1251.patch b/tools/generators/darts_cp1251.patch new file mode 100644 index 0000000..2bff9ac --- /dev/null +++ b/tools/generators/darts_cp1251.patch @@ -0,0 +1,21 @@ +diff --git a/casino3d.inc b/casino3d.inc +--- a/casino3d.inc ++++ b/casino3d.inc +@@ -174,1 +174,2 @@ + include src_graphics\observer_data.inc ++include src_graphics\darts_data.inc +@@ -5198,1 +5199,2 @@ + include src_graphics\observer.inc ++include src_graphics\darts.inc +@@ -5357,1 +5359,9 @@ + check_slots_mode: ++ cmp engineMode,MODE_DARTS ++ jne check_darts_done ++ call HandleDarts3D ++ call RenderDarts3D ++ jmp present_frame ++check_darts_done: + cmp engineMode,MODE_FORTUNE +@@ -5642,1 +5652,2 @@ + call FreeRussianAssets3D ++ call FreeDarts3D diff --git a/tools/generators/darts_hall.patch b/tools/generators/darts_hall.patch new file mode 100644 index 0000000..1adf1d5 --- /dev/null +++ b/tools/generators/darts_hall.patch @@ -0,0 +1,15 @@ +diff --git a/src_graphics/hall_scene.inc b/src_graphics/hall_scene.inc +--- a/src_graphics/hall_scene.inc ++++ b/src_graphics/hall_scene.inc +@@ -783,1 +783,2 @@ + hall_check_other: ++ invoke HallTarget3D,FP_1*305/10,28*FP_1,INTERACT_DARTS,OFFSET dtPrompt +@@ -799,1 +800,9 @@ + TriggerHallInteraction3D PROC ++ cmp interactTargetId,INTERACT_DARTS ++ jne hall_not_darts ++ call BeginDarts3D ++ mov eax,1 ++ ret ++hall_not_darts: + call TryFortuneInteraction3D diff --git a/tools/generators/observer_cp1251.patch b/tools/generators/observer_cp1251.patch new file mode 100644 index 0000000..759684e --- /dev/null +++ b/tools/generators/observer_cp1251.patch @@ -0,0 +1,29 @@ +diff --git a/casino3d.inc b/casino3d.inc +--- a/casino3d.inc ++++ b/casino3d.inc +@@ -173,1 +173,2 @@ + include src_graphics\charms_data.inc ++include src_graphics\observer_data.inc +@@ -4856,5 +4857,5 @@ + check_target_observer: + cmp eax, INTERACT_OBSERVER + jne target_unported +- call BeginUnavailable3D ++ call BeginObserver3D + ret +@@ -5196,1 +5197,2 @@ + include src_graphics\charms.inc ++include src_graphics\observer.inc +@@ -5245,2 +5247,11 @@ + call ToggleFullscreen3D + @@: ++ cmp obsActive3D,0 ++ jne engine_observer_frame ++ cmp engineMode,MODE_OBSERVER ++ jne engine_observer_skip ++engine_observer_frame: ++ call UpdateMouseLockState ++ call ObserverFrame3D ++ jmp present_frame ++engine_observer_skip: + invoke Pressed3D, 1Bh diff --git a/tools/generators/pack_darts.py b/tools/generators/pack_darts.py new file mode 100644 index 0000000..8e38f00 --- /dev/null +++ b/tools/generators/pack_darts.py @@ -0,0 +1,103 @@ +"""Exact clock-board geometry plus fixed-grid full-arm animation packing.""" +from pathlib import Path +import math +import random +import struct +from PIL import Image, ImageDraw, ImageFont + +ROOT = Path(__file__).resolve().parents[2] +ART = ROOT / "assets/sprites/darts" +OUT = ROOT / "assets/animations/darts" +SECTORS = [20, 1, 18, 4, 13, 6, 10, 15, 2, 17, 3, 19, 7, 16, 8, 11, 14, 9, 12, 5] + + +def pack(name, frames): + width, height = frames[0].size + payload = b"".join(frame.tobytes("raw", "BGRA") for frame in frames) + (OUT / name).write_bytes(struct.pack("<4s4I", b"ANM1", width, height, len(frames), len(payload)) + payload) + + +def board(): + rng = random.Random(301) + image = Image.new("RGBA", (512, 512)) + pixels = image.load() + for y in range(512): + for x in range(512): + dx, dy = x-256, 256-y + 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) + if 99 <= radius <= 107 or 162 <= radius: + color = (174, 37, 47) if red else (28, 112, 87) + if radius <= 15.9: + color = (28, 112, 87) + if radius <= 6.35: + color = (187, 37, 47) + elif radius < 210: + color = (20, 24, 25) + 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) + pixels[x, y] = tuple(max(0, min(255, c+grain)) for c in color) + (255,) + draw = ImageDraw.Draw(image) + wire = (171, 178, 173, 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) + 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) + 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") + return image + + + + +def main(): + ART.mkdir(parents=True, exist_ok=True) + OUT.mkdir(parents=True, exist_ok=True) + pack("board-v1.bin", [board()]) + wall = Image.open(ART / "lounge-wall-v1.png").convert("RGBA") + pack("lounge-wall-v1.bin", [wall.resize((512,512), Image.Resampling.LANCZOS)]) + anchors = [] + for name in ("arm-throw-v3", "dart-views-v1"): + source = ART / (name + ".png") + sheet = Image.open(source).convert("RGBA") + assert sheet.width == sheet.height + cell = sheet.width / 4 + frames = [sheet.crop(tuple(round(v*cell) for v in (x, y, x+1, y+1))) + .resize((256, 256), Image.Resampling.LANCZOS) for y in range(4) for x in range(4)] + assert all(f.getchannel("A").getextrema()[0] == 0 for f in frames) + assert len({f.tobytes() for f in frames}) == 16 + pack(name + ".bin", frames) + if name == "dart-views-v1": + for frame in frames: + metal = [(x,y) for y in range(256) for x in range(256) + if (lambda p: p[3] > 220 and min(p[:3]) > 50 + and max(p[:3])-min(p[:3]) < 60)(frame.getpixel((x,y)))] + tip_x = min(x for x,y in metal) + ys = sorted(y for x,y in metal if x <= tip_x+2) + anchors.append((tip_x, ys[len(ys)//2])) + (ROOT / "src_graphics/darts_art.inc").write_text( + "; Generated per-view needle anchors in the 256px source canvas.\n" + + "dtDartTipX3D dd " + ",".join(str(p[0]) for p in anchors) + "\n" + + "dtDartTipY3D dd " + ",".join(str(p[1]) for p in anchors) + "\n", encoding="ascii") + print("Packed exact 20-sector board; whole-arm animation uses one fixed canvas and scale.") + + +if __name__ == "__main__": + main() diff --git a/tools/generators/pack_fortune_session.py b/tools/generators/pack_fortune_session.py new file mode 100644 index 0000000..e4a8018 --- /dev/null +++ b/tools/generators/pack_fortune_session.py @@ -0,0 +1,63 @@ +"""Offline sprite-sheet compiler; runtime animation stays in MASM.""" +from pathlib import Path +import json +import struct +from PIL import Image + +ROOT = Path(__file__).resolve().parents[2] +ART = ROOT / "assets/sprites/fortune" +OUT = ROOT / "assets/animations/fortune" + + +def grid(name, columns, rows, size): + source = Image.open(ART / name).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(size, Image.Resampling.LANCZOS) + for y in range(rows) for x in range(columns)] + + +def tween(frames, loop=False): + # One shared canvas/scale, never fit moving hands independently per frame. + output = [] + for i, frame in enumerate(frames): + following = frames[(i+1) % len(frames)] if loop or i+1 < len(frames) else frame + output.extend(Image.blend(frame, following, step/4) for step in range(4)) + return output + + +def pack(name, frames): + width, height = frames[0].size + assert len(frames) <= 64 + assert all(frame.size == (width, height) for frame in frames) + payload = b"".join(frame.tobytes("raw", "BGRA") for frame in frames) + (OUT / (name + ".bin")).write_bytes( + struct.pack("<4s4I", b"ANM1", width, height, len(frames), len(payload)) + payload) + return {"file": name + ".bin", "width": width, "height": height, "frames": len(frames)} + + +def main(): + OUT.mkdir(parents=True, exist_ok=True) + body = grid("teller-actions-v1.png", 8, 5, (256, 256)) + cards = grid("tarot-symbols-v1.png", 6, 4, (96, 128)) + # Orientations are real upside-down artwork, not merely a different label. + faces = cards[:22] + [c.transpose(Image.Transpose.ROTATE_180) for c in cards[:22]] + [cards[22]] + room = Image.open(ART / "seated-room-v1.png").convert("RGBA").resize((640, 480), Image.Resampling.LANCZOS) + manifest = [pack("room-v1", [room]), + pack("shuffle-v1", tween(body[:16], loop=True)), + pack("deal-v1", tween(body[16:24])), + pack("gesture-v1", tween(body[24:32], loop=True)), + pack("orb-hands-v1", tween(body[32:40], loop=True)), + pack("orb-mist-v1", grid("crystal-mist-v1.png", 4, 4, (192, 192))), + pack("cards-v1", faces)] + (ART / "packed-v1.json").write_text(json.dumps(manifest, indent=2) + "\n", encoding="ascii") + preview = room.copy() + preview.alpha_composite(body[0].resize((280, 250)), (134, 32)) + for index in range(3): + preview.alpha_composite(cards[22].resize((72, 96)), (198+index*100, 302)) + preview.save(ROOT / "build/hall-expansion/fortune-session-art.png") + print("Packed 160 hand poses, 16 mist frames, 44 oriented faces, back and seated room.") + + +if __name__ == "__main__": + main() diff --git a/tools/tests/billiards_module.asm b/tools/tests/billiards_module.asm index b306647..d5af1c4 100644 --- a/tools/tests/billiards_module.asm +++ b/tools/tests/billiards_module.asm @@ -33,6 +33,8 @@ InitPaths3D PROTO ExitProcess PROTO :DWORD WriteActionSnapshot3D PROTO :DWORD include src_graphics/billiards_data.inc +.data +obsActive3D dd 0 .code include src_graphics/billiards.inc include tools/tests/pyramid_test.inc diff --git a/tools/tests/check_darts.py b/tools/tests/check_darts.py new file mode 100644 index 0000000..0f95480 --- /dev/null +++ b/tools/tests/check_darts.py @@ -0,0 +1,175 @@ +"""Fresh MASM dart scoring, mouse aiming, full-arm animation and 3D follow-camera proofs.""" +from pathlib import Path +import argparse +import hashlib +import re +import struct +import subprocess +from PIL import Image +from check_card_exit_build import Coff +from check_terminals import signature + +ROOT = Path(__file__).resolve().parents[2] +OUT = ROOT / "build/darts" + + +def read(path): + raw = (ROOT / path).read_bytes() + try: + return raw.decode("utf8").replace("\r\n", "\n") + except UnicodeDecodeError: + return raw.decode("cp1251").replace("\r\n", "\n") + + +def proc(text, name): + return re.search(rf"^{name} PROC\b.*?^{name} ENDP", text, re.M | re.S).group() + + +def expand(path): + return re.sub(r"^include ([^\n]+)$", lambda m: expand(m[1].strip().replace("\\", "/")), + read(path), flags=re.M) + + +def prepare(): + OUT.mkdir(parents=True, exist_ok=True) + code = expand("src_graphics/darts.inc") + code += "\n" + proc(read("src_graphics/platform_code.inc"), "WindowEvents3D") + for name in ("ResolveCitySurface3D", "DrawCityVertical3D"): + code += "\n" + proc(read("src_graphics/city_bins.inc"), name) + code += "\n" + proc(read("tools/tests/casino_actions_test.inc"), "WriteActionSnapshot3D") + code += "\nPlayCasinoSound3D PROC arg0:DWORD,arg1:DWORD,arg2:DWORD\n ret\nPlayCasinoSound3D ENDP\n" + code += "\n" + read("tools/tests/darts_test.inc") + code += "\n" + read("tools/tests/darts_ui.inc") + data = expand("src_graphics/darts_data.inc") + stages = [(0, t) for t in range(0, 320, 40)] + [(1, t) for t in range(0, 200, 40)] + stages += [(2, t) for t in range(0, 1200, 40)] + stages += [(3, t) for t in range(0, 1300, 80)] + [(4, t) for t in range(0, 751, 50)] + code += "\nDartsTestFrames PROC\n call ResetDarts3D\n mov dtAimX3D,0\n mov dtAimY3D,103\n mov dtHitX3D,0\n mov dtHitY3D,103\n" + for index, (state, time) in enumerate(stages): + data += f'\n.data\ndtFrame{index} db "build\\darts\\frame-{index:03}.bmp",0\n' + if state == 3 and stages[index-1][0] != 3: + code += " mov dtState3D,DT_FLIGHT\n call SettleDarts3D\n" + code += f" mov dtState3D,{state}\n mov dtTimer3D,{time}\n mov dtClock3D,{time}\n" + if state == 0: + code += f" mov dtHolding3D,1\n mov dtHoldTime3D,{time}\n" + code += f" call RenderDarts3D\n invoke WriteActionSnapshot3D,OFFSET dtFrame{index}\n" + data += '\n.data\ndtScoreFrame db "build\\darts\\scoreboard.bmp",0\n' + code += " mov dtScoreOpen3D,1\n call RenderDarts3D\n invoke WriteActionSnapshot3D,OFFSET dtScoreFrame\n" + code += " call ResetDarts3D\n mov dtHolding3D,1\n mov dtCursorX3D,320*256\n mov dtCursorY3D,214*256-103*DT_FOCAL*256/DT_WALL\n" + for index in range(80): + data += f'\n.data\ndtSwayFrame{index} db "build\\darts\\sway-{index:03}.bmp",0\n' + code += f" mov dtClock3D,{index*40}\n mov dtHoldTime3D,{index*40}\n call DartsAim3D\n call RenderDarts3D\n invoke WriteActionSnapshot3D,OFFSET dtSwayFrame{index}\n" + code += " ret\nDartsTestFrames ENDP\n" + code = code.replace(":SDWORD", ":DWORD") + data += "\n.data\nactionBmpHeader db 42h,4Dh\n dd 1228854\n dw 0,0\n dd 54,40,640,-480\n dw 1,32\n dd 0,1228800,0,0,0,0\n" + own_funcs = set(re.findall(r"^(\w+) PROC", code, re.M)) + own_data = set(re.findall(r"^(\w+)\s+(?:db|dw|dd|REAL4|REAL8|AnimPack3D|LABEL)\b", data + "\n" + code, re.M)) + source = "\n".join(read(str(p.relative_to(ROOT))) for p in + [ROOT / "poker.asm", ROOT / "casino3d.inc", ROOT / "casino3d_data.inc", + *sorted(p for p in (ROOT / "src_graphics").glob("*.inc") if p.name != "renderer.inc")]) + structures = [re.search(rf"^{name} STRUCT\b.*?^{name} ENDS", source, re.M | re.S).group() + for name in ("AnimPack3D", "BJBot3D")] + types = {name: kind for name, kind in re.findall(r"^(\w+)\s+(AnimPack3D|BJBot3D)\b", source, re.M)} + types.update({name: {"db": "BYTE", "dw": "WORD", "dd": "DWORD"}[kind.lower()] + for name, kind in re.findall(r"^(\w+)\s+(db|dw|dd)\b", source, re.M | re.I)}) + types.update({name: kind.upper() for name, kind in + re.findall(r"^(\w+)\s+(REAL4|REAL8)\b", source, re.M | re.I)}) + coff = Coff(ROOT / "build/graphics/poker.obj") + helper = bytearray(coff.data) + symbol_at = struct.unpack_from(" 0 and re.fullmatch(r"[A-Za-z_]\w*", name) and storage in (2, 3): + secname = helper[20+(section-1)*40:28+(section-1)*40].rstrip(b"\0") + if secname in (b".data", b".bss", b".const"): + helper[pos+16] = 3 if name in own_data else 2 + if name not in own_data: + externs.append(f"EXTERN {name}:{types.get(name, 'DWORD')}") + match = re.fullmatch(r"_(\w+)@(\d+)", name) + if match and kind == 0x20 and section > 0: + helper[pos+16] = 3 if match[1] in own_funcs else 2 + if match[1] not in own_funcs: + protos[match[1]] = int(match[2]) // 4 + for name, params in re.findall(r"^(\w+)\s+PROTO([^\n]*)", source, re.M): + protos.setdefault(name, params.count(":DWORD")) + for name in own_funcs: + protos[name] = len(re.findall(r":S?DWORD", proc(code, name).splitlines()[0])) + constants = dict(re.findall(r"^(\w+)\s+equ\s+([^;\n]+)", source, re.M)) + used = set(re.findall(r"^(\w+) equ", data, re.M)) + ordered = [] + def constant(name): + if name not in constants or name in used: + return + used.add(name) + for dep in re.findall(r"\b[A-Za-z_]\w*", constants[name]): + constant(dep) + ordered.append(f"{name} equ {constants[name]}") + for name in re.findall(r"\b[A-Za-z_]\w*", code + data): + constant(name) + 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"]*count) if count else "") for name, count in protos.items()) + data = re.sub(r"^\w+ PROTO[^\n]*\n", "", data, flags=re.M) + (OUT / "helpers.obj").write_bytes(helper) + (OUT / "module.asm").write_text(header + "\n" + data + "\n.code\n" + code + "\nend DartsTestStart\n", encoding="cp1251") + return {name for name in own_funcs if not name.startswith(("DartsTest", "DartsUi"))} - { + "PlayCasinoSound3D", "WriteActionSnapshot3D"} + + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--parity", action="store_true") + parser.add_argument("--interactive", action="store_true") + args = parser.parse_args() + functions = prepare() + if args.parity: + tested, shipping = Coff(OUT / "module.obj"), Coff(ROOT / "build/graphics/poker.obj") + for name in functions: + symbol = next(key for key in tested.functions if key.startswith("_" + name + "@")) + assert signature(tested, symbol, tail_branches=True) == signature(shipping, symbol, tail_branches=True), name + 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.", + "/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" + entry = "DartsUiStart" if args.interactive else "DartsTestStart" + subprocess.run([str(masm / "bin/link.exe"), "/nologo", "/SUBSYSTEM:WINDOWS", f"/ENTRY:{entry}@0", + f"/LIBPATH:{sdk}", f"/LIBPATH:{masm/'lib'}", f"/OUT:build/darts/{target}.exe", + "build/darts/module.obj", "build/darts/helpers.obj", "kernel32.lib", "user32.lib", + "gdi32.lib", "winmm.lib", "ws2_32.lib"], cwd=ROOT, check=True) + def profile(): + return {str(p): hashlib.sha256(p.read_bytes()).digest() for p in (ROOT / "runtime").rglob("*") + if p.is_file()} + before = profile() + run = subprocess.run([str(OUT / (target + ".exe"))], cwd=ROOT, timeout=900 if args.interactive else 180) + assert run.returncode == 0, f"Native darts assertion: {run.returncode}" + assert profile() == before, "Darts changed persistent profile files" + if args.interactive: + print("PASS: interactive native scene closed without changing the profile") + return + frames = [Image.open(p).convert("RGB") for p in sorted(OUT.glob("frame-*.bmp"))] + assert len(frames) >= 60 and len({f.tobytes() for f in frames}) >= 40 + assert all(len(f.getcolors(640*480)) > 80 for f in frames) + frames[0].save(OUT / "throw.gif", save_all=True, append_images=frames[1:], duration=40, loop=0) + sheet = Image.new("RGB", (640*3, 480*2)) + for slot, index in enumerate((0, 10, 22, 36, 48, len(frames)-1)): + sheet.paste(frames[index], ((slot % 3)*640, (slot // 3)*480)) + sheet.save(OUT / "contact.png") + sway = [Image.open(p).convert("RGB") for p in sorted(OUT.glob("sway-*.bmp"))] + assert len(sway) == 80 and len({f.tobytes() for f in sway}) > 24 + sway[0].save(OUT / "sway.gif", save_all=True, append_images=sway[1:], duration=40, loop=0) + triples, = struct.unpack(" 45000, (name, "chamber must use its own blue palette", blue) modal = Image.open(OUT/"fortune-unavailable.bmp").convert("RGB").crop((20,65,620,100)) assert len(modal.getcolors(600*35)) >= 2, "native fortune dialogue status must be drawn" + # These are frames rendered by the actual x86 procedures, not a Python scene simulation. + motion = [Image.open(OUT/f"fortune-session-motion-{i:02}.bmp").convert("RGB") for i in range(64)] + assert len({im.crop((134,32,414,282)).tobytes() for im in motion}) >= 48 + motion[0].save(OUT/"fortune-session-shuffle.gif",save_all=True, + append_images=motion[1:],duration=25,loop=0) + stages = ("deal-one","deal-two","deal-three","flip-one","explain-one", + "flip-two","explain-two","flip-three","explain-three","summary") + frames = [Image.open(OUT/f"fortune-session-{s}.bmp").convert("RGB") for s in stages] + 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) + 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() + magic,w,h,count,size = struct.unpack_from("<4s4I",data) + assert magic == b"ANM1" and len(data) == size+20 and size == w*h*4*count + assert count <= 64 and size <= 32*1024*1024 + if count > 1: + pixels = [data[20+i*w*h*4:20+(i+1)*w*h*4] for i in range(count)] + assert len(set(pixels)) >= count-3, (name,"animation frames must differ") + if name == "cards": + for i in range(22): + upright = Image.frombytes("RGBA",(w,h),pixels[i],"raw","BGRA") + reversed_card = Image.frombytes("RGBA",(w,h),pixels[i+22],"raw","BGRA") + assert upright.transpose(Image.Transpose.ROTATE_180).tobytes() == reversed_card.tobytes() entrance = Image.open(OUT / (("old-seam-" if args.prove_seam_regression else "") + "upper-entrance-open.bmp")).convert("RGB").crop((200,150,300,350)) dominant = max(count for count,_ in entrance.getcolors(20000))/20000 @@ -308,8 +344,6 @@ def main(): x0,y0,x1,y1 = sprite.getbbox() assert 0 < x0 < x1 < w and 0 < y0 < y1 < h, "uncropped musician cutout" print("PASS: native stairs/doorways, automatic door, tickets, gait, portal, secret passage, bar camera and fortune dialogue") - if args.with_fortune_ai: - print("PASS: actual x86 game input/IPC/rendering with the x64 local model, response and child cleanup") if __name__ == "__main__": diff --git a/tools/tests/check_observer.py b/tools/tests/check_observer.py new file mode 100644 index 0000000..6ef4c10 --- /dev/null +++ b/tools/tests/check_observer.py @@ -0,0 +1,148 @@ +"""Compile fresh native spectator/game code and run autonomous matches, not mock results.""" +from pathlib import Path +import argparse +import hashlib +import re +import struct +import subprocess +from PIL import Image +from check_card_exit_build import Coff +from check_terminals import signature + +ROOT = Path(__file__).resolve().parents[2] +OUT = ROOT / "build/observer" + + +def read(path): + raw = (ROOT / path).read_bytes() + try: + return raw.decode("utf8").replace("\r\n", "\n") + except UnicodeDecodeError: + return raw.decode("cp1251").replace("\r\n", "\n") + + +def proc(text, name): + return re.search(rf"^{name} PROC\b.*?^{name} ENDP", text, re.M | re.S).group() + + +def expand(path): + return re.sub(r"^include ([^\n]+)$", lambda m: expand(m[1].strip().replace("\\", "/")), + read(path), flags=re.M) + + +def prepare(): + OUT.mkdir(parents=True, exist_ok=True) + code = "\n".join(expand("src_graphics/" + name + ".inc") for name in + ("observer", "native_poker", "native_durak", "native_blackjack", "pyramid_play", "eightball", + "card_tables_render", "durak_render")) + for path, names in (("platform_code", ("SaveProgress3D",)), + ("npc_contracts", ("NpcRecordRound3D",)), + ("charms", ("TriggerCharmLossProtection",)), + ("card_table_input", ("HandleCardExit3D", "RequestCardExit3D", "LeaveCardTable3D", "TableBusy3D"))): + code += "\n" + "\n".join(proc(read("src_graphics/" + path + ".inc"), name) for name in names) + code += "\n" + proc(read("tools/tests/casino_actions_test.inc"), "WriteActionSnapshot3D") + code += "\n" + read("tools/tests/durak_test.inc") + code += "\nPlayCasinoSound3D PROC arg0:DWORD,arg1:DWORD,arg2:DWORD\n ret\nPlayCasinoSound3D ENDP\n" + # Stop before any persistence side effect even if a tested observer barrier regresses. + for name in ("RefreshTerminalAchievements3D", "NpcEnsureJournal3D"): + code += f"\n{name} PROC\n invoke ExitProcess,8402\n ret\n{name} ENDP\n" + code += "\n" + read("tools/tests/observer_test.inc") + code = code.replace(":SDWORD", ":DWORD") + data = read("src_graphics/observer_data.inc") + data += "\n.data\nactionBmpHeader db 42h,4Dh\n dd 1228854\n dw 0,0\n dd 54,40,640,-480\n dw 1,32\n dd 0,1228800,0,0,0,0\n" + own_funcs = set(re.findall(r"^(\w+) PROC", code, re.M)) + own_data = set(re.findall(r"^(\w+)\s+(?:db|dd)\b", data + "\n" + code, re.M)) + source = "\n".join(read(str(p.relative_to(ROOT))) for p in + [ROOT / "poker.asm", ROOT / "casino3d.inc", ROOT / "casino3d_data.inc", + *sorted(p for p in (ROOT / "src_graphics").glob("*.inc") if p.name != "renderer.inc")]) + structures = [re.search(rf"^{name} STRUCT\b.*?^{name} ENDS", source, re.M | re.S).group() + for name in ("AnimPack3D", "BJBot3D")] + types = {name: kind for name, kind in re.findall(r"^(\w+)\s+(AnimPack3D|BJBot3D)\b", source, re.M)} + types.update({name: {"db": "BYTE", "dw": "WORD", "dd": "DWORD"}[kind.lower()] + for name, kind in re.findall(r"^(\w+)\s+(db|dw|dd)\b", source, re.M | re.I)}) + types.update({name: kind.upper() for name, kind in + re.findall(r"^(\w+)\s+(REAL4|REAL8)\b", source, re.M | re.I)}) + coff = Coff(ROOT / "build/graphics/poker.obj") + helper = bytearray(coff.data) + symbol_at = struct.unpack_from(" 0 and re.fullmatch(r"[A-Za-z_]\w*", name) and storage in (2, 3): + secname = helper[20+(section-1)*40:28+(section-1)*40].rstrip(b"\0") + if secname in (b".data", b".bss", b".const"): + helper[pos+16] = 3 if name in own_data else 2 + if name not in own_data: + externs.append(f"EXTERN {name}:{types.get(name, 'DWORD')}") + match = re.fullmatch(r"_(\w+)@(\d+)", name) + if match and kind == 0x20 and section > 0: + helper[pos+16] = 3 if match[1] in own_funcs else 2 + if match[1] not in own_funcs: + protos[match[1]] = int(match[2]) // 4 + for name, params in re.findall(r"^(\w+)\s+PROTO([^\n]*)", source, re.M): + protos.setdefault(name, params.count(":DWORD")) + for name in own_funcs: + protos[name] = len(re.findall(r":S?DWORD", proc(code, name).splitlines()[0])) + constants = dict(re.findall(r"^(\w+)\s+equ\s+([^;\n]+)", source, re.M)) + used = set(re.findall(r"^(\w+) equ", data, re.M)) + ordered = [] + def constant(name): + if name not in constants or name in used: + return + used.add(name) + for dep in re.findall(r"\b[A-Za-z_]\w*", constants[name]): + constant(dep) + ordered.append(f"{name} equ {constants[name]}") + for name in re.findall(r"\b[A-Za-z_]\w*", code + data): + constant(name) + 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"]*count) if count else "") for name, count in protos.items()) + data = re.sub(r"^\w+ PROTO[^\n]*\n", "", data, flags=re.M) + (OUT / "helpers.obj").write_bytes(helper) + (OUT / "module.asm").write_text(header + "\n" + data + "\n.code\n" + code + "\nend ObserverTestStart\n", encoding="cp1251") + return {name for name in own_funcs if not name.startswith("DurakTest") and name != "RunDurakTest3D"} - { + "PlayCasinoSound3D", "WriteActionSnapshot3D", "ObserverTestStart", + "RefreshTerminalAchievements3D", "NpcEnsureJournal3D"} + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--parity", action="store_true") + args = parser.parse_args() + functions = prepare() + if args.parity: + tested, shipping = Coff(OUT / "module.obj"), Coff(ROOT / "build/graphics/poker.obj") + for name in functions: + symbol = next(key for key in tested.functions if key.startswith("_" + name + "@")) + assert signature(tested, symbol, tail_branches=True) == signature(shipping, symbol, tail_branches=True), name + print("PASS: native observer and game code match the shipping object") + return + masm = Path("F:/MASM32/masm32") + subprocess.run([str(masm / "bin/ml.exe"), "/c", "/coff", "/Cp", "/nologo", "/I.", + "/Fobuild/observer/module.obj", "build/observer/module.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:ObserverTestStart@0", + f"/LIBPATH:{sdk}", f"/LIBPATH:{masm/'lib'}", "/OUT:build/observer/module.exe", + "build/observer/module.obj", "build/observer/helpers.obj", "kernel32.lib", "user32.lib", + "gdi32.lib", "winmm.lib", "ws2_32.lib"], cwd=ROOT, check=True) + def profile(): + return {str(p): hashlib.sha256(p.read_bytes()).digest() for p in (ROOT / "runtime").rglob("*") + if p.is_file()} + before = profile() + run = subprocess.run([str(OUT / "module.exe")], cwd=ROOT, timeout=180) + assert run.returncode == 0, f"Native observer assertion: {run.returncode}" + assert profile() == before, "Observation changed persistent profile files" + sheet = Image.new("RGB", (1280, 480*4)) + for i, name in enumerate(("menu", "poker", "blackjack", "durak", "transfer", "pyramid", "eight")): + frame = Image.open(OUT / (name + ".bmp")).convert("RGB") + assert len(frame.getcolors(640*480)) >= (4 if name == "menu" else 500), name + frame.save(OUT / (name + ".png")) + sheet.paste(frame, ((i % 2)*640, (i // 2)*480)) + sheet.save(OUT / "contact.png") + print("PASS: 80 autonomous card matches, both billiard seats, pause, profile restore and no persistent writes") + + +if __name__ == "__main__": + main() diff --git a/tools/tests/darts_hall_test.inc b/tools/tests/darts_hall_test.inc new file mode 100644 index 0000000..ae38a58 --- /dev/null +++ b/tools/tests/darts_hall_test.inc @@ -0,0 +1,42 @@ +.data +dtHallSnapshot db "build\hall-expansion\darts-hall.bmp",0 +.code +ExpansionDartsTests PROC + mov expansionStage,8510 + mov hallLevel3D,0 + mov engineMode,MODE_WALK + mov worldMap3D,OFFSET casinoMap + mov playerX,29*FP_1 + mov playerY,28*FP_1 + mov playerAngle,0 + invoke CanWalk3D,playerX,playerY + test eax,eax + jz dt_hall_fail + call UpdateInteriorEye3D + call ExpansionFrame + invoke ExpansionSnapshot,OFFSET dtHallSnapshot + call CheckHallInteractions3D + cmp interactTargetId,INTERACT_DARTS + jne dt_hall_fail + call TriggerHallInteraction3D + test eax,eax + jz dt_hall_fail + cmp engineMode,MODE_DARTS + jne dt_hall_fail + cmp dtError3D,0 + jne dt_hall_fail + cmp dtScores3D,500 + jne dt_hall_fail + cmp dtScores3D[4],500 + jne dt_hall_fail + cmp playerX,29*FP_1 + jne dt_hall_fail + cmp playerY,28*FP_1 + jne dt_hall_fail + call FreeDarts3D + mov engineMode,MODE_WALK + ret +dt_hall_fail: + invoke ExitProcess,8510 + ret +ExpansionDartsTests ENDP diff --git a/tools/tests/darts_test.inc b/tools/tests/darts_test.inc new file mode 100644 index 0000000..14f4386 --- /dev/null +++ b/tools/tests/darts_test.inc @@ -0,0 +1,374 @@ +.data +dtVectors3D LABEL DWORD + dd 0,53,20,1 + dd 0,103,60,3 + dd 0,166,40,2 + dd 16,50,1,1 + dd 32,98,3,3 + dd 51,158,2,2 + dd 31,43,18,1 + dd 61,83,54,3 + dd 98,134,36,2 + dd 43,31,4,1 + dd 83,61,12,3 + dd 134,98,8,2 + dd 50,16,13,1 + dd 98,32,39,3 + dd 158,51,26,2 + dd 53,0,6,1 + dd 103,0,18,3 + dd 166,0,12,2 + dd 50,-16,10,1 + dd 98,-32,30,3 + dd 158,-51,20,2 + dd 43,-31,15,1 + dd 83,-61,45,3 + dd 134,-98,30,2 + dd 31,-43,2,1 + dd 61,-83,6,3 + dd 98,-134,4,2 + dd 16,-50,17,1 + dd 32,-98,51,3 + dd 51,-158,34,2 + dd 0,-53,3,1 + dd 0,-103,9,3 + dd 0,-166,6,2 + dd -16,-50,19,1 + dd -32,-98,57,3 + dd -51,-158,38,2 + dd -31,-43,7,1 + dd -61,-83,21,3 + dd -98,-134,14,2 + dd -43,-31,16,1 + dd -83,-61,48,3 + dd -134,-98,32,2 + dd -50,-16,8,1 + dd -98,-32,24,3 + dd -158,-51,16,2 + dd -53,0,11,1 + dd -103,0,33,3 + dd -166,0,22,2 + dd -50,16,14,1 + dd -98,32,42,3 + dd -158,51,28,2 + dd -43,31,9,1 + dd -83,61,27,3 + dd -134,98,18,2 + dd -31,43,12,1 + dd -61,83,36,3 + dd -98,134,24,2 + dd -16,50,5,1 + dd -32,98,15,3 + dd -51,158,10,2 + dd 0,0,50,2 + dd 0,6,50,2 + dd 0,10,25,1 + dd 0,16,20,1 + dd 0,190,0,0 + dd 2147483647,0,0,0 + dd -2147483648,0,0,0 +dtVectorCount3D equ 67 +dtTestTriples3D dd 0 +dtTestMetricPath db "build\darts\aim-metrics.bin",0 +.code +DartsTestWriteMetrics PROC uses ebx + local written:DWORD + invoke CreateFileA,OFFSET dtTestMetricPath,40000000h,0,0,2,80h,0 + cmp eax,-1 + je dt_test_metric_fail + mov ebx,eax + invoke WriteFile,ebx,OFFSET dtTestTriples3D,4,ADDR written,0 + invoke CloseHandle,ebx + ret +dt_test_metric_fail: + invoke ExitProcess,8505 + ret +DartsTestWriteMetrics ENDP + +DartsTestStart PROC uses ebx esi edi + local legs:DWORD + call InitPaths3D + lea esi,dtVectors3D + mov ebx,dtVectorCount3D +dt_test_vector: + invoke DartsScore3D,[esi],[esi+4] + cmp eax,[esi+8] + jne dt_test_fail + cmp edx,[esi+12] + jne dt_test_fail + add esi,16 + dec ebx + jnz dt_test_vector + mov playerX,123456 + mov playerY,789012 + mov bankroll,12345 + call ResetDarts3D + mov dtCursorX3D,320*256 + mov dtCursorY3D,214*256 + mov dtClock3D,384 + mov dtHolding3D,1 + mov dtHoldTime3D,0 + call DartsAim3D + cmp dtAimX3D,10 + jl dt_test_fail + cmp dtAimX3D,13 + jg dt_test_fail + mov ebx,dtAimX3D + mov dtHoldTime3D,4000 + call DartsAim3D + mov eax,dtAimX3D + sub eax,ebx + cmp eax,8 + jne dt_test_fail + ; Fine mouse motion must survive the hall's coarser 320px UI coordinates. + mov engineMode,MODE_DARTS + mov viewWidth3D,2560 + mov viewHeight3D,1440 + mov viewLeft3D,0 + mov viewTop3D,0 + invoke WindowEvents3D,0,200h,0,02820500h + call DartsAim3D + mov ebx,dtAimX3D + invoke WindowEvents3D,0,200h,0,02820502h + call DartsAim3D + cmp dtAimX3D,ebx + jle dt_test_fail + cmp mouseX3D,160 + jne dt_test_fail + mov ebx,dtAimY3D + invoke WindowEvents3D,0,200h,0,02840502h + call DartsAim3D + cmp dtAimY3D,ebx + jge dt_test_fail + cmp mouseY3D,107 + jne dt_test_fail + call ResetDarts3D + call DartsCamera3D + invoke DartsProject3D,-248,0,DT_WALL + mov ebx,eax + invoke DartsProject3D,248,0,DT_WALL + sub eax,ebx + cmp eax,170 + jl dt_test_fail + cmp eax,180 + jg dt_test_fail + ; Camping at the centre of T20 no longer guarantees a triple every time. + mov dtCursorX3D,320*256 + mov dtCursorY3D,214*256-103*DT_FOCAL*256/DT_WALL + mov dtClock3D,0 + mov dtTestTriples3D,0 + mov edi,100 +dt_test_sway_samples: + call DartsAim3D + invoke DartsScore3D,dtAimX3D,dtAimY3D + cmp eax,60 + jne @F + inc dtTestTriples3D +@@: + add dtClock3D,16 + dec edi + jnz dt_test_sway_samples + cmp dtTestTriples3D,10 + jb dt_test_fail + cmp dtTestTriples3D,60 + ja dt_test_fail + call DartsTestWriteMetrics + mov dtState3D,DT_FLIGHT + mov dtTimer3D,400 + mov deltaTime3D,16 + mov pressedKeys3D[9],1 + call HandleDarts3D + cmp dtScoreOpen3D,1 + jne dt_test_fail + cmp dtTimer3D,400 + jne dt_test_fail + mov pressedKeys3D[9],1 + call HandleDarts3D + cmp dtScoreOpen3D,0 + jne dt_test_fail + cmp dtTimer3D,416 + jne dt_test_fail + call ResetDarts3D + mov dtHitX3D,0 + mov dtHitY3D,103 + mov dtRemaining3D,59 + mov dtVisitStart3D,100 + mov dtState3D,DT_FLIGHT + call SettleDarts3D + cmp dtRemaining3D,100 + jne dt_test_fail + cmp dtBust3D,1 + jne dt_test_fail + cmp dtCount3D,1 + jne dt_test_fail + call SettleDarts3D + cmp dtCount3D,1 + jne dt_test_fail + call ResetDarts3D + mov dtHitX3D,0 + mov dtHitY3D,166 + mov dtRemaining3D,40 + mov dtVisitStart3D,40 + mov dtState3D,DT_FLIGHT + call SettleDarts3D + cmp dtRemaining3D,0 + jne dt_test_fail + mov dtTimer3D,1300 + mov deltaTime3D,16 + call HandleDarts3D + cmp dtState3D,DT_WIN + jne dt_test_fail + call ResetDarts3D + mov dtHitY3D,166 + mov dtRemaining3D,41 + mov dtVisitStart3D,41 + mov dtState3D,DT_FLIGHT + call SettleDarts3D + cmp dtRemaining3D,1 + jne dt_test_fail + cmp dtBust3D,0 + jne dt_test_fail + mov dtCount3D,3 + mov dtState3D,DT_RETURN + mov dtTimer3D,750 + call HandleDarts3D + cmp dtCount3D,0 + jne dt_test_fail + cmp dtVisit3D,2 + jne dt_test_fail + cmp dtActor3D,1 + jne dt_test_fail + cmp dtRemaining3D,500 + jne dt_test_fail + cmp dtScores3D,1 + jne dt_test_fail + cmp dtScores3D[4],500 + jne dt_test_fail + call ResetDarts3D + mov engineMode,MODE_DARTS + mov viewWidth3D,640 + mov viewHeight3D,480 + mov viewLeft3D,0 + mov viewTop3D,0 + mov mouseClick3D,0 + invoke WindowEvents3D,0,200h,0,00D60140h + cmp mouseClick3D,0 + jne dt_test_fail + invoke WindowEvents3D,0,201h,1,00D60140h + call HandleDarts3D + cmp dtState3D,DT_AIM + jne dt_test_fail + cmp dtHolding3D,1 + jne dt_test_fail + invoke WindowEvents3D,0,202h,0,00D60140h + call HandleDarts3D + cmp dtState3D,DT_RELEASE + jne dt_test_fail + cmp dtThrows3D,1 + jne dt_test_fail + mov ebx,dtHitX3D + mov esi,dtHitY3D + invoke WindowEvents3D,0,200h,0,00640064h + call HandleDarts3D + cmp dtHitX3D,ebx + jne dt_test_fail + cmp dtHitY3D,esi + jne dt_test_fail + call ResetDarts3D + mov dtHitX3D,80 + mov dtHitY3D,103 + mov dtState3D,DT_FLIGHT + mov dtTimer3D,0 + call DartsCamera3D + cmp dtCamZ3D,0 + jne dt_test_fail + mov dtTimer3D,600 + call DartsCamera3D + cmp dtCamZ3D,DT_WALL/2-160 + jne dt_test_fail + cmp dtYawSin3D,50000 + jl dt_test_fail + mov dtTimer3D,1200 + call DartsCamera3D + cmp dtCamZ3D,DT_WALL-420 + jne dt_test_fail + invoke DartsProject3D,80,103,DT_WALL + cmp eax,320 + jl dt_test_fail + cmp eax,420 + jg dt_test_fail + cmp edx,120 + jl dt_test_fail + cmp edx,214 + jg dt_test_fail + cmp dtYawSin3D,12000 + jg dt_test_fail + ; Actual bot input ignores the cursor and automatically releases a dart. + call ResetDarts3D + mov dtActor3D,1 + mov mouseX3D,0 + mov mouseY3D,0 + mov deltaTime3D,1600 + call HandleDarts3D + cmp dtState3D,DT_RELEASE + jne dt_test_fail + cmp dtThrows3D,1 + jne dt_test_fail + cmp dtScores3D,500 + jne dt_test_fail + ; Complete a real match through normal phase/visit transitions. + mov legs,20 +dt_test_another_match: + call ResetDarts3D + mov edi,5000 +dt_test_match: + cmp dtState3D,DT_WIN + je dt_test_match_done + cmp dtState3D,DT_AIM + jne dt_test_match_tick + cmp dtActor3D,0 + jne dt_test_match_tick + call DartsBotAim3D + mov eax,dtBotAimX3D + mov dtAimX3D,eax + mov eax,dtBotAimY3D + mov dtAimY3D,eax + call ThrowDarts3D +dt_test_match_tick: + mov deltaTime3D,1400 + call HandleDarts3D + cmp dtScores3D,500 + ja dt_test_fail + cmp dtScores3D[4],500 + ja dt_test_fail + dec edi + jnz dt_test_match + jmp dt_test_fail +dt_test_match_done: + mov eax,dtWinner3D + cmp dtScores3D[eax*4],0 + jne dt_test_fail + dec legs + jnz dt_test_another_match + cmp playerX,123456 + jne dt_test_fail + cmp playerY,789012 + jne dt_test_fail + cmp bankroll,12345 + jne dt_test_fail + call LoadDarts3D + test eax,eax + jz dt_test_assets + call DartsTestFrames + call FreeDarts3D + xor eax,eax + jmp dt_test_exit +dt_test_assets: + mov eax,8502 + jmp dt_test_exit +dt_test_fail: + mov eax,8501 +dt_test_exit: + invoke ExitProcess,eax +DartsTestStart ENDP + diff --git a/tools/tests/darts_ui.inc b/tools/tests/darts_ui.inc new file mode 100644 index 0000000..041a636 --- /dev/null +++ b/tools/tests/darts_ui.inc @@ -0,0 +1,64 @@ +; Interactive fixture reuses the actual native scene, window events and presenter. +.code +DartsUiStart PROC + call InitPaths3D + call Create3DWindow + test eax,eax + jz dt_ui_fail + invoke SetWindowLongA,h3DWnd,-4,OFFSET WindowEvents3D + call ToggleFullscreen3D + call BeginDarts3D + call RenderDarts3D + invoke ShowWindow,h3DWnd,1 + call Present3D + invoke SetForegroundWindow,h3DWnd + invoke GetTickCount + mov previousTick3D,eax +dt_ui_loop: + invoke PeekMessageA,OFFSET msg3D,0,0,0,1 + test eax,eax + jz dt_ui_frame + cmp dword ptr msg3D[4],12h + je dt_ui_close + invoke TranslateMessage,OFFSET msg3D + invoke DispatchMessageA,OFFSET msg3D + jmp dt_ui_loop +dt_ui_frame: + invoke GetTickCount + mov frameStart3D,eax + sub eax,previousTick3D + cmp eax,50 + jbe @F + mov eax,50 +@@: + mov deltaTime3D,eax + mov eax,frameStart3D + mov previousTick3D,eax + invoke GetForegroundWindow + cmp eax,h3DWnd + jne dt_ui_idle + invoke Pressed3D,1Bh + test eax,eax + jnz dt_ui_close + invoke Pressed3D,7Ah + test eax,eax + jz @F + call ToggleFullscreen3D +@@: + call HandleDarts3D + call RenderDarts3D + call Present3D +dt_ui_idle: + invoke Sleep,16 + jmp dt_ui_loop +dt_ui_close: + call FreeDarts3D + invoke ReleaseDC,h3DWnd,h3DDC + invoke DestroyWindow,h3DWnd +dt_ui_exit: + invoke ExitProcess,0 + ret +dt_ui_fail: + invoke ExitProcess,8503 + ret +DartsUiStart ENDP diff --git a/tools/tests/fortune_dialog_test.inc b/tools/tests/fortune_dialog_test.inc index 47ede9a..66d3ef9 100644 --- a/tools/tests/fortune_dialog_test.inc +++ b/tools/tests/fortune_dialog_test.inc @@ -1,112 +1,243 @@ .data -fortuneLiveQuestion db 209,242,238,232,242,32,235,232,32,237,224,247,224,242,252,32,243,247,232,242,252,241,255,32,240,232,241,238,226,224,237,232,254,63,0 -fortuneLiveShot db "build\hall-expansion\fortune-dialog-answer.bmp",0 -fortuneLiveBank dd 0 +fortuneSessionBank dd 0 +fortuneSessionPlayer dd 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 +fortuneSessionShot0 db "build\hall-expansion\fortune-session-input.bmp",0 +fortuneSessionShot1 db "build\hall-expansion\fortune-session-shuffle.bmp",0 +fortuneSessionShot2 db "build\hall-expansion\fortune-session-deal-one.bmp",0 +fortuneSessionShot3 db "build\hall-expansion\fortune-session-deal-two.bmp",0 +fortuneSessionShot4 db "build\hall-expansion\fortune-session-deal-three.bmp",0 +fortuneSessionShot5 db "build\hall-expansion\fortune-session-flip-one.bmp",0 +fortuneSessionShot6 db "build\hall-expansion\fortune-session-explain-one.bmp",0 +fortuneSessionShot7 db "build\hall-expansion\fortune-session-flip-two.bmp",0 +fortuneSessionShot8 db "build\hall-expansion\fortune-session-explain-two.bmp",0 +fortuneSessionShot9 db "build\hall-expansion\fortune-session-flip-three.bmp",0 +fortuneSessionShot10 db "build\hall-expansion\fortune-session-explain-three.bmp",0 +fortuneSessionShot11 db "build\hall-expansion\fortune-session-summary.bmp",0 +fortuneSessionShot12 db "build\hall-expansion\fortune-session-done.bmp",0 +fortuneSessionShot13 db "build\hall-expansion\fortune-session-orb-rise.bmp",0 +fortuneSessionShot14 db "build\hall-expansion\fortune-session-orb-mist.bmp",0 +fortuneSessionShot15 db "build\hall-expansion\fortune-session-orb-final.bmp",0 +fortuneSessionShots dd OFFSET fortuneSessionShot0,OFFSET fortuneSessionShot1,OFFSET fortuneSessionShot2,OFFSET fortuneSessionShot3 + dd OFFSET fortuneSessionShot4,OFFSET fortuneSessionShot5,OFFSET fortuneSessionShot6,OFFSET fortuneSessionShot7 + dd OFFSET fortuneSessionShot8,OFFSET fortuneSessionShot9,OFFSET fortuneSessionShot10,OFFSET fortuneSessionShot11 + dd OFFSET fortuneSessionShot12 .code -FortuneDialogNativeTests PROC uses esi edi +FortuneSessionStepTest PROC + add animationClock3D,100 + call UpdateFortuneSession3D + ret +FortuneSessionStepTest ENDP +FortuneDialogNativeTests PROC uses ebx esi edi mov expansionStage,80 - mov hallLevel3D,3 - mov engineMode,MODE_WALK - mov worldMap3D,OFFSET fortuneRoomMap3D - mov floorTexture3D,134 - mov playerX,FP_1*155/10 - mov playerY,11*FP_1 - mov playerAngle,256 - call UpdateInteriorEye3D - call ExpansionFrame mov eax,bankroll - mov fortuneLiveBank,eax + mov fortuneSessionBank,eax + mov eax,playerX + mov fortuneSessionPlayer,eax call BeginFortuneDialog3D + cmp fortuneSessionShuffle3D.frames,64 + jne fortune_session_fail + cmp fortuneSessionDeal3D.frames,32 + jne fortune_session_fail + cmp fortuneSessionCards3D.frames,45 + jne fortune_session_fail + cmp fortuneProcess3D,0 + jne fortune_session_fail + cmp fortuneRead3D,0 + jne fortune_session_fail + invoke WindowEvents3D,0,102h,'1',0 + cmp fortuneSubMode3D,FORTUNE_SUBMODE_TAROT + jne fortune_session_fail + ; Lower edge of the question box is not an Exit-button hit target. + mov mouseX3D,260 + mov mouseY3D,222 + mov mouseClick3D,1 + call HandleFortuneDialog3D cmp engineMode,MODE_FORTUNE - jne fortune_live_fail - cmp fortuneWorkerState3D,1 - jne fortune_live_fail + jne fortune_session_fail + call SubmitFortuneQuestion3D + cmp fortunePhase3D,0 + jne fortune_session_fail mov esi,250 -fortune_live_full_input: +fortune_session_input_limit: invoke WindowEvents3D,0,102h,'a',0 dec esi - jnz fortune_live_full_input + jnz fortune_session_input_limit cmp fortuneQuestionLength3D,240 - jne fortune_live_fail + jne fortune_session_fail cmp fortuneQuestion3D[240],0 - jne fortune_live_fail + jne fortune_session_fail invoke WindowEvents3D,0,102h,8,0 cmp fortuneQuestionLength3D,239 - jne fortune_live_fail + jne fortune_session_fail mov fortuneQuestionLength3D,0 mov fortuneQuestion3D,0 - mov esi,OFFSET fortuneLiveQuestion -fortune_live_type: + mov esi,OFFSET fortuneSessionQuestion +fortune_session_input: movzx eax,BYTE PTR [esi] test eax,eax - jz fortune_live_ready + jz fortune_session_input_done invoke WindowEvents3D,0,102h,eax,0 inc esi - jmp fortune_live_type -fortune_live_ready: - mov edi,9000 -fortune_live_wait_ready: - call PollFortuneWorker3D - cmp fortuneWorkerState3D,2 - je fortune_live_submit - cmp fortuneWorkerState3D,4 - je fortune_live_fail - invoke Sleep,20 - dec edi - jnz fortune_live_wait_ready - jmp fortune_live_fail -fortune_live_submit: - mov expansionStage,81 - call SubmitFortuneQuestion3D - cmp fortuneWorkerState3D,3 - jne fortune_live_fail - mov esi,fortuneQuestionLength3D - invoke WindowEvents3D,0,102h,'X',0 - cmp fortuneQuestionLength3D,esi - jne fortune_live_fail - call SubmitFortuneQuestion3D - mov edi,9000 -fortune_live_wait_answer: - call PollFortuneWorker3D - cmp fortuneWorkerState3D,2 - je fortune_live_answer - cmp fortuneWorkerState3D,4 - je fortune_live_fail - invoke Sleep,20 - dec edi - jnz fortune_live_wait_answer - jmp fortune_live_fail -fortune_live_answer: - cmp fortuneAnswer3D,0 - je fortune_live_fail - cmp fortuneQuestionLength3D,0 - jne fortune_live_fail + jmp fortune_session_input +fortune_session_input_done: + mov fortuneSpeechTime3D,3000 call RenderFortuneDialog3D - invoke ExpansionSnapshot,OFFSET fortuneLiveShot + invoke ExpansionSnapshot,OFFSET fortuneSessionShot0 + call SubmitFortuneQuestion3D + cmp fortunePhase3D,1 + jne fortune_session_fail + cmp fortuneDealt3D,0 + jne fortune_session_fail + invoke WindowEvents3D,0,102h,'X',0 + cmp fortuneQuestionLength3D,22 + jne fortune_session_fail + call SubmitFortuneQuestion3D + cmp fortunePhase3D,1 + jne fortune_session_fail + mov fortunePhaseTime3D,700 + mov fortuneSpeechTime3D,1000 + call RenderFortuneDialog3D + invoke ExpansionSnapshot,OFFSET fortuneSessionShot1 + cmp fortunePhaseTime3D,700 + jne fortune_session_fail + xor edi,edi +fortune_session_motion: + mov eax,edi + xor edx,edx + mov ecx,10 + div ecx + add al,'0' + add dl,'0' + mov fortuneSessionDigits[0],al + mov fortuneSessionDigits[1],dl + mov eax,edi + imul eax,25 + mov fortunePhaseTime3D,eax + call RenderFortuneDialog3D + invoke ExpansionSnapshot,OFFSET fortuneSessionMotion + inc edi + cmp edi,64 + jb fortune_session_motion + mov ebx,1 +fortune_session_phases: + mov eax,fortuneDurations3D[ebx*4] + mov fortunePhaseTime3D,eax + call FortuneSessionStepTest + inc ebx + cmp fortunePhase3D,ebx + jne fortune_session_fail + mov fortunePhaseTime3D,600 + mov fortuneSpeechTime3D,3000 + call RenderFortuneDialog3D + invoke ExpansionSnapshot,fortuneSessionShots[ebx*4] + cmp ebx,12 + jb fortune_session_phases + cmp fortuneDealt3D,3 + jne fortune_session_fail + cmp fortuneRevealed3D,3 + jne fortune_session_fail + cmp fortuneQuestionLength3D,22 + jne fortune_session_fail + mov eax,fortuneSpeechLength3D + cmp fortuneSpeechVisible3D,eax + jne fortune_session_fail + ; Distinct underlying cards even when orientation differs. + xor esi,esi +fortune_session_unique_outer: + mov eax,fortuneDrawCards3D[esi*4] + xor edx,edx + mov ecx,22 + div ecx + mov edi,edx + lea ebx,[esi+1] +fortune_session_unique_inner: + cmp ebx,3 + jae fortune_session_unique_next + mov eax,fortuneDrawCards3D[ebx*4] + xor edx,edx + div ecx + cmp edx,edi + je fortune_session_fail + inc ebx + jmp fortune_session_unique_inner +fortune_session_unique_next: + inc esi + cmp esi,2 + jb fortune_session_unique_outer + mov expansionStage,81 + ; Bubble grows as glyphs appear and cannot exceed the scene edge. + invoke SetFortuneSpeech3D,OFFSET fortuneSummary3D + mov fortuneSpeechTime3D,280 + call RenderFortuneDialog3D + mov esi,fortuneSpeechWidth3D + mov fortuneSpeechTime3D,3000 + call RenderFortuneDialog3D + cmp fortuneSpeechWidth3D,esi + jbe fortune_session_fail + cmp fortuneSpeechWidth3D,204 + ja fortune_session_fail + cmp fortuneSpeechHeight3D,140 + ja fortune_session_fail + ; Return to selection, then display the neutral, non-grading orb sequence. + mov mouseX3D,200 + mov mouseY3D,232 mov mouseClick3D,1 + call HandleFortuneDialog3D + cmp fortuneSubMode3D,0 + jne fortune_session_fail + invoke WindowEvents3D,0,102h,'2',0 + cmp fortuneSubMode3D,FORTUNE_SUBMODE_ORB + jne fortune_session_fail + mov fortunePhaseTime3D,700 + call RenderFortuneDialog3D + invoke ExpansionSnapshot,OFFSET fortuneSessionShot13 + mov fortunePhaseTime3D,1400 + call FortuneSessionStepTest + cmp fortunePhase3D,2 + jne fortune_session_fail + mov fortunePhaseTime3D,1500 + mov fortuneSpeechTime3D,3000 + call RenderFortuneDialog3D + invoke ExpansionSnapshot,OFFSET fortuneSessionShot14 + mov fortunePhaseTime3D,5000 + call FortuneSessionStepTest + cmp fortunePhase3D,3 + jne fortune_session_fail + mov fortuneSpeechTime3D,3000 + call RenderFortuneDialog3D + invoke ExpansionSnapshot,OFFSET fortuneSessionShot15 + ; Exit in the middle of an animation as well as after completion. mov mouseX3D,260 - mov mouseY3D,226 + mov mouseY3D,232 + mov mouseClick3D,1 call HandleFortuneDialog3D cmp engineMode,MODE_WALK - jne fortune_live_fail - cmp fortuneProcess3D,0 - jne fortune_live_fail - cmp fortuneRead3D,0 - jne fortune_live_fail - cmp fortuneWrite3D,0 - jne fortune_live_fail + jne fortune_session_fail + cmp fortuneSessionRoom3D.pixels,0 + jne fortune_session_fail + call BeginFortuneDialog3D + invoke FortuneChar3D,'1' + invoke FortuneChar3D,'a' + call SubmitFortuneQuestion3D + mov mouseX3D,260 + mov mouseY3D,232 + mov mouseClick3D,1 + call HandleFortuneDialog3D + cmp engineMode,MODE_WALK + jne fortune_session_fail + cmp fortuneSessionShuffle3D.pixels,0 + jne fortune_session_fail mov eax,bankroll - cmp eax,fortuneLiveBank - jne fortune_live_fail - cmp playerX,FP_1*155/10 - jne fortune_live_fail - cmp playerY,11*FP_1 - jne fortune_live_fail - cmp playerAngle,256 - jne fortune_live_fail + cmp eax,fortuneSessionBank + jne fortune_session_fail + mov eax,playerX + cmp eax,fortuneSessionPlayer + jne fortune_session_fail ret -fortune_live_fail: - call StopFortuneWorker3D +fortune_session_fail: invoke ExitProcess,expansionStage ret FortuneDialogNativeTests ENDP diff --git a/tools/tests/fortune_room_test.inc b/tools/tests/fortune_room_test.inc index 0d2d332..382d939 100644 --- a/tools/tests/fortune_room_test.inc +++ b/tools/tests/fortune_room_test.inc @@ -278,7 +278,7 @@ fortune_test_arrived: invoke ExpansionSnapshot,OFFSET fortuneTestDialog mov mouseClick3D,1 mov mouseX3D,260 - mov mouseY3D,226 + mov mouseY3D,232 call HandleFortuneDialog3D cmp engineMode,MODE_WALK jne fortune_test_fail diff --git a/tools/tests/hall_expansion_test.inc b/tools/tests/hall_expansion_test.inc index fd9c605..c77d56d 100644 --- a/tools/tests/hall_expansion_test.inc +++ b/tools/tests/hall_expansion_test.inc @@ -1162,9 +1162,8 @@ expansion_leaf_shut: jne expansion_fail call FortuneNativeTests call BarCameraNativeTests -IFDEF CASINO_FORTUNE_AI_LIVE call FortuneDialogNativeTests -ENDIF + call ExpansionDartsTests invoke ExitProcess,0 expansion_fail: invoke ExitProcess,expansionStage @@ -1173,3 +1172,4 @@ ExpansionTestStart ENDP 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 diff --git a/tools/tests/observer_test.inc b/tools/tests/observer_test.inc new file mode 100644 index 0000000..f8cf4cf --- /dev/null +++ b/tools/tests/observer_test.inc @@ -0,0 +1,161 @@ +.data +obsTestGame dd 0 +obsTestTicks dd 0 +obsTestTurns dd 0 +obsTestMenu db "build\observer\menu.bmp",0 +obsTestPaths dd OFFSET obsTestPoker,OFFSET obsTestBlackjack,OFFSET obsTestDurak + dd OFFSET obsTestTransfer,OFFSET obsTestPyramid,OFFSET obsTestEight +obsTestPoker db "build\observer\poker.bmp",0 +obsTestBlackjack db "build\observer\blackjack.bmp",0 +obsTestDurak db "build\observer\durak.bmp",0 +obsTestTransfer db "build\observer\transfer.bmp",0 +obsTestPyramid db "build\observer\pyramid.bmp",0 +obsTestEight db "build\observer\eight.bmp",0 +.code +ObserverTestStart PROC + call InitPaths3D + mov arrivalConfirmed3D,1 + mov bankroll,12345 + mov charmCloverUses,17 + mov charmDevilUses,18 + mov obsActive3D,1 + invoke TriggerCharmLossProtection,100 + test eax,eax + jnz obs_test_fail + cmp bankroll,12345 + jne obs_test_fail + mov obsActive3D,0 + call BeginObserver3D + call RenderObserver3D + invoke WriteActionSnapshot3D,OFFSET obsTestMenu +obs_test_game: + mov eax,obsTestGame + mov obsSelection3D,eax + call StartObserver3D + cmp obsActive3D,1 + jne obs_test_fail + mov obsTestTicks,0 + mov obsTestTurns,0 +obs_test_tick: + mov deltaTime3D,50 + call ObserverTick3D + cmp obsTestGame,4 + jb obs_test_cards + cmp poolState3D,POOL_STRIKE + jne @F + mov eax,poolPlayer3D + bts obsTestTurns,eax +@@: + cmp obsTestTicks,1500 + jb obs_test_next_tick + cmp obsTestTurns,3 + jne obs_test_fail + jmp obs_test_snapshot +obs_test_cards: + cmp obsRound3D,21 + jb obs_test_next_tick + mov eax,obsWins3D[0] + add eax,obsWins3D[4] + add eax,obsWins3D[8] + cmp eax,20 + jne obs_test_fail + jmp obs_test_snapshot +obs_test_next_tick: + inc obsTestTicks + cmp obsTestTicks,150000 + jb obs_test_tick + jmp obs_test_fail +obs_test_snapshot: + ; Advance into a real deal/turn before capturing the watching surface. + mov ecx,40 +obs_test_render_advance: + push ecx + mov deltaTime3D,50 + call ObserverTick3D + pop ecx + loop obs_test_render_advance + call RenderObserver3D + mov eax,obsTestGame + invoke WriteActionSnapshot3D,obsTestPaths[eax*4] + mov obsPaused3D,1 + mov eax,obsRound3D + push eax + mov eax,obsTimer3D + push eax + mov deltaTime3D,50 + call ObserverFrame3D + pop eax + cmp eax,obsTimer3D + jne obs_test_fail + pop eax + cmp eax,obsRound3D + jne obs_test_fail + mov pressedKeys3D[9],1 + call ObserverFrame3D + cmp obsSpeed3D,2 + jne obs_test_fail + mov pressedKeys3D[9],1 + call ObserverFrame3D + cmp obsSpeed3D,4 + jne obs_test_fail + mov pressedKeys3D[9],1 + call ObserverFrame3D + cmp obsSpeed3D,1 + jne obs_test_fail + call StopObserver3D + cmp bankroll,12345 + jne obs_test_fail + cmp charmCloverUses,17 + jne obs_test_fail + cmp charmDevilUses,18 + jne obs_test_fail + cmp obsActive3D,0 + jne obs_test_fail + inc obsTestGame + cmp obsTestGame,OBS_GAME_COUNT + jb obs_test_game + ; The actual selector opens a table, and Escape unwinds watch -> menu -> hall. + mov pressedKeys3D[31h],1 + call ObserverFrame3D + cmp obsActive3D,1 + jne obs_test_fail + mov pressedKeys3D[1Bh],1 + call ObserverFrame3D + cmp engineMode,MODE_OBSERVER + jne obs_test_fail + cmp bankroll,12345 + jne obs_test_fail + mov mouseX3D,100 + mov mouseY3D,174 + mov mouseClick3D,1 + call ObserverFrame3D + cmp obsActive3D,1 + jne obs_test_fail + cmp poolVariant3D,1 + jne obs_test_fail + mov mouseX3D,20 + mov mouseY3D,230 + mov mouseClick3D,1 + call ObserverFrame3D + cmp obsPaused3D,1 + jne obs_test_fail + mov mouseX3D,250 + mov mouseClick3D,1 + call ObserverFrame3D + cmp obsActive3D,0 + jne obs_test_fail + mov pressedKeys3D[1Bh],1 + call ObserverFrame3D + cmp engineMode,MODE_WALK + jne obs_test_fail + mov obsActive3D,1 + call RunDurakTest3D + test eax,eax + jnz obs_test_exit + xor eax,eax + jmp obs_test_exit +obs_test_fail: + mov eax,8401 +obs_test_exit: + invoke ExitProcess,eax +ObserverTestStart ENDP diff --git a/tools/tests/release_smoke_check.ps1 b/tools/tests/release_smoke_check.ps1 index 4ab87e1..a118248 100644 --- a/tools/tests/release_smoke_check.ps1 +++ b/tools/tests/release_smoke_check.ps1 @@ -70,27 +70,18 @@ try { $cp1251 = [Text.Encoding]::GetEncoding(1251) $asmText = $cp1251.GetString($asmBytes) foreach ($needle in @( - 'settingsCinematicText db "Cinematic -', - 'settingsAdaptiveText db "Adaptive -', - 'settingsStableText db "Stable -', - 'AnimShouldDrawFrame PROC', - 'AnimFrameDelay PROC', - 'WriteAnimProfileFile PROC', 'SAVE_SIGN_SIZE equ 276', 'SAVE_FILE_SIZE equ 280', 'CalcSaveSignature PROC', 'mov DWORD PTR [saveBuffer+SAVE_SIGN_SIZE], eax', 'cmp charsRead, SAVE_FILE_SIZE', - 'includelib ws2_32.lib', - 'GameOnlineCasino PROC', - 'OnlineRRSessionHost PROC', - 'OnlineRRSessionClient PROC' + 'includelib ws2_32.lib' )) { if ($asmText.IndexOf($needle) -lt 0) { Fail "Missing source marker: $needle" } } - Pass 'Animation profile and save signature markers exist in poker.asm' + Pass 'Save signature and library markers exist in poker.asm' if ($asmText -match 'MAMBO|mambo|PlayMambo') { Fail 'MAMBO markers still exist in poker.asm'