Add native NPC visitors with contracts, Clover slots, Pyramid, and party table scenes

Implement 32x32 BFS visitor navigation and daily contracts system with companion .npc journals; add native assembly scenes for Clover slots, Pyramid cards, Blackjack Party and Russian Party; package animation and sprite packs; add comprehensive offline test suites and rebuild shipping binaries.
This commit is contained in:
Artvasil09 2026-10-13 09:47:33 +03:00
parent 4f8ae0893b
commit 881a0061dc
116 changed files with 33552 additions and 22942 deletions

Binary file not shown.

View file

@ -0,0 +1,20 @@
{
"symbols": [
"clover",
"lemon",
"cherries",
"bell",
"diamond",
"chest",
"seven"
],
"cabinet": [
640,
480
],
"symbol": [
56,
56
],
"runtime": "MASM; five reels, three complete rows, legacy additive combos"
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -251,5 +251,109 @@
"symCherry": "ВИШНЯ",
"symClover": "КЛЕВЕР",
"symGem": "АЛМАЗ",
"symCrown": "КОРОНА"
"symCrown": "КОРОНА",
"npcName6": "Дмитрий Серов",
"npcName7": "Виктор Громов",
"npcName8": "Григорий Лис",
"npcName9": "Павел Мирный",
"npcName10": "Антон Богатый",
"npcName11": "Алиса Ветрова",
"npcGreeting0": "Проверим твою выдержку?",
"npcGreeting1": "Сегодня я играю смело.",
"npcGreeting2": "Удача любит терпеливых.",
"npcGreeting3": "Давай сыграем без спешки.",
"npcGreeting4": "Есть дело с наградой.",
"npcGreeting5": "Попробуем свои силы?",
"npcQuest0": "Заверши любые 3 раунда",
"npcQuest1": "Подбрось монетку 3 раза",
"npcQuest2": "Выиграй в монетку",
"npcQuest3": "Сыграй 2 раунда в блэкджек",
"npcQuest4": "Выиграй в блэкджек",
"npcQuest5": "Заверши 2 раздачи покера",
"npcQuest6": "Выиграй раздачу покера",
"npcQuest7": "Сыграй 3 раунда в рулетку",
"npcQuest8": "Выиграй в рулетку",
"npcQuest9": "Крути автомат 3 раза",
"npcQuest10": "Заверши партию в дурака",
"npcQuest11": "Выиграй партию в дурака",
"npcTalkPrompt3D": "[E] ПОГОВОРИТЬ",
"npcContractTitle3D": "ЧЕЛЛЕНДЖ НА СЕГОДНЯ",
"npcProgressText3D": "Прогресс",
"npcRewardText3D": "Награда",
"npcAcceptText3D": "ВЗЯТЬ",
"npcClaimText3D": "НАГРАДА",
"npcCloseText3D": "ЗАКРЫТЬ",
"npcAvailableText3D": "Можно принять",
"npcActiveText3D": "Выполняется",
"npcReadyText3D": "Готово! Забери награду",
"npcDoneText3D": "Награда уже получена",
"npcIoErrorText3D": "Не удалось сохранить задание",
"cbPartyName0": "ТОЛЯ",
"cbPartyName1": "СЕРГЕЙ",
"cbPartyName2": "АЛИСА",
"cbBotsLabel3D": "БОТЫ",
"cbBotWaitText3D": "ЖДЁТ",
"cbBotHitText3D": "КАРТА",
"cbBotStandText3D": "ХВАТИТ",
"cbBotBustText3D": "ПЕРЕБОР",
"cbBotWinText3D": "ПОБЕДА",
"cbBotLoseText3D": "ПРОИГРЫШ",
"cbBotPushText3D": "НИЧЬЯ",
"cbBotNaturalText3D": "БЛЭКДЖЕК",
"cbDealerLabel3D": "ДИЛЕР",
"cbPartyPlayerText3D": "ВЫ",
"rrPartyName0": "ТОЛЯ",
"rrPartyName1": "СЕРГЕЙ",
"rrPartyName2": "ГРИГОРИЙ",
"rrPartyCountText3D": "СОПЕРНИКИ",
"rrPartyOutText3D": "ВЫБЫЛ",
"rrPartyTurnText3D": "ХОД",
"rrPartyAliveText3D": "ОЖИДАЕТ",
"rrPartyPoolText3D": "БАНК",
"rrPartyExitText3D": "Раунд ещё не закончен.",
"slotRefundText3D": "ВОЗВРАТ",
"slotNetText3D": "ИТОГ",
"clpTitle3D": "КЛЕВЕР 5x3",
"clpTotalText3D": "МНОЖИТЕЛЬ",
"clpNoCombo3D": "НЕТ КОМБИНАЦИЙ",
"clpSymbolsText3D": "СИМВОЛ",
"clpShapesText3D": "ФИГУРА",
"clpOverlapText3D": "ФИГУРА x СИМВОЛ; СУММА",
"clpShape0": "РЯД 3",
"clpShape1": "РЯД 4",
"clpShape2": "РЯД 5",
"clpShape3": "ВЕРТИКАЛЬ",
"clpShape4": "ДИАГОНАЛЬ",
"clpShape5": "СТРЕЛКА ВВЕРХ",
"clpShape6": "СТРЕЛКА ВНИЗ",
"clpShape7": "ВЕРХНИЙ РЯД",
"clpShape8": "НИЖНИЙ РЯД",
"clpShape9": "ГЛАЗ",
"clpShape10": "ДЖЕКПОТ",
"clpSymbol0": "КЛЕВЕР",
"clpSymbol1": "ЛИМОН",
"clpSymbol2": "ВИШНЯ",
"clpSymbol3": "КОЛОКОЛ",
"clpSymbol4": "АЛМАЗ",
"clpSymbol5": "СУНДУК",
"clpSymbol6": "СЕМЬ",
"slotTripleText3D": "ЦЕНТР: ТРИ ОДИНАКОВЫХ",
"pyrExit3D": "В ХОЛЛ",
"pyrTitle3D": "ПИРАМИДА",
"pyrBreakText3D": "РАЗБИТИЕ",
"pyrTurnText3D": "ХОД",
"pyrCueText3D": "БИТОК",
"pyrGuideText3D": "ПРИЦЕЛ",
"pyrPenaltyText3D": "ШТРАФНОЙ ШАР",
"pyrChoiceText3D": "ПОСЛЕ ШТРАФА",
"pyrPlayText3D": "ИГРАТЬ",
"pyrPassText3D": "ПЕРЕДАТЬ",
"pyrRackText3D": "ПЕРЕБИТЬ",
"pyrTheirBreakText3D": "ЕГО УДАР",
"pyrNoContactText3D": "НЕТ КАСАНИЯ",
"pyrBadBreakText3D": "НЕВЕРНЫЙ РАЗБОЙ",
"pyrBadEndText3D": "НЕВЕРНЫЙ УДАР",
"pyrPlayerName3D": "ВЫ",
"pyrAIName3D": "ИИ",
"pyrSkipText3D": "ПРОПУСК"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 MiB

View file

@ -0,0 +1,3 @@
{
"tool": "built-in image_gen"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View file

@ -0,0 +1,6 @@
{
"mode": "built-in image_gen",
"table": "Use case: stylized-concept. Asset type: opaque 4:3 overhead game background for Russian pyramid billiards inside the existing ornate retro casino. Precisely top-down orthographic view, never perspective: one full long wooden Russian billiards table, rich dark burgundy polished wood rails, dark emerald cloth, warm brass sights, broad rounded wood corners, realistic cushions and restrained reflections. Crisp detailed Doom-era pixel-painted bitmap art, not photoreal, not vectors. Playable cloth is a completely empty clean rectangle at x9.375% to90.625% of canvas and y20.833% to75%; this rectangle has exactly 2:1 proportions. No balls, no cue, no people, no lettering, no lights painted on cloth, no symbols. Six small black pocket mouths, corners and midpoints of the two long sides, narrow Russian pyramid pockets, not enormous pool holes. Rails around cloth, hints of crimson casino carpet beyond. Leave top 8% and bottom 16% as dark quiet framing for game HUD. All four table edges visible, table fills most of screen. Consistent pixel textures and physically coherent furniture, no skew or vanishing point, no watermark.",
"table_narrow_edit": "Edit target: supplied Russian billiard overhead table bitmap. Change only the six pocket mouths and immediately adjoining green cushion noses. Reduce all six black pocket-mouth widths and heights to about 50% of their current size, creating genuinely narrow Russian pyramid pockets. Extend the same green felt cushion noses towards these smaller holes, preserving realistic brass collars and shadows. Keep all six pocket centers at exactly the same positions. Preserve every other pixel-art design, orthographic camera, table proportions, wood, carpet, colours, framing, and empty cloth. No balls, no cue, no new objects, no text or watermark. This remains a crisp pixel-painted casino game background, not a photo.",
"balls": "Use case: stylized-concept. Asset type: transparent 4 by 2 equal square cell game sprite atlas with exactly eight Russian billiard ball sprites viewed from directly above, perfectly circular matching anchor/diameter. Top row four ivory white glossy phenolic billiard balls, bottom row four golden amber yellow cue balls. Four slightly different rotation/reflection poses of the same ball, subtle varied surface details; no deformations, no number markings or text. Consistent upper-left warm highlight, softly shaded lower-right edge and faint ivory and honey material tone, high-contrast crisp readable spherical silhouettes, Doom-era richly shaded pixel-painted game style. Each ball fully isolated in its cell with generous transparent gutters, same size and center, no cast shadows extending beyond ball, no background, no grid, no checkerboard, no border, no watermark. This is Russian pyramid: no coloured solids/stripes, only four whites and four identical amber cue ball rotation poses."
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

View file

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

Binary file not shown.

10997
casino3d.inc

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -0,0 +1,71 @@
# Native Blackjack: Shared Table
## Implemented
The graphical blackjack table can seat the player and zero to three AI guests,
with a house dealer. The existing solo mode remains the default. Before a hand,
the four numbered controls select the guest count (also keys 0, 1, 2, 3).
Selection is locked until the hand has ended. Choosing a different count after
a result clears that displayed hand without touching the wallet.
All seats receive actual identities from one shuffled 52-card deck. The deal
is round-robin, followed by the player's turn, timed guest decisions and the
dealer's turn. Each guest has a separate hand, virtual stake and result.
Guest stakes do not come from the player's balance. Guests do not yet have
persistent wallets or bring the hall visitor's identity to their table seat.
Tolya is cautious, Sergey hits more aggressively, and Alisa adapts her stopping
threshold to the dealer's upcard. A two-card eleven can double against an
appropriate upcard. Decisions never examine the hidden dealer card, another
seat's hand or future deck cards. These are limited personality strategies,
not a complete optimal blackjack strategy chart. Dealer stands on all 17s;
player blackjack pays 3:2, pushes return the deducted stake.
A player bust or natural no longer truncates the guests' turns. A dealer
natural resolves all seats immediately. The existing exit confirmation freezes
the table and forfeits only the player's stake on confirmation. Settlement and
NPC challenge progress happen once for the player, never once per guest.
## Artwork
`assets/sprites/tables/blackjack-guests-v1.png` was generated with the built-in
image generator as one transparent three-column, two-row sprite sheet.
Each adult character has an attentive pose and a card-request gesture.
`tools/generators/pack_blackjack_guests.py` normalizes its six cells into
160-by-160 BGRA frames in `assets/animations/tables/blackjack-guests-v1.bin`.
These are two poses per guest, not a full continuous character animation.
Native timed card flights and queued dealer-hand sprites animate each draw.
`blackjack-room-v1.png` and its compiled ANM1 background put the guests behind
the far wooden rim instead of drawing their bodies directly on the felt.
Generation prompt: detailed hand-painted Doom-era pixel sprites, transparent
background, three seated adult guests (bald gray-suited man, dark-haired navy-
jacketed man, auburn-haired emerald-dressed woman); identical anchors and scale
across attentive/extended-hand rows; warm amber light, burgundy shadows; no
table, cards, chips, chairs, text or cross-cell overlap.
Room prompt: empty first-person vintage casino background in the same detailed
pixel style; three empty burgundy chairs behind a wooden blackjack-table rim
at 52% canvas height, green felt below, front padded rim at 90%; dark wood,
brass lights, no people, text or cards. The built-in generator's first room
plate was edited to lower the far rim to the intended midpoint.
## Verification
`tools/tests/blackjack_party_test.inc` runs in the native action suite:
600 complete hands across all three guest counts; exact deck consumption and
card uniqueness; bounded counts; independent player payout; natural/push/bust;
no hidden-card dependence; actual mouse and keyboard selection; safe exit and
frozen timers; render purity and six framebuffer captures.
`check_blackjack_party.py --parity` compares the tested and shipping MASM
procedures and inspects the native captures. `test_blackjack_party.ps1` is a
focused native probe linked against the action-test object.
## Still Pending
Native three/four-player poker, Russian roulette and Buckshot are separate
features. They are not implemented by this blackjack work, and the old console
four-seat poker loop is not connected to the graphical table. Poker still needs
correct multiplayer betting, all-ins, side pots and tie splits before that
loop can become a graphical game.

67
docs/native-clover.md Normal file
View file

@ -0,0 +1,67 @@
# Native Clover 5x3
The runtime is MASM x86 only. `native_clover.inc` owns input, five timed reel
strips, clipping, final-board evaluation, settlement, paid-combo selection and
rendering. The original assembly `EvaluateSlotCombos` is reused directly; the
console `GameSlot`, its sleeps, console output and payout functions are never
called by this mode.
## Rules
Fifteen final symbols are sampled once when the stake is debited. Symbol values:
clover 3, lemon 2, cherries 2, bell 3, diamond 5, chest 5, seven 7.
Each paid pattern contributes its factor times its symbol value. Contributions
are added; a pattern entirely covered by earlier paid patterns is not paid
again. Original order: jackpot, eye, sky/earth, up/down arrows, rows 5, rows 4,
verticals, diagonals, rows 3. The jackpot is additive, matching the original
game, not a replacement for the remaining matches.
Displayed multipliers and credited payouts include any returned stake. The
separate net field is actual credit minus the frozen stake. A bank-cap-clipped
payout displays only the money actually credited. The bank cap is 1,000,000,000.
777 uses the same gross/net convention, with neutral feedback for a stake
refund, not victory feedback. Its unchanged strip/table has theoretical gross
RTP 90.025% under independent uniform stop selection; this is not a guarantee
for the actual pseudo-random generator or any individual session.
## Interaction
Space/Enter spins, W/S or Up/Down change the idle stake, P opens/closes the pay
table, Left/Right select an actually paid combo. All controls have clickable
equivalents. Escape first closes the pay table; during a paid spin it queues
leaving until settlement/save completes, otherwise it returns to the hall.
No pause menu or console fallback is opened.
The result grid highlights the selected paid mask, with its type, multiplier,
index/count, aggregate multiplier, gross payout and net separately. Only the
middle line of the separate 777 machine pays; its payout panel says the stake
is included.
## Assets
Generated with the built-in image generator, then copied to
`assets/sprites/clover/cabinet-v1.png` and `symbols-v1.png`. The cabinet is opaque
640x480, symbols seven alpha-tested 56x56 frames. `pack_clover_assets.py` is an
offline packer, not a non-assembly game implementation.
Cabinet prompt: wide front-facing Clover machine in a richly furnished retro
casino, emerald enamel/worn brass/crimson trim, crisp textured Doom-era pixel
art; one large blank near-black rectangular reel bay, no baked symbols,
dividers, letters or people, straight-on framing.
Symbol prompt: transparent equal-cell 4x2 pixel-painted atlas, clover/lemon/
cherries/bell on top, diamond/chest/red seven below, final cell empty; isolated,
centered, consistent apparent size and readable silhouettes, no grid/background.
## Verification And Scope
Independent offline fixture oracle covers 553 boards, including all seven
jackpots and each pattern. Native tests cover 500 paid spins at four update
rates, frozen RNG/stake, exact final tape rows, monotonic stopping, queued exit,
idempotent payout, bank cap, missing funds and six framebuffer captures.
`check_clover.py --parity` checks assets, real framebuffer clipping, all fifteen
complete cells, selected paid-mask changes and tested/shipping assembly code.
Native talisman modifiers and legacy console achievement/dance side effects
are not ported by this change. NPC challenges count native slot rounds and
classify only positive net results as wins. No claim of completing every legacy
Clover feature or its balance is made.

View file

@ -43,6 +43,12 @@ queued departure and result persistence. Six framebuffer captures cover draft,
launch, fast/slow rotation, landing and result. The shipping loader probe also
loads/frees wheel packs from both the project root and TEMP.
Additional native captures cover all 37 settled results and isolated distant
and rotating labels. The offline checker compares every foreground digit pixel
with an independent font mask, verifies the large central result, and checks
that labels never overlap at any of the 512 rotation angles. Small labels write
physical framebuffer pixels directly; they do not use the coarser UI grid.
The dealer background is currently a static pose. This is a native animated
wheel/table scene, not a complete animated croupier performance or a physical
simulation of a freely rolling ball.

View file

@ -0,0 +1,59 @@
# Native Russian Roulette: Multiple Opponents
## Rules
The existing one-opponent game remains the default. Ready-screen controls select
one, two or three opponents; Left/Right changes the selection. The existing
1/2/3 keys still choose the number of loaded cartridges. Mouse controls also
start the round, fire on the player's turn, and open the exit confirmation.
The round locks its participant count and player's stake at the start. In the
group variant, all participants use one cylinder in cyclic order. Each takes
their own turn; there is no selectable target. A live shot consumes that loaded
round and removes the current participant. Empty chambers pass to the next
remaining participant, skipping eliminated seats. The last surviving player
receives the initial stake times the initial participant count. Opponent stakes
are virtual casino stakes, not withdrawals from the player's account.
An exhausted cylinder is reloaded and spun without another payment. Reloading
does not restore eliminated opponents or change the locked pool. The player's
elimination preserves the existing glitch/dark/G-man/return sequence. The match
is recorded once for NPC challenges; intermediate opponent eliminations are
not extra paid wins. Confirmed early exit forfeits the already-paid stake.
Escape opens the shared leave/continue dialog in both solo and party modes;
the dialog freezes the scene and cylinder. Cancelling resumes the same turn.
Leaving after settlement does not make a second payout or challenge event.
## Graphics
Three distinct neutral seated opponents use six frames from
`assets/sprites/roulette/participants-v1.png`, generated with the built-in image
generator and packed offline into `assets/animations/roulette/participants-v1.bin`.
The active seat is named/highlighted; an eliminated seat fades and becomes empty.
The existing first-person player and cylinder animations are retained.
The party view passes the existing revolver along the tabletop. The opponents
currently have idle/conversational poses, not complete individual cinematic
firing animations. The attempted new firing-pose sheet was rejected by the
image tool and is not a shipped asset. No replacement has been disguised as a
full new shooting animation. One-opponent firing remains the existing scene.
Neutral sprite prompt: three waist-up adult casino guests, bald waistcoated
man, dark-haired charcoal-jacketed man, older gray-haired brown-jacketed man;
three columns by two rows, attentive idle and calm palm-up conversation;
same identity/scale/anchors, transparent background, crisp late-1990s pixel
sprites, amber light and crimson shadows; no objects, weapons, wounds or text.
## Verification
`russian_party_test.inc` covers 600 complete group matches at 16/50/250 ms
updates, 1..3 loaded rounds, elimination order, unchanged cylinder between
empty chambers, reload-without-payment, locked-pool settlement, render purity,
all active pre-result exit phases, zero bank and invalid bullet count.
The existing 2000 solo-round tests still run. Native captures and tested/shipping
procedure parity are checked by `check_russian_party.py --parity`.
The runtime remains MASM. Python only packs assets and checks captures;
the existing C# launcher remains packaging, not game code. Multiplayer poker
and Buckshot are still separate unfinished features.

View file

@ -14,7 +14,10 @@ stake is charged; animation and repeated renderer calls cannot reroll them.
Three matches pay gross multipliers: seven 100, cherries 8, lemon 10, bell 15,
BAR 25, grapes 12, diamond 60 and crown 150. Exactly two cherries pay 2x;
any other exact pair returns 1x. No match pays nothing. Gross payouts include
the charged stake. Under uniformly sampled stops the configured return is
the charged stake. The footer separates actual credited payout from signed net
profit/loss. An exact non-cherry pair is a neutral stake refund, with no victory
sound or winning-line flash. The pay table identifies the middle paying line.
Under independently and uniformly sampled stops the configured return is
7202 / 8000 = 90.025%; this is not a guarantee of individual-session results.
Stake choices are 10, 50, 100, 500, 1000, 5000 and 10000. The native bank cap
@ -59,6 +62,6 @@ the shipping and test COFF code for every slot handler.
`test_street_asset_paths.ps1` also initializes/frees the shipping native slot
cabinet from project-root and TEMP working directories, without launching a window.
CloverPit, talisman effects, native achievements and slot-specific profile
statistics still need separate integration; the native 777 does not call their
legacy console implementations.
CloverPit now has a separate native 5x3 machine; see `native-clover.md`. Talisman
effects, native achievements and slot-specific profile statistics still need
separate integration; neither machine calls their legacy console implementations.

53
docs/npc-life.md Normal file
View file

@ -0,0 +1,53 @@
# Native Visitors and Contracts
The graphical runtime remains MASM. The user's six personality records in
`npc_mind_data.inc` are retained. The visible people now read the positions of
those records instead of a separate, unrelated square-loop animation.
`npc_life.inc` owns bounded 32x32 BFS navigation, millisecond walking with
fractional carry, standing, seated lounge visits, entrance departures and
replacement visitors. All proposed steps check the existing body-clearance,
furniture/stage collision rules, the player and other visitors. A blocked person
stops animating their feet and chooses another destination after waiting.
The six active slots alternate between twelve identities. Standing/walking and
seated poses use the existing interior sprite pack; a full animated transition
into a chair and additional directional interior poses are not implemented yet.
Exterior people use outdoor sprites and long sidewalk routes, not courtyard
pacing or indoor guest sprites.
Press E when the conversation prompt appears. The owned graphical modal has
accept, reward and close commands. Mouse, 1/2, Enter, 0 and Escape work without
console input. The conversation holds the scene while it is open.
Twelve contract types cover played/won rounds of native coin, blackjack, poker,
European roulette, 777 and Durak. The any-round contract also counts Russian
roulette, Buckshot, dice, cups and skill checks. Only settled paid rounds count, with wins
requiring a positive result rather than a push. Progress starts after acceptance;
tasks remain associated with visitor identity even while that person is absent.
Offers rotate with the local calendar and visitors. Rewards range from $150 to
$700 and are capped by the game's existing billion-dollar balance limit.
Each local save has a companion `.npc` journal. It contains a date, bounded
accepted/claimed masks, capped progress and a checksum. Truncated, malformed,
wrong-date and cross-profile journals are not imported. Completed contracts
cannot be claimed twice after closing/reopening the dialog or restarting.
Journal write failures do not award repeatable money. The existing wallet save
and companion journal are separate writes; an abrupt power loss between them
can lose the reward credit, so this is not a transactional banking system.
The original save format and the legacy forty-task daily pool are unchanged.
Native tests exercise ten reachable destinations, equal-time movement at three
update rates, two minutes of body-clearance checks for all six visitors, capped
progress, early/double claims, persistence, profile isolation, day rollover,
mouse dialog commands and Escape. They use only journals inside build fixtures.
`check_npc_life.py --parity` checks three native framebuffer captures and
tested/shipping procedure parity.
## Multiplayer Tables
The user's legacy four-seat poker/personality code is preserved. It is not
called from the graphical casino because it uses console input/output. The
existing native tables remain heads-up. Native three/four-seat poker needs
proper repeated betting turns, ties, all-ins/side pots, exit settlement and
seated opponent rendering before it can replace that loop. Russian roulette
and Buckshot likewise still use their existing two-actor scenes.

30441
poker.asm

File diff suppressed because it is too large Load diff

BIN
poker.exe

Binary file not shown.

BIN
poker.obj

Binary file not shown.

File diff suppressed because it is too large Load diff

View file

@ -4,7 +4,10 @@ POOL_STRIKE equ 1
POOL_MOVING equ 2
POOL_HAND equ 3
POOL_RESULT equ 4
POOL_RADIUS equ 5*FP_1
POOL_RADIUS equ FP_1*5/2
POOL_DIAMETER equ 5*FP_1
POOL_PENALTY equ 5
POOL_CHOICE equ 6
InitPool3D PROTO
HandlePool3D PROTO
RenderPool3D PROTO
@ -25,8 +28,10 @@ poolColors3D dd 00F0E8D3h,00D4B524h,00304EADh,00B72B36h,006A438Ch,00DD8030h,003B
poolRackOrder3D dd 1,9,2,3,8,10,11,4,12,5,6,13,7,14,15
poolPocketX3D dd 30*FP_1,160*FP_1,290*FP_1,30*FP_1,160*FP_1,290*FP_1
poolPocketY3D dd 50*FP_1,50*FP_1,50*FP_1,180*FP_1,180*FP_1,180*FP_1
poolJawX3D dd 42*FP_1,30*FP_1,145*FP_1,175*FP_1,290*FP_1,278*FP_1,42*FP_1,30*FP_1,145*FP_1,175*FP_1,290*FP_1,278*FP_1
poolJawY3D dd 50*FP_1,62*FP_1,58*FP_1,58*FP_1,62*FP_1,50*FP_1,180*FP_1,168*FP_1,172*FP_1,172*FP_1,168*FP_1,180*FP_1
poolJawX3D dd FP_1*137/4,30*FP_1,157*FP_1,163*FP_1,290*FP_1,FP_1*1143/4
dd FP_1*137/4,30*FP_1,157*FP_1,163*FP_1,290*FP_1,FP_1*1143/4
poolJawY3D dd 50*FP_1,FP_1*217/4,50*FP_1,50*FP_1,FP_1*217/4,50*FP_1
dd 180*FP_1,FP_1*703/4,180*FP_1,180*FP_1,FP_1*703/4,180*FP_1
poolState3D dd POOL_AIM
poolPlayer3D dd 0
poolAI3D dd 1
@ -64,3 +69,4 @@ poolBallText3D db 216,192,208,0
poolPocketText3D db 203,211,199,192,0
poolGroupTexts3D dd OFFSET poolOpenText3D,OFFSET poolSolidsText3D,OFFSET poolStripesText3D
.code
include src_graphics\pyramid_data.inc

View file

@ -230,6 +230,7 @@ bs_finish_win:
jbe @F
mov bankroll, 1000000000
@@:
invoke NpcRecordRound3D,7,1
call SaveProgress3D
ret
bs_finish_lose:
@ -237,6 +238,7 @@ bs_finish_lose:
mov bs3d_result, BS_LOSE
mov bs3d_victim, 0
mov bs3d_timer, 800
invoke NpcRecordRound3D,7,0
call SaveProgress3D
bs_finish_done:
ret

View file

@ -1,5 +1,6 @@
; All runtime rendering/gameplay is native. Artwork and rank tables are binary assets.
FreeCardTableAssets3D PROC
call FreeBJParty3D
cmp cardTablePixels3D, 0
je @F
invoke HeapFree, cardTableHeap3D, 0, cardTablePixels3D

View file

@ -119,8 +119,13 @@ LeaveCardTable3D PROC
jmp card_leave_hall
@@:
cmp engineMode, MODE_DURAK
jne card_leave_hall
jne @F
invoke NativeDurakSettle3D, 1
jmp card_leave_hall
@@:
cmp engineMode,MODE_RUSSIAN
jne card_leave_hall
call AbortRussianRound3D
card_leave_hall:
mov cardExitActive3D, 0
mov paused3D, 0
@ -146,6 +151,12 @@ RequestCardExit3D ENDP
; Called by the engine's Escape dispatch, before the generic pause branch.
CardTableEscape3D PROC
cmp engineMode,MODE_NPC_DIALOG
jne @F
call CloseNpcDialog3D
mov eax,1
ret
@@:
cmp engineMode, MODE_COIN
jne @F
call CoinEscape3D
@ -163,12 +174,20 @@ CardTableEscape3D PROC
call SlotEscape3D
mov eax,1
ret
@@:
cmp engineMode, MODE_CLOVER
jne @F
call CloverEscape3D
mov eax,1
ret
@@:
cmp engineMode, MODE_POKER
je card_escape_handle
cmp engineMode, MODE_BLACKJACK
je card_escape_handle
cmp engineMode, MODE_DURAK
je card_escape_handle
cmp engineMode, MODE_RUSSIAN
jne card_escape_other
card_escape_handle:
cmp cardExitActive3D, 0
@ -283,6 +302,8 @@ TableBusy3D PROC
je table_busy_russian
cmp eax, MODE_SLOTS
je table_busy_slots
cmp eax, MODE_CLOVER
je table_busy_clover
cmp eax, MODE_ROULETTE
je table_busy_wheel
cmp eax, MODE_COIN
@ -334,6 +355,10 @@ table_busy_slots:
cmp slotSpinState, 1
je table_is_busy
jmp table_not_busy
table_busy_clover:
cmp clpState3D,SLOT_SPIN
je table_is_busy
jmp table_not_busy
table_busy_wheel:
cmp roulState, 1
je table_is_busy

View file

@ -299,6 +299,11 @@ RenderNativePoker3D ENDP
RenderNativeBlackjack3D PROC uses ebx esi edi
local hand:DWORD,nCards:DWORD,y:DWORD,spacing:DWORD,startX:DWORD,hidden:DWORD,flipCard:DWORD
cmp cbBotCount3D, 0
je @F
call RenderBJParty3D
ret
@@:
call RenderCardTable3D
invoke DrawTableHeader3D, OFFSET cbTitle3D, cbStake3D
xor eax, eax
@ -461,6 +466,7 @@ bj_render_next_card:
invoke DrawString8x8, 116, 135, cbResults3D[eax*4], 00EFD583h
bj_render_buttons:
call DrawQueuedTableHand3D
call DrawBJPartySelector3D
cmp cbError3D, 0
je @F
invoke DrawString8x8, 80, 135, OFFSET textNoCash3D, 00F89880h
@ -509,7 +515,12 @@ card_exit_dim:
loop card_exit_dim
invoke DrawBoxBorder, 20, 64, 280, 120, 00151D20h, 00D5C79Eh, 1
invoke DrawString8x8, 104, 78, OFFSET cardExitTitle3D, 00F2E4BCh
invoke DrawString8x8, 60, 104, OFFSET cardExitWarning3D, 00D8DFDDh
mov ecx,OFFSET cardExitWarning3D
cmp engineMode,MODE_RUSSIAN
jne @F
mov ecx,OFFSET rrPartyExitText3D
@@:
invoke DrawString8x8, 60, 104, ecx, 00D8DFDDh
invoke DrawString8x8, 72, 120, OFFSET cardExitStake3D, 00F4B6A4h
mov ebx, 00D5C79Eh
mov esi, 00556555h

View file

@ -152,11 +152,11 @@ cityEntryWoman3D LABEL BYTE
cityObjectsEnd3D LABEL BYTE
.errnz ((cityObjectsEnd3D-cityObjects3D)/SIZEOF CityProp3D)-CITY_PROP_COUNT
cityWalkRoutes3D CityWalkRoute3D <(cityNearMan3D-cityObjects3D)/SIZEOF CityProp3D,10*FP_1,28*FP_1,FP_1*253/20,0,32000,0,140>
CityWalkRoute3D <(cityFarWoman3D-cityObjects3D)/SIZEOF CityProp3D,10*FP_1,28*FP_1,FP_1*169/10,32000,32000,16,160>
CityWalkRoute3D <(cityFarWoman3D-cityObjects3D)/SIZEOF CityProp3D,10*FP_1,28*FP_1,16*FP_1+FP_HALF,32000,32000,16,160>
CityWalkRoute3D <(cityNearWoman3D-cityObjects3D)/SIZEOF CityProp3D,11*FP_1,27*FP_1,FP_1*63/5,21000,30000,16,155>
CityWalkRoute3D <(cityFarMan3D-cityObjects3D)/SIZEOF CityProp3D,10*FP_1,28*FP_1,FP_1*337/20,13000,34000,0,145>
CityWalkRoute3D <(cityEntryMan3D-cityObjects3D)/SIZEOF CityProp3D,20*FP_1+FP_1/5,21*FP_1+FP_1/5,9*FP_1,0,4000,0,140>
CityWalkRoute3D <(cityEntryWoman3D-cityObjects3D)/SIZEOF CityProp3D,23*FP_1+FP_1*4/5,24*FP_1+FP_1*4/5,9*FP_1,4000,5200,16,160>
CityWalkRoute3D <(cityFarMan3D-cityObjects3D)/SIZEOF CityProp3D,10*FP_1,28*FP_1,16*FP_1+FP_HALF,13000,34000,0,145>
CityWalkRoute3D <(cityEntryMan3D-cityObjects3D)/SIZEOF CityProp3D,11*FP_1,29*FP_1,FP_1*253/20,4000,31000,0,140>
CityWalkRoute3D <(cityEntryWoman3D-cityObjects3D)/SIZEOF CityProp3D,12*FP_1,30*FP_1,16*FP_1+FP_HALF,18000,30000,16,160>
IFDEF CASINO_ACTION_TEST
cityVisiblePixels3D dd CITY_PROP_COUNT dup(0)
ENDIF

File diff suppressed because it is too large Load diff

View file

@ -7,6 +7,7 @@ InitNativeBlackjack3D PROC
mov cbDealerCount3D, 0
mov cbStake3D, 0
mov cbError3D, 0
call InitBJParty3D
call ClearKeys3D
ret
InitNativeBlackjack3D ENDP
@ -59,6 +60,8 @@ NativeBJSettle3D PROC result:DWORD
mov cbVisible3D, 1
mov eax, result
mov cbOutcome3D, eax
call ResolveBJParty3D
mov eax, result
cmp eax, 1
je bj_settle_loss
mov eax, cbStake3D
@ -78,6 +81,14 @@ bj_settle_loss:
invoke PlayCasinoSound3D, OFFSET soundRoundLose, 0, 1
bj_settle_save:
call ClearKeys3D
xor eax,eax
cmp cbOutcome3D,0
sete al
cmp cbOutcome3D,3
jne @F
mov eax,1
@@:
invoke NpcRecordRound3D,1,eax
call SaveProgress3D
bj_settle_done:
ret
@ -96,6 +107,9 @@ StartNativeBlackjack3D PROC
mov cbStake3D, eax
mov cbError3D, 0
mov cbDouble3D, 0
mov cbEarlyOutcome3D, -1
mov cbBotsSettled3D, 0
mov cbInitialFlights3D, 4
mov cbVisible3D, 0
mov cbPlayerCount3D, 2
mov cbDealerCount3D, 2
@ -109,6 +123,10 @@ StartNativeBlackjack3D PROC
mov cbPlayer3D[4], eax
mov eax, cbDeck3D[12]
mov cbDealer3D[4], eax
cmp cbBotCount3D, 0
je @F
call DealBJParty3D
@@:
invoke NativeBJScore3D, OFFSET cbPlayer3D, 2
mov cbPlayerScore3D, eax
invoke NativeBJScore3D, OFFSET cbDealer3D, 2
@ -130,6 +148,8 @@ NativeBJHit3D PROC uses ebx
jne bj_hit_done
cmp cbPlayerCount3D, 12
jae bj_hit_done
cmp cbCursor3D, 52
jae bj_hit_done
mov ebx, cbCursor3D
mov eax, cbDeck3D[ebx*4]
mov cbCard3D, eax
@ -146,6 +166,7 @@ HandleNativeBlackjack3D PROC uses ebx
call HandleCardExit3D
test eax, eax
jnz bj_input_done
call BJPartyInput3D
invoke CardTableMouse3D, MODE_BLACKJACK
cmp cardExitActive3D, 0
jne bj_input_done
@ -157,11 +178,18 @@ HandleNativeBlackjack3D PROC uses ebx
je bj_ready_input
cmp cbState3D, CB_PLAYER
je bj_player_input
cmp cbState3D, CB_BOTS
jne @F
call UpdateBJParty3D
ret
@@:
mov eax, deltaTime3D
add cbTimer3D, eax
cmp cbState3D, CB_DEAL
jne bj_animate_hit
cmp cbTimer3D, TABLE_FLIGHT_MS*4
mov eax, cbInitialFlights3D
imul eax, TABLE_FLIGHT_MS
cmp cbTimer3D, eax
jb bj_input_done
cmp cbDealerScore3D, 21
jne bj_player_natural
@ -175,7 +203,7 @@ HandleNativeBlackjack3D PROC uses ebx
bj_player_natural:
cmp cbPlayerScore3D, 21
jne bj_begin_player
invoke NativeBJSettle3D, 3
invoke FinishBJPlayer3D, 3
ret
bj_begin_player:
mov cbState3D, CB_PLAYER
@ -199,7 +227,7 @@ bj_animate_hit:
jne bj_begin_dealer
jmp bj_begin_player
bj_player_bust:
invoke NativeBJSettle3D, 1
invoke FinishBJPlayer3D, 1
ret
bj_animate_dealer:
cmp cbTimer3D, 600
@ -208,6 +236,8 @@ bj_animate_dealer:
jae bj_compare_hands
cmp cbDealerCount3D, 12
jae bj_compare_hands
cmp cbCursor3D, 52
jae bj_compare_hands
mov ebx, cbCursor3D
mov eax, cbDeck3D[ebx*4]
inc cbCursor3D
@ -220,6 +250,11 @@ bj_animate_dealer:
invoke PlayCasinoSound3D, OFFSET soundCards, 0, 1
ret
bj_compare_hands:
cmp cbEarlyOutcome3D, -1
je @F
invoke NativeBJSettle3D, cbEarlyOutcome3D
ret
@@:
mov eax, cbDealerScore3D
cmp eax, 21
ja bj_player_win
@ -260,10 +295,7 @@ bj_player_input:
test eax, eax
jz bj_input_done
bj_begin_dealer:
mov cbVisible3D, 1
mov cbState3D, CB_DEALER
mov cbTimer3D, 0
call ClearKeys3D
invoke FinishBJPlayer3D, -1
ret
bj_ready_input:
invoke Pressed3D, 26h
@ -287,3 +319,4 @@ bj_ready_input:
bj_input_done:
ret
HandleNativeBlackjack3D ENDP
include src_graphics\native_blackjack_party.inc

View file

@ -0,0 +1,357 @@
InitBJParty3D PROC uses edi
invoke LoadAnimPack3D,OFFSET cbPartyRoom3D,OFFSET cbPartyRoomPath3D
cmp cbPartyRoom3D.frames,1
jne bj_party_invalid_room
cmp cbPartyRoom3D.imageWidth,SCREEN_W
jne bj_party_invalid_room
cmp cbPartyRoom3D.height,SCREEN_H
je bj_party_room_ready
bj_party_invalid_room:
invoke FreeAnimPack3D,OFFSET cbPartyRoom3D
bj_party_room_ready:
cmp cbBotCount3D,3
jbe @F
mov cbBotCount3D,0
@@:
mov cbEarlyOutcome3D,-1
mov cbBotsSettled3D,0
mov cbBotTurn3D,0
mov cbBotTimer3D,0
lea edi,cbBots3D
mov ecx,SIZEOF BJBot3D*3/4
xor eax,eax
rep stosd
invoke LoadAnimPack3D,OFFSET cbPartyArt3D,OFFSET cbPartyArtPath3D
cmp cbPartyArt3D.frames,6
jne bj_party_invalid_art
cmp cbPartyArt3D.imageWidth,160
jne bj_party_invalid_art
cmp cbPartyArt3D.height,160
jne bj_party_invalid_art
ret
bj_party_invalid_art:
call FreeBJParty3D
ret
InitBJParty3D ENDP
FreeBJParty3D PROC
invoke FreeAnimPack3D,OFFSET cbPartyArt3D
invoke FreeAnimPack3D,OFFSET cbPartyRoom3D
ret
FreeBJParty3D ENDP
; Round-robin: player, each guest, then dealer. Old solo order is unchanged.
DealBJParty3D PROC uses ebx esi edi
call InitBJParty3D
mov eax,cbBotCount3D
add eax,2
shl eax,1
mov cbInitialFlights3D,eax
mov cbCursor3D,0
xor edi,edi
bj_party_deal_pass:
mov ecx,cbCursor3D
mov eax,cbDeck3D[ecx*4]
mov cbPlayer3D[edi*4],eax
inc cbCursor3D
xor ebx,ebx
bj_party_deal_guest:
cmp ebx,cbBotCount3D
jae bj_party_deal_dealer
mov esi,ebx
imul esi,SIZEOF BJBot3D
add esi,OFFSET cbBots3D
mov ecx,cbCursor3D
mov eax,cbDeck3D[ecx*4]
mov [esi+edi*4].BJBot3D.cards,eax
inc cbCursor3D
mov [esi].BJBot3D.count,2
mov eax,cbBet3D
mov [esi].BJBot3D.stake,eax
inc ebx
jmp bj_party_deal_guest
bj_party_deal_dealer:
mov ecx,cbCursor3D
mov eax,cbDeck3D[ecx*4]
mov cbDealer3D[edi*4],eax
inc cbCursor3D
inc edi
cmp edi,2
jb bj_party_deal_pass
xor ebx,ebx
bj_party_deal_score:
cmp ebx,cbBotCount3D
jae bj_party_deal_done
mov esi,ebx
imul esi,SIZEOF BJBot3D
add esi,OFFSET cbBots3D
invoke NativeBJScore3D,esi,2
mov [esi].BJBot3D.score,eax
inc ebx
jmp bj_party_deal_score
bj_party_deal_done:
ret
DealBJParty3D ENDP
BeginBJDealer3D PROC
mov cbVisible3D,1
mov cbState3D,CB_DEALER
mov cbTimer3D,0
call ClearKeys3D
ret
BeginBJDealer3D ENDP
; Keep the table alive after a player bust/natural so other seats finish.
FinishBJPlayer3D PROC pending:DWORD
mov eax,pending
mov cbEarlyOutcome3D,eax
cmp cbBotCount3D,0
je bj_party_solo_finish
mov cbBotTurn3D,0
mov cbBotTimer3D,0
mov cbState3D,CB_BOTS
call ClearKeys3D
ret
bj_party_solo_finish:
cmp pending,-1
jne @F
call BeginBJDealer3D
ret
@@:
invoke NativeBJSettle3D,pending
ret
FinishBJPlayer3D ENDP
; No hidden dealer card, other hands, future deck cards or RNG are consulted.
BJBotDecision3D PROC uses ebx esi slot:DWORD
cmp slot,3
jae bj_bot_stand
mov esi,slot
imul esi,SIZEOF BJBot3D
add esi,OFFSET cbBots3D
cmp [esi].BJBot3D.count,12
jae bj_bot_stand
mov eax,cbDealer3D
xor edx,edx
mov ecx,13
div ecx
lea ebx,[edx+2]
cmp ebx,11
jbe @F
mov ebx,10
@@:
cmp edx,12
jne @F
mov ebx,11
@@:
mov eax,[esi].BJBot3D.score
cmp eax,21
jae bj_bot_stand
cmp [esi].BJBot3D.count,2
jne bj_bot_threshold
cmp eax,11
jne bj_bot_threshold
cmp ebx,10
jae bj_bot_threshold
mov eax,2
ret
bj_bot_threshold:
mov ecx,17
cmp slot,1
jne @F
mov ecx,19
@@:
cmp slot,2
jne @F
cmp ebx,7
jae @F
mov ecx,13
@@:
cmp eax,ecx
jae bj_bot_stand
mov eax,1
ret
bj_bot_stand:
xor eax,eax
ret
BJBotDecision3D ENDP
UpdateBJParty3D PROC uses ebx esi
mov eax,deltaTime3D
cmp eax,250
jbe @F
mov eax,250
@@:
add cbBotTimer3D,eax
mov ebx,cbBotTurn3D
cmp ebx,cbBotCount3D
jae bj_bot_finish_table
mov esi,ebx
imul esi,SIZEOF BJBot3D
add esi,OFFSET cbBots3D
cmp [esi].BJBot3D.state,BJ_BOT_FLIGHT
je bj_bot_commit
cmp cbBotTimer3D,BJ_PARTY_MS
jb bj_bot_tick_done
invoke BJBotDecision3D,ebx
test eax,eax
jz bj_bot_finish_seat
cmp cbCursor3D,52
jae bj_bot_finish_seat
cmp eax,2
jne @F
shl [esi].BJBot3D.stake,1
@@:
mov ecx,cbCursor3D
mov eax,cbDeck3D[ecx*4]
mov cbBotCard3D,eax
inc cbCursor3D
mov cbBotTimer3D,0
mov [esi].BJBot3D.state,BJ_BOT_FLIGHT
invoke PlayCasinoSound3D,OFFSET soundCards,0,1
ret
bj_bot_commit:
cmp cbBotTimer3D,TABLE_FLIGHT_MS
jb bj_bot_tick_done
mov ecx,[esi].BJBot3D.count
cmp ecx,12
jae bj_bot_finish_seat
mov eax,cbBotCard3D
mov [esi+ecx*4].BJBot3D.cards,eax
inc [esi].BJBot3D.count
invoke NativeBJScore3D,esi,[esi].BJBot3D.count
mov [esi].BJBot3D.score,eax
cmp eax,21
jae bj_bot_finish_seat
mov eax,cbBet3D
cmp [esi].BJBot3D.stake,eax
ja bj_bot_finish_seat
mov [esi].BJBot3D.state,BJ_BOT_WAIT
mov cbBotTimer3D,0
ret
bj_bot_finish_seat:
mov [esi].BJBot3D.state,BJ_BOT_DONE
inc cbBotTurn3D
mov cbBotTimer3D,0
ret
bj_bot_finish_table:
call BeginBJDealer3D
bj_bot_tick_done:
ret
UpdateBJParty3D ENDP
ResolveBJParty3D PROC uses ebx esi
cmp cbBotsSettled3D,0
jne bj_bot_resolve_done
mov cbBotsSettled3D,1
xor ebx,ebx
bj_bot_resolve:
cmp ebx,cbBotCount3D
jae bj_bot_resolve_done
mov esi,ebx
imul esi,SIZEOF BJBot3D
add esi,OFFSET cbBots3D
mov [esi].BJBot3D.state,BJ_BOT_DONE
mov eax,1
mov ecx,[esi].BJBot3D.score
cmp ecx,21
ja bj_bot_result
cmp cbDealerScore3D,21
jne bj_bot_no_dealer_natural
cmp cbDealerCount3D,2
jne bj_bot_no_dealer_natural
cmp ecx,21
jne bj_bot_result
cmp [esi].BJBot3D.count,2
jne bj_bot_result
mov eax,2
jmp bj_bot_result
bj_bot_no_dealer_natural:
cmp ecx,21
jne bj_bot_compare
cmp [esi].BJBot3D.count,2
jne bj_bot_compare
mov eax,3
jmp bj_bot_result
bj_bot_compare:
cmp cbDealerScore3D,21
ja bj_bot_won
cmp ecx,cbDealerScore3D
jb bj_bot_result
je bj_bot_pushed
bj_bot_won:
xor eax,eax
jmp bj_bot_result
bj_bot_pushed:
mov eax,2
bj_bot_result:
mov [esi].BJBot3D.outcome,eax
inc ebx
jmp bj_bot_resolve
bj_bot_resolve_done:
ret
ResolveBJParty3D ENDP
SetBJPartyCount3D PROC count:DWORD
cmp count,3
ja bj_party_count_done
cmp cbState3D,CB_IDLE
je @F
cmp cbState3D,CB_RESULT
jne bj_party_count_done
@@:
mov eax,count
cmp eax,cbBotCount3D
je bj_party_count_done
mov cbBotCount3D,eax
mov cbState3D,CB_IDLE
mov cbPlayerCount3D,0
mov cbDealerCount3D,0
mov cbStake3D,0
mov cbError3D,0
call InitBJParty3D
bj_party_count_done:
ret
SetBJPartyCount3D ENDP
BJPartyInput3D PROC uses ebx
cmp cbState3D,CB_IDLE
je @F
cmp cbState3D,CB_RESULT
jne bj_party_input_done
@@:
xor ebx,ebx
bj_party_key:
lea eax,[ebx+30h]
invoke Pressed3D,eax
test eax,eax
jz @F
invoke SetBJPartyCount3D,ebx
ret
@@:
inc ebx
cmp ebx,4
jb bj_party_key
cmp mouseClick3D,0
je bj_party_input_done
cmp mouseY3D,35
jb bj_party_input_done
cmp mouseY3D,51
jae bj_party_input_done
mov eax,mouseX3D
sub eax,212
jc bj_party_input_done
cmp eax,100
jae bj_party_input_done
xor edx,edx
mov ecx,25
div ecx
cmp edx,22
jae bj_party_input_done
invoke SetBJPartyCount3D,eax
mov mouseClick3D,0
bj_party_input_done:
ret
BJPartyInput3D ENDP
include src_graphics\native_blackjack_party_render.inc

View file

@ -0,0 +1,60 @@
; Independent blackjack seats share one deck, never the player's wallet.
CB_BOTS equ 6
BJ_BOT_WAIT equ 0
BJ_BOT_FLIGHT equ 1
BJ_BOT_DONE equ 2
BJ_PARTY_MS equ 550
BJBot3D STRUCT
cards dd 12 dup(0)
count dd 0
score dd 0
stake dd 0
outcome dd 0
state dd 0
BJBot3D ENDS
InitBJParty3D PROTO
FreeBJParty3D PROTO
DealBJParty3D PROTO
BeginBJDealer3D PROTO
FinishBJPlayer3D PROTO :DWORD
UpdateBJParty3D PROTO
BJBotDecision3D PROTO :DWORD
ResolveBJParty3D PROTO
BJPartyInput3D PROTO
SetBJPartyCount3D PROTO :DWORD
RenderBJParty3D PROTO
DrawBJPartySelector3D PROTO
DrawBJMiniCard3D PROTO :DWORD,:DWORD,:DWORD,:DWORD
DrawBJMiniFlight3D PROTO :DWORD,:DWORD,:DWORD,:DWORD
.data
cbBotCount3D dd 0
cbBots3D BJBot3D 3 dup(<>)
cbBotTurn3D dd 0
cbBotTimer3D dd 0
cbBotCard3D dd 0
cbEarlyOutcome3D dd -1
cbBotsSettled3D dd 0
cbInitialFlights3D dd 4
cbPartyArt3D AnimPack3D <>
cbPartyArtPath3D db "assets\animations\tables\blackjack-guests-v1.bin",0
cbPartyRoom3D AnimPack3D <>
cbPartyRoomPath3D db "assets\animations\tables\blackjack-room-v1.bin",0
cbPartyNames3D dd OFFSET cbPartyName0,OFFSET cbPartyName1,OFFSET cbPartyName2
cbPartyName0 db 210,206,203,223,0
cbPartyName1 db 209,197,208,195,197,201,0
cbPartyName2 db 192,203,200,209,192,0
cbBotsLabel3D db 193,206,210,219,0
cbBotWaitText3D db 198,196,168,210,0
cbBotHitText3D db 202,192,208,210,192,0
cbBotStandText3D db 213,194,192,210,200,210,0
cbBotBustText3D db 207,197,208,197,193,206,208,0
cbBotWinText3D db 207,206,193,197,196,192,0
cbBotLoseText3D db 207,208,206,200,195,208,219,216,0
cbBotPushText3D db 205,200,215,220,223,0
cbBotNaturalText3D db 193,203,221,202,196,198,197,202,0
cbBotResults3D dd OFFSET cbBotWinText3D,OFFSET cbBotLoseText3D,OFFSET cbBotPushText3D,OFFSET cbBotNaturalText3D
cbDealerLabel3D db 196,200,203,197,208,0
cbPartyPlayerText3D db 194,219,0
cbPartyDigit3D db "0",0,"1",0,"2",0,"3",0
cbPartyX3D dd 8,120,232
.code

View file

@ -0,0 +1,407 @@
DrawBJPartySelector3D PROC uses ebx esi edi
xor edi,edi
cmp cbState3D,CB_IDLE
je @F
cmp cbState3D,CB_RESULT
jne bj_party_selector
@@:
mov edi,1
bj_party_selector:
invoke DrawString8x8,176,39,OFFSET cbBotsLabel3D,00D4C5A4h
xor ebx,ebx
bj_party_selector_loop:
mov esi,ebx
imul esi,25
add esi,212
mov ecx,00454D48h
test edi,edi
jz @F
mov ecx,00D1B16Eh
@@:
mov eax,00202D26h
cmp ebx,cbBotCount3D
jne @F
mov eax,003B6C59h
@@:
invoke DrawBoxBorder,esi,35,22,16,eax,ecx,1
lea edx,cbPartyDigit3D[ebx*2]
lea eax,[esi+7]
invoke DrawString8x8,eax,39,edx,00F2E9D3h
inc ebx
cmp ebx,4
jb bj_party_selector_loop
ret
DrawBJPartySelector3D ENDP
; Smaller guest cards retain an unambiguous rank and suit, never fake identities.
DrawBJMiniCard3D PROC uses ebx esi x:DWORD,y:DWORD,card:DWORD,hidden:DWORD
local ink:DWORD,rank:DWORD,suit:DWORD
mov ecx,00EEEDE2h
cmp hidden,0
je @F
mov ecx,004A2941h
@@:
invoke DrawBoxBorder,x,y,20,30,ecx,00BCA878h,1
cmp hidden,0
jne bj_mini_back
mov eax,card
xor edx,edx
mov ecx,13
div ecx
mov suit,eax
mov rank,edx
mov ink,00191F24h
cmp eax,1
je @F
cmp eax,2
jne bj_mini_face
@@:
mov ink,00BF263Ch
bj_mini_face:
lea edx,cardRankNames3D[edx*2]
mov eax,x
add eax,3
mov ecx,y
add ecx,3
invoke DrawString8x8,eax,ecx,edx,ink
mov eax,x
add eax,6
mov edx,y
add edx,17
invoke DrawNativeSuit3D,eax,edx,suit,ink
ret
bj_mini_back:
mov eax,x
add eax,4
mov edx,y
add edx,4
invoke DrawBoxBorder,eax,edx,12,22,004A2941h,00C9B589h,1
ret
DrawBJMiniCard3D ENDP
DrawBJMiniFlight3D PROC uses ebx x:DWORD,y:DWORD,card:DWORD,timeMs:DWORD
local px:DWORD,py:DWORD
mov ebx,timeMs
cmp ebx,TABLE_FLIGHT_MS
jbe @F
mov ebx,TABLE_FLIGHT_MS
@@:
mov eax,x
sub eax,170
imul eax,ebx
cdq
mov ecx,TABLE_FLIGHT_MS
idiv ecx
add eax,170
mov px,eax
mov eax,y
sub eax,24
imul eax,ebx
cdq
idiv ecx
add eax,24
mov py,eax
invoke QueueTableHand3D,px,py,timeMs,0
invoke DrawBJMiniCard3D,px,py,card,0
ret
DrawBJMiniFlight3D ENDP
RenderBJParty3D PROC uses ebx esi edi
local guest:DWORD,stepX:DWORD,px:DWORD,pose:DWORD,shown:DWORD,slide:DWORD,cardId:DWORD,hide:DWORD
call RenderCardTable3D
cmp cbPartyRoom3D.pixels,0
je @F
mov esi,cbPartyRoom3D.pixels
lea edi,screenBuffer
mov ecx,SCREEN_SIZE
rep movsd
@@:
invoke DrawBox,0,0,320,52,00151D20h
invoke DrawString8x8,8,6,OFFSET cbTitle3D,00E8D7ADh
invoke DrawString8x8,8,20,OFFSET cbDealerLabel3D,00D1B16Eh
invoke DrawString8x8,192,20,OFFSET textBank3D,00A8DDB9h
invoke FormatNumber,bankroll,OFFSET hudNumBuffer
invoke DrawString8x8,240,20,OFFSET hudNumBuffer,00F4F3E8h
call DrawBJPartySelector3D
xor edi,edi
bj_party_render_guest:
cmp edi,cbBotCount3D
jae bj_party_render_dealer
mov esi,edi
imul esi,SIZEOF BJBot3D
add esi,OFFSET cbBots3D
mov guest,esi
mov ebx,cbPartyX3D[edi*4]
invoke DrawString8x8,ebx,54,cbPartyNames3D[edi*4],00E8D7ADh
mov pose,edi
cmp cbState3D,CB_BOTS
jne @F
cmp edi,cbBotTurn3D
jne @F
add pose,3
@@:
mov eax,ebx
add eax,4
shl eax,1
invoke DrawAnimSprite3D,OFFSET cbPartyArt3D,pose,eax,112,144,144
cmp cbState3D,CB_IDLE
je bj_party_next_guest
cmp cbState3D,CB_DEAL
je @F
invoke FormatNumber,[esi].BJBot3D.score,OFFSET hudNumBuffer
lea eax,[ebx+64]
invoke DrawString8x8,eax,54,OFFSET hudNumBuffer,00EDDFB1h
@@:
mov eax,[esi].BJBot3D.count
mov shown,eax
cmp cbState3D,CB_BOTS
jne @F
cmp edi,cbBotTurn3D
jne @F
cmp [esi].BJBot3D.state,BJ_BOT_FLIGHT
jne @F
inc shown
inc eax
@@:
mov stepX,16
cmp eax,4
jbe @F
dec eax
mov ecx,eax
mov eax,60
xor edx,edx
div ecx
mov stepX,eax
@@:
xor ebx,ebx
bj_party_guest_card:
mov eax,ebx
imul eax,stepX
add eax,cbPartyX3D[edi*4]
mov px,eax
cmp ebx,[esi].BJBot3D.count
jb bj_party_guest_existing
invoke DrawBJMiniFlight3D,px,124,cbBotCard3D,cbBotTimer3D
jmp bj_party_guest_next_card
bj_party_guest_existing:
mov edx,[esi+ebx*4].BJBot3D.cards
mov cardId,edx
cmp cbState3D,CB_DEAL
jne bj_party_guest_fixed
mov eax,cbBotCount3D
add eax,2
imul eax,ebx
lea eax,[eax+edi+1]
imul eax,TABLE_FLIGHT_MS
mov ecx,cbTimer3D
sub ecx,eax
js bj_party_guest_next_card
invoke DrawBJMiniFlight3D,px,124,cardId,ecx
jmp bj_party_guest_next_card
bj_party_guest_fixed:
invoke DrawBJMiniCard3D,px,124,cardId,0
bj_party_guest_next_card:
inc ebx
cmp ebx,shown
jb bj_party_guest_card
mov ecx,OFFSET cbBotWaitText3D
cmp cbState3D,CB_RESULT
jne bj_party_guest_live_status
mov eax,[esi].BJBot3D.outcome
mov ecx,cbBotResults3D[eax*4]
jmp bj_party_guest_status
bj_party_guest_live_status:
cmp [esi].BJBot3D.state,BJ_BOT_DONE
jne @F
mov ecx,OFFSET cbBotStandText3D
@@:
cmp [esi].BJBot3D.score,21
jbe @F
mov ecx,OFFSET cbBotBustText3D
@@:
cmp cbState3D,CB_BOTS
jne bj_party_guest_status
cmp edi,cbBotTurn3D
jne bj_party_guest_status
mov ecx,OFFSET cbBotHitText3D
bj_party_guest_status:
mov eax,cbPartyX3D[edi*4]
invoke DrawString8x8,eax,158,ecx,00DCE7DBh
bj_party_next_guest:
inc edi
jmp bj_party_render_guest
bj_party_render_dealer:
cmp cbState3D,CB_IDLE
je bj_party_render_controls
mov stepX,22
cmp cbDealerCount3D,5
jbe @F
mov eax,cbDealerCount3D
dec eax
mov ecx,eax
mov eax,100
xor edx,edx
div ecx
mov stepX,eax
@@:
xor ebx,ebx
bj_party_dealer_card:
mov eax,ebx
imul eax,stepX
add eax,64
mov px,eax
mov edx,cbDealer3D[ebx*4]
mov cardId,edx
mov hide,0
cmp ebx,1
jne @F
cmp cbVisible3D,0
jne @F
mov hide,1
@@:
cmp cbState3D,CB_DEAL
jne bj_party_dealer_fixed
mov eax,cbBotCount3D
add eax,2
imul eax,ebx
add eax,cbBotCount3D
inc eax
imul eax,TABLE_FLIGHT_MS
mov ecx,cbTimer3D
sub ecx,eax
js bj_party_dealer_next
; Initial dealer cards stay inside the header, with the hole card hidden.
bj_party_dealer_fixed:
invoke DrawBJMiniCard3D,px,20,cardId,hide
bj_party_dealer_next:
inc ebx
cmp ebx,cbDealerCount3D
jb bj_party_dealer_card
xor ebx,ebx
mov shown,0
cmp cbState3D,CB_HIT
jne @F
mov shown,1
@@:
mov eax,cbPlayerCount3D
add shown,eax
mov stepX,40
cmp shown,4
jbe @F
mov eax,shown
dec eax
mov ecx,eax
mov eax,126
xor edx,edx
div ecx
mov stepX,eax
@@:
mov eax,shown
dec eax
imul eax,stepX
add eax,34
mov edx,320
sub edx,eax
shr edx,1
mov slide,edx
bj_party_player_card:
mov eax,ebx
imul eax,stepX
add eax,slide
mov px,eax
cmp ebx,cbPlayerCount3D
jb bj_party_player_existing
invoke DrawSlidingCard3D,px,168,cbCard3D,0,cbTimer3D
jmp bj_party_player_next
bj_party_player_existing:
mov edx,cbPlayer3D[ebx*4]
mov cardId,edx
cmp cbState3D,CB_DEAL
jne bj_party_player_fixed
mov eax,cbBotCount3D
add eax,2
imul eax,ebx
imul eax,TABLE_FLIGHT_MS
mov ecx,cbTimer3D
sub ecx,eax
js bj_party_player_next
invoke DrawSlidingCard3D,px,168,cardId,0,ecx
jmp bj_party_player_next
bj_party_player_fixed:
invoke DrawNativeCard3D,px,168,cardId,0,34
bj_party_player_next:
inc ebx
cmp ebx,shown
jb bj_party_player_card
invoke DrawString8x8,64,174,OFFSET cbPartyPlayerText3D,00E8D7ADh
invoke FormatNumber,cbPlayerScore3D,OFFSET hudNumBuffer
invoke DrawString8x8,64,188,OFFSET hudNumBuffer,00EDDFB1h
bj_party_render_controls:
call DrawQueuedTableHand3D
; UI labels stay above animated hands instead of disappearing during a deal.
invoke DrawBox,0,52,320,10,00151D20h
xor edi,edi
bj_party_final_names:
cmp edi,cbBotCount3D
jae bj_party_final_names_done
mov ebx,cbPartyX3D[edi*4]
invoke DrawString8x8,ebx,54,cbPartyNames3D[edi*4],00E8D7ADh
cmp cbState3D,CB_IDLE
je bj_party_final_name_next
cmp cbState3D,CB_DEAL
je bj_party_final_name_next
mov esi,edi
imul esi,SIZEOF BJBot3D
add esi,OFFSET cbBots3D
invoke FormatNumber,[esi].BJBot3D.score,OFFSET hudNumBuffer
lea eax,[ebx+64]
invoke DrawString8x8,eax,54,OFFSET hudNumBuffer,00EDDFB1h
bj_party_final_name_next:
inc edi
jmp bj_party_final_names
bj_party_final_names_done:
invoke DrawString8x8,8,174,OFFSET textBet3D,00DDCFB1h
invoke FormatNumber,cbBet3D,OFFSET hudNumBuffer
invoke DrawString8x8,8,186,OFFSET hudNumBuffer,00F6F4DDh
xor edi,edi
cmp cbState3D,CB_IDLE
je bj_party_ready
cmp cbState3D,CB_RESULT
je bj_party_ready
invoke DrawStakeControls3D,0
cmp cbState3D,CB_PLAYER
jne @F
mov edi,1
@@:
invoke DrawTableButton3D,8,OFFSET cbHitText3D,edi
invoke DrawTableButton3D,86,OFFSET cbStandText3D,edi
cmp cbPlayerCount3D,2
jne bj_party_no_double
mov eax,cbStake3D
cmp bankroll,eax
jae @F
bj_party_no_double:
xor edi,edi
@@:
invoke DrawTableButton3D,164,OFFSET cbDoubleText3D,edi
jmp bj_party_render_exit
bj_party_ready:
invoke DrawStakeControls3D,1
invoke DrawTableButton3D,125,OFFSET pkDealText3D,1
cmp cbState3D,CB_RESULT
jne bj_party_render_exit
mov eax,cbOutcome3D
mov ecx,OFFSET cbNaturalText3D
cmp eax,3
je @F
mov ecx,cbResults3D[eax*4]
@@:
invoke DrawString8x8,8,226,ecx,00EFD583h
bj_party_render_exit:
cmp cbError3D,0
je @F
invoke DrawString8x8,80,183,OFFSET textNoCash3D,00F89880h
@@:
call RenderCardExit3D
ret
RenderBJParty3D ENDP

View file

@ -0,0 +1,685 @@
FreeNativeClover3D PROC
invoke FreeAnimPack3D,OFFSET clpCabinet3D
invoke FreeAnimPack3D,OFFSET clpSymbols3D
ret
FreeNativeClover3D ENDP
InitNativeClover3D PROC
invoke LoadAnimPack3D,OFFSET clpCabinet3D,OFFSET clpCabinetPath3D
test eax,eax
jz clp_init_fail
cmp clpCabinet3D.imageWidth,640
jne clp_init_fail
cmp clpCabinet3D.height,480
jne clp_init_fail
cmp clpCabinet3D.frames,1
jne clp_init_fail
invoke LoadAnimPack3D,OFFSET clpSymbols3D,OFFSET clpSymbolsPath3D
test eax,eax
jz clp_init_fail
cmp clpSymbols3D.imageWidth,CLP_CELL
jne clp_init_fail
cmp clpSymbols3D.height,CLP_CELL
jne clp_init_fail
cmp clpSymbols3D.frames,7
jne clp_init_fail
mov clpState3D,SLOT_READY
mov clpTimer3D,0
mov clpStake3D,0
mov clpGross3D,0
mov clpTotal3D,0
mov clpCount3D,0
mov clpWinMask3D,0
mov clpSelected3D,0
mov clpPaytable3D,0
mov clpLeave3D,0
mov clpError3D,0
mov clpBet3D,50
mov clpBetIndex3D,1
mov paused3D,0
mov cardExitActive3D,0
call ClearKeys3D
mov eax,1
ret
clp_init_fail:
call FreeNativeClover3D
xor eax,eax
ret
InitNativeClover3D ENDP
; Tape contents and final board are frozen at the single stake debit.
StartNativeClover3D PROC uses ebx esi edi
cmp clpState3D,SLOT_SPIN
je clp_start_done
cmp clpPaytable3D,0
jne clp_start_done
cmp clpCabinet3D.pixels,0
je clp_start_done
cmp clpSymbols3D.pixels,0
je clp_start_done
mov eax,clpBet3D
test eax,eax
jz clp_start_done
cmp eax,10000
ja clp_start_done
cmp eax,bankroll
ja clp_start_money
sub bankroll,eax
mov clpStake3D,eax
mov clpState3D,SLOT_SPIN
mov clpTimer3D,0
mov clpGross3D,0
mov clpCount3D,0
mov clpTotal3D,0
mov clpWinMask3D,0
mov clpSelected3D,0
mov clpLeave3D,0
mov clpError3D,0
xor esi,esi
clp_start_column:
mov ebx,esi
shl ebx,8
xor edi,edi
clp_start_tape:
invoke RandomRange3D,7
mov clpTape3D[ebx+edi*4],eax
inc edi
cmp edi,64
jb clp_start_tape
xor edi,edi
clp_start_rows:
mov ecx,edi
imul ecx,5
add ecx,esi
mov eax,clpGrid3D[ecx*4]
mov clpTape3D[ebx+edi*4],eax
invoke RandomRange3D,7
mov clpGrid3D[ecx*4],eax
mov edx,clpSteps3D[esi*4]
add edx,edi
mov clpTape3D[ebx+edx*4],eax
inc edi
cmp edi,3
jb clp_start_rows
inc esi
cmp esi,5
jb clp_start_column
call SaveProgress3D
invoke PlayCasinoSound3D,OFFSET soundSlotSpin,0,1
clp_start_done:
ret
clp_start_money:
mov clpError3D,1
ret
StartNativeClover3D ENDP
; Reuse the original pure assembly evaluator, never the console game or payer.
CloverEvaluate3D PROC uses ebx esi edi
mov clpTotal3D,0
mov clpCount3D,0
mov clpWinMask3D,0
mov clpJackpot3D,0
xor esi,esi
clp_eval_validate:
cmp clpGrid3D[esi*4],6
ja clp_eval_done
inc esi
cmp esi,15
jb clp_eval_validate
lea esi,clpGrid3D
lea edi,slotGrid
mov ecx,15
rep movsd
call EvaluateSlotCombos
mov eax,slotBestMultiplier
mov clpTotal3D,eax
mov eax,slotWinMask
mov clpWinMask3D,eax
mov eax,slotJackpotHit
mov clpJackpot3D,eax
mov eax,slotComboCount
mov clpCount3D,eax
lea esi,slotComboNames
lea edi,clpNames3D
mov ecx,eax
rep movsd
lea esi,slotComboMults
lea edi,clpMults3D
mov ecx,clpCount3D
rep movsd
lea esi,slotComboMasks
lea edi,clpMasks3D
mov ecx,clpCount3D
rep movsd
clp_eval_done:
xor eax,eax
ret
CloverEvaluate3D ENDP
SettleNativeClover3D PROC
cmp clpState3D,SLOT_SPIN
jne clp_settle_done
call CloverEvaluate3D
mov eax,clpTotal3D
mul clpStake3D
mov ecx,1000000000
sub ecx,bankroll
cmp eax,ecx
jbe @F
mov eax,ecx
@@:
mov clpGross3D,eax
add bankroll,eax
mov clpState3D,SLOT_RESULT
mov clpTimer3D,3400
xor eax,eax
mov ecx,clpGross3D
cmp ecx,clpStake3D
seta al
invoke NpcRecordRound3D,4,eax
call SaveProgress3D
mov eax,clpGross3D
cmp eax,clpStake3D
jb clp_settle_loss
jne @F
invoke PlayCasinoSound3D,0,0,0
jmp clp_settle_leave
@@:
invoke PlayCasinoSound3D,OFFSET soundRoundWin,0,1
jmp clp_settle_leave
clp_settle_loss:
invoke PlayCasinoSound3D,OFFSET soundRoundLose,0,1
clp_settle_leave:
cmp clpLeave3D,0
je clp_settle_done
call CloverEscape3D
clp_settle_done:
ret
SettleNativeClover3D ENDP
CloverEscape3D PROC
mov paused3D,0
cmp clpPaytable3D,0
je @F
mov clpPaytable3D,0
call ClearKeys3D
ret
@@:
cmp clpState3D,SLOT_SPIN
jne clp_escape_hall
mov clpLeave3D,1
call ClearKeys3D
ret
clp_escape_hall:
mov clpLeave3D,0
mov engineMode,MODE_WALK
call ClearKeys3D
invoke PlayCasinoSound3D,0,0,0
ret
CloverEscape3D ENDP
CloverReelPosition3D PROC uses ebx index:DWORD
mov ebx,index
xor eax,eax
cmp ebx,4
ja clp_pos_done
cmp clpState3D,SLOT_SPIN
jne clp_pos_end
mov eax,clpTimer3D
cmp eax,clpDurations3D[ebx*4]
jae clp_pos_end
shl eax,10
xor edx,edx
div clpDurations3D[ebx*4]
mov ecx,2048
sub ecx,eax
imul eax,ecx
mov ecx,clpSteps3D[ebx*4]
imul ecx,CLP_CELL
mul ecx
shrd eax,edx,20
ret
clp_pos_end:
mov eax,clpSteps3D[ebx*4]
imul eax,CLP_CELL
clp_pos_done:
ret
CloverReelPosition3D ENDP
HandleNativeClover3D PROC uses ebx
cmp mouseClick3D,0
je clp_handle_tick
mov mouseClick3D,0
mov eax,mouseY3D
cmp eax,3
jb clp_handle_tick
cmp eax,17
jae clp_handle_mouse_controls
cmp mouseX3D,252
jb clp_handle_tick
cmp mouseX3D,312
jae clp_handle_tick
call CloverEscape3D
cmp clpState3D,SLOT_SPIN
je clp_handle_tick
ret
clp_handle_mouse_controls:
cmp clpState3D,SLOT_SPIN
je clp_handle_tick
cmp clpPaytable3D,0
je @F
cmp eax,202
jb clp_handle_tick
cmp eax,220
jae clp_handle_tick
cmp mouseX3D,112
jb clp_handle_tick
cmp mouseX3D,208
jae clp_handle_tick
mov clpPaytable3D,0
ret
@@:
cmp eax,170
jb clp_handle_tick
cmp eax,185
jae clp_handle_mouse_footer
cmp mouseX3D,232
jb clp_handle_tick
cmp mouseX3D,250
jb clp_handle_previous
cmp mouseX3D,292
jb clp_handle_tick
cmp mouseX3D,310
jb clp_handle_next
jmp clp_handle_tick
clp_handle_mouse_footer:
cmp eax,222
jb clp_handle_tick
cmp eax,238
jae clp_handle_tick
mov eax,mouseX3D
cmp eax,11
jb clp_handle_tick
cmp eax,29
jb clp_handle_down
cmp eax,35
jb clp_handle_tick
cmp eax,53
jb clp_handle_up
cmp eax,120
jb clp_handle_tick
cmp eax,190
jb clp_handle_start
cmp eax,202
jb clp_handle_tick
cmp eax,272
jb clp_handle_paytable
clp_handle_tick:
cmp clpState3D,SLOT_SPIN
jne clp_handle_keys
mov eax,deltaTime3D
cmp eax,250
jbe @F
mov eax,250
@@:
add clpTimer3D,eax
cmp clpTimer3D,3400
jb clp_handle_done
call SettleNativeClover3D
ret
clp_handle_keys:
invoke Pressed3D,50h
test eax,eax
jnz clp_handle_paytable
cmp clpPaytable3D,0
je clp_handle_game_keys
invoke Pressed3D,20h
test eax,eax
jnz clp_handle_paytable
invoke Pressed3D,0Dh
test eax,eax
jnz clp_handle_paytable
ret
clp_handle_game_keys:
invoke Pressed3D,25h
test eax,eax
jnz clp_handle_previous
invoke Pressed3D,27h
test eax,eax
jnz clp_handle_next
invoke Pressed3D,57h
test eax,eax
jnz clp_handle_up
invoke Pressed3D,26h
test eax,eax
jnz clp_handle_up
invoke Pressed3D,53h
test eax,eax
jnz clp_handle_down
invoke Pressed3D,28h
test eax,eax
jnz clp_handle_down
invoke Pressed3D,20h
test eax,eax
jnz clp_handle_start
invoke Pressed3D,0Dh
test eax,eax
jz clp_handle_done
clp_handle_start:
call StartNativeClover3D
ret
clp_handle_paytable:
xor clpPaytable3D,1
ret
clp_handle_previous:
cmp clpCount3D,0
je clp_handle_done
cmp clpSelected3D,0
jne @F
mov eax,clpCount3D
mov clpSelected3D,eax
@@:
dec clpSelected3D
ret
clp_handle_next:
cmp clpCount3D,0
je clp_handle_done
inc clpSelected3D
mov eax,clpSelected3D
cmp eax,clpCount3D
jb clp_handle_done
mov clpSelected3D,0
ret
clp_handle_up:
cmp clpBetIndex3D,6
jae clp_handle_done
inc clpBetIndex3D
jmp clp_handle_bet
clp_handle_down:
cmp clpBetIndex3D,0
je clp_handle_done
dec clpBetIndex3D
clp_handle_bet:
mov ecx,clpBetIndex3D
mov eax,slotBetValues3D[ecx*4]
mov clpBet3D,eax
mov clpError3D,0
clp_handle_done:
ret
HandleNativeClover3D ENDP
DrawCloverSymbol3D PROC uses ebx esi edi symbol:DWORD,x:DWORD,y:DWORD
local row:DWORD,lastRow:DWORD,source:DWORD
cmp symbol,6
ja clp_symbol_done
mov eax,clpSymbols3D.pixels
test eax,eax
jz clp_symbol_done
mov ecx,symbol
imul ecx,CLP_CELL*CLP_CELL*4
add eax,ecx
mov source,eax
mov eax,y
cmp eax,CLP_TOP
jge @F
mov eax,CLP_TOP
@@:
mov row,eax
mov eax,y
add eax,CLP_CELL
cmp eax,CLP_BOTTOM
jle @F
mov eax,CLP_BOTTOM
@@:
mov lastRow,eax
clp_symbol_row:
mov eax,row
cmp eax,lastRow
jge clp_symbol_done
mov ecx,eax
sub ecx,y
imul ecx,CLP_CELL*4
mov esi,source
add esi,ecx
imul eax,640*4
mov ecx,x
lea edi,screenBuffer[eax+ecx*4]
mov ecx,CLP_CELL
clp_symbol_column:
lodsd
test eax,0FF000000h
jz @F
and eax,00FFFFFFh
mov [edi],eax
@@:
add edi,4
dec ecx
jnz clp_symbol_column
inc row
jmp clp_symbol_row
clp_symbol_done:
ret
DrawCloverSymbol3D ENDP
DrawCloverReel3D PROC uses ebx esi edi index:DWORD
local symbolY:DWORD,baseStop:DWORD,columnX:DWORD
mov ebx,index
mov eax,clpColumns3D[ebx*4]
mov columnX,eax
cmp clpState3D,SLOT_SPIN
jne clp_reel_static
invoke CloverReelPosition3D,ebx
xor edx,edx
mov ecx,CLP_CELL
div ecx
mov baseStop,eax
mov eax,CLP_TOP
sub eax,edx
mov symbolY,eax
shl ebx,8
xor esi,esi
clp_reel_tape:
mov ecx,baseStop
add ecx,esi
mov eax,clpTape3D[ebx+ecx*4]
invoke DrawCloverSymbol3D,eax,columnX,symbolY
add symbolY,CLP_CELL
inc esi
cmp esi,4
jb clp_reel_tape
ret
clp_reel_static:
mov symbolY,CLP_TOP
xor esi,esi
clp_reel_rows:
mov ecx,esi
imul ecx,5
add ecx,ebx
mov eax,clpGrid3D[ecx*4]
invoke DrawCloverSymbol3D,eax,columnX,symbolY
add symbolY,CLP_CELL
inc esi
cmp esi,3
jb clp_reel_rows
ret
DrawCloverReel3D ENDP
CloverComboText3D PROC index:DWORD
mov ecx,index
cmp ecx,clpCount3D
jae clp_combo_none
mov edx,clpNames3D[ecx*4]
xor ecx,ecx
clp_combo_lookup:
cmp edx,clpLegacyNames3D[ecx*4]
je clp_combo_found
inc ecx
cmp ecx,11
jb clp_combo_lookup
clp_combo_none:
mov eax,OFFSET clpNoCombo3D
ret
clp_combo_found:
mov eax,clpShapeNames3D[ecx*4]
ret
CloverComboText3D ENDP
RenderNativeClover3D PROC uses ebx esi edi
local cellX:DWORD,cellY:DWORD,selectedMask:DWORD
mov esi,clpCabinet3D.pixels
test esi,esi
jz clp_render_done
lea edi,screenBuffer
mov ecx,SCREEN_SIZE
rep movsd
xor esi,esi
clp_render_reels:
mov ebx,clpColumns3D[esi*4]
shr ebx,1
invoke DrawBoxBorder,ebx,63,28,84,000D1816h,00405A46h,1
invoke DrawCloverReel3D,esi
inc esi
cmp esi,5
jb clp_render_reels
cmp clpState3D,SLOT_RESULT
jne clp_render_header
mov ecx,clpSelected3D
cmp ecx,clpCount3D
jae clp_render_header
mov eax,clpMasks3D[ecx*4]
mov selectedMask,eax
xor esi,esi
clp_render_highlight:
mov eax,1
mov ecx,esi
shl eax,cl
test selectedMask,eax
jz clp_render_next_cell
mov eax,esi
xor edx,edx
mov ecx,5
div ecx
imul eax,28
add eax,63
mov cellY,eax
mov ebx,clpColumns3D[edx*4]
shr ebx,1
mov cellX,ebx
; Border strips do not paint over the transparent symbol interior.
invoke DrawBox,ebx,cellY,28,1,00E9CE71h
mov eax,cellY
add eax,27
invoke DrawBox,ebx,eax,28,1,00E9CE71h
invoke DrawBox,ebx,cellY,1,28,00E9CE71h
add ebx,27
invoke DrawBox,ebx,cellY,1,28,00E9CE71h
clp_render_next_cell:
inc esi
cmp esi,15
jb clp_render_highlight
clp_render_header:
invoke DrawBox,0,0,320,19,00151D20h
invoke DrawString8x8,8,6,OFFSET clpTitle3D,00E8D7ADh
invoke DrawString8x8,108,6,OFFSET textBank3D,00A8DDB9h
invoke FormatNumber,bankroll,OFFSET hudNumBuffer
invoke DrawString8x8,157,6,OFFSET hudNumBuffer,00F4F3E8h
invoke DrawBoxBorder,252,3,60,14,002C3637h,00687672h,1
invoke DrawString8x8,258,6,OFFSET cardExitButton3D,00E9D8B0h
invoke DrawBox,63,160,194,26,00151D20h
mov ebx,OFFSET slotReadyText3D
cmp clpState3D,SLOT_SPIN
jne @F
mov ebx,OFFSET slotRollingText3D
@@:
cmp clpState3D,SLOT_RESULT
jne @F
mov ebx,OFFSET clpNoCombo3D
cmp clpCount3D,0
je @F
mov ebx,OFFSET clpTotalText3D
@@:
cmp clpError3D,0
je @F
mov ebx,OFFSET slotNoMoney3D
@@:
cmp clpLeave3D,0
je @F
mov ebx,OFFSET slotExitText3D
@@:
invoke DrawString8x8,72,163,ebx,00E8D7ADh
cmp clpState3D,SLOT_RESULT
jne clp_render_footer
cmp clpCount3D,0
je clp_render_footer
invoke DrawString8x8,208,163,OFFSET strMultiplier3D,00A8DDB9h
invoke FormatNumber,clpTotal3D,OFFSET hudNumBuffer
invoke DrawString8x8,216,163,OFFSET hudNumBuffer,00A8DDB9h
invoke CloverComboText3D,clpSelected3D
invoke DrawString8x8,72,175,eax,00F4F3E8h
mov ecx,clpSelected3D
invoke FormatNumber,clpMults3D[ecx*4],OFFSET hudNumBuffer
invoke DrawString8x8,208,175,OFFSET strMultiplier3D,00E9CE71h
invoke DrawString8x8,216,175,OFFSET hudNumBuffer,00E9CE71h
invoke DrawBoxBorder,232,170,18,15,0025372Ch,00E9CE71h,1
invoke DrawBoxBorder,292,170,18,15,0025372Ch,00E9CE71h,1
invoke DrawString8x8,237,174,OFFSET clpLeft3D,00E9CE71h
invoke DrawString8x8,297,174,OFFSET clpRight3D,00E9CE71h
mov eax,clpSelected3D
inc eax
invoke FormatNumber,eax,OFFSET hudNumBuffer
invoke DrawString8x8,252,174,OFFSET hudNumBuffer,00F4F3E8h
invoke DrawString8x8,268,174,OFFSET clpSlash3D,00F4F3E8h
invoke FormatNumber,clpCount3D,OFFSET hudNumBuffer
invoke DrawString8x8,276,174,OFFSET hudNumBuffer,00F4F3E8h
clp_render_footer:
invoke DrawBox,0,198,320,42,00151D20h
invoke DrawString8x8,11,201,OFFSET slotBetText3D,00E8D7ADh
invoke FormatNumber,clpBet3D,OFFSET hudNumBuffer
invoke DrawString8x8,75,201,OFFSET hudNumBuffer,00F4F3E8h
cmp clpState3D,SLOT_RESULT
jne @F
invoke DrawSlotTotals3D,clpGross3D,clpStake3D,120
@@:
mov ebx,1
cmp clpState3D,SLOT_SPIN
jne @F
xor ebx,ebx
@@:
invoke DrawBoxBorder,11,222,18,16,0025372Ch,00EEE5CDh,1
invoke DrawBoxBorder,35,222,18,16,0025372Ch,00EEE5CDh,1
invoke DrawString8x8,16,226,OFFSET tableMinusText3D,00EEE5CDh
invoke DrawString8x8,40,226,OFFSET tablePlusText3D,00EEE5CDh
invoke DrawTableButton3D,120,OFFSET slotSpinText3D,ebx
invoke DrawTableButton3D,202,OFFSET slotPayText3D,ebx
cmp clpPaytable3D,0
je clp_render_done
invoke DrawBoxBorder,18,22,284,200,00151D20h,00D1B16Eh,1
invoke DrawString8x8,76,29,OFFSET slotPayTitle3D,00E8D7ADh
invoke DrawString8x8,28,44,OFFSET clpSymbolsText3D,00A8DDB9h
invoke DrawString8x8,164,44,OFFSET clpShapesText3D,00A8DDB9h
xor esi,esi
clp_render_pay_row:
mov edi,esi
imul edi,11
add edi,58
mov eax,clpShapeNames3D[esi*4]
invoke DrawString8x8,156,edi,eax,00F4F3E8h
invoke FormatNumber,clpShapeFactors3D[esi*4],OFFSET hudNumBuffer
invoke DrawString8x8,280,edi,OFFSET hudNumBuffer,00E8D7ADh
cmp esi,7
jae clp_render_pay_next
mov eax,clpSymbolNames3D[esi*4]
invoke DrawString8x8,28,edi,eax,00F4F3E8h
invoke FormatNumber,slotSymbolValues[esi*4],OFFSET hudNumBuffer
invoke DrawString8x8,132,edi,OFFSET hudNumBuffer,00E8D7ADh
clp_render_pay_next:
inc esi
cmp esi,11
jb clp_render_pay_row
invoke DrawString8x8,28,181,OFFSET clpOverlapText3D,00A8DDB9h
invoke DrawString8x8,28,193,OFFSET slotPayNote3D,00A8DDB9h
invoke DrawBoxBorder,112,204,96,15,003B6C59h,00E9D8B0h,1
invoke DrawString8x8,128,207,OFFSET slotCloseText3D,00E9D8B0h
clp_render_done:
ret
RenderNativeClover3D ENDP

View file

@ -0,0 +1,80 @@
MODE_CLOVER equ 21
CLP_CELL equ 56
CLP_TOP equ 126
CLP_BOTTOM equ 294
InitNativeClover3D PROTO
FreeNativeClover3D PROTO
StartNativeClover3D PROTO
HandleNativeClover3D PROTO
RenderNativeClover3D PROTO
CloverEscape3D PROTO
SettleNativeClover3D PROTO
CloverEvaluate3D PROTO
CloverReelPosition3D PROTO :DWORD
DrawCloverSymbol3D PROTO :DWORD,:DWORD,:DWORD
DrawCloverReel3D PROTO :DWORD
CloverComboText3D PROTO :DWORD
.data
clpCabinet3D AnimPack3D <>
clpSymbols3D AnimPack3D <>
clpCabinetPath3D db "assets\animations\clover\cabinet-v1.bin",0
clpSymbolsPath3D db "assets\animations\clover\symbols-v1.bin",0
clpState3D dd 0
clpTimer3D dd 0
clpBet3D dd 50
clpBetIndex3D dd 1
clpStake3D dd 0
clpGross3D dd 0
clpTotal3D dd 0
clpWinMask3D dd 0
clpCount3D dd 0
clpSelected3D dd 0
clpJackpot3D dd 0
clpPaytable3D dd 0
clpLeave3D dd 0
clpError3D dd 0
clpGrid3D dd 0,1,2,3,4,5,6,0,1,2,3,4,5,6,0
clpTape3D dd 320 dup(0)
clpNames3D dd 32 dup(0)
clpMults3D dd 32 dup(0)
clpMasks3D dd 32 dup(0)
clpDurations3D dd 1800,2200,2600,3000,3400
clpSteps3D dd 28,32,36,40,44
clpColumns3D dd 144,216,288,360,432
clpTitle3D db 202,203,197,194,197,208,32,53,120,51,0
clpTotalText3D db 204,205,206,198,200,210,197,203,220,0
clpNoCombo3D db 205,197,210,32,202,206,204,193,200,205,192,214,200,201,0
clpSymbolsText3D db 209,200,204,194,206,203,0
clpShapesText3D db 212,200,195,211,208,192,0
clpOverlapText3D db 212,200,195,211,208,192,32,120,32,209,200,204,194,206,203,59,32,209,211,204,204,192,0
clpShape0 db 208,223,196,32,51,0
clpShape1 db 208,223,196,32,52,0
clpShape2 db 208,223,196,32,53,0
clpShape3 db 194,197,208,210,200,202,192,203,220,0
clpShape4 db 196,200,192,195,206,205,192,203,220,0
clpShape5 db 209,210,208,197,203,202,192,32,194,194,197,208,213,0
clpShape6 db 209,210,208,197,203,202,192,32,194,205,200,199,0
clpShape7 db 194,197,208,213,205,200,201,32,208,223,196,0
clpShape8 db 205,200,198,205,200,201,32,208,223,196,0
clpShape9 db 195,203,192,199,0
clpShape10 db 196,198,197,202,207,206,210,0
clpSymbol0 db 202,203,197,194,197,208,0
clpSymbol1 db 203,200,204,206,205,0
clpSymbol2 db 194,200,216,205,223,0
clpSymbol3 db 202,206,203,206,202,206,203,0
clpSymbol4 db 192,203,204,192,199,0
clpSymbol5 db 209,211,205,196,211,202,0
clpSymbol6 db 209,197,204,220,0
clpShapeNames3D dd OFFSET clpShape0,OFFSET clpShape1,OFFSET clpShape2
dd OFFSET clpShape3,OFFSET clpShape4,OFFSET clpShape5,OFFSET clpShape6
dd OFFSET clpShape7,OFFSET clpShape8,OFFSET clpShape9,OFFSET clpShape10
clpLegacyNames3D dd OFFSET slotComboHorM,OFFSET slotComboHorL,OFFSET slotComboHorXL
dd OFFSET slotComboVertM,OFFSET slotComboDiagM,OFFSET slotComboUp,OFFSET slotComboDown
dd OFFSET slotComboSky,OFFSET slotComboEarth,OFFSET slotComboEye,OFFSET slotComboJackpot
clpShapeFactors3D dd 1,2,3,1,1,4,4,7,7,8,10
clpSymbolNames3D dd OFFSET clpSymbol0,OFFSET clpSymbol1,OFFSET clpSymbol2,OFFSET clpSymbol3
dd OFFSET clpSymbol4,OFFSET clpSymbol5,OFFSET clpSymbol6
clpLeft3D db "<",0
clpRight3D db ">",0
clpSlash3D db "/",0
.code

View file

@ -98,6 +98,10 @@ coin_settle_loss:
invoke PlayCasinoSound3D,OFFSET soundLose,0,1
coin_settle_save:
mov coin3d_state,COIN_RESULT
xor eax,eax
cmp coin3d_win,0
setne al
invoke NpcRecordRound3D,0,eax
call SaveProgress3D
coin_settle_done:
ret

View file

@ -133,6 +133,10 @@ dk_settle_loss:
invoke PlayCasinoSound3D, OFFSET soundRoundLose, 0, 1
dk_settle_save:
call ClearKeys3D
xor eax,eax
cmp dkOutcome3D,0
sete al
invoke NpcRecordRound3D,5,eax
call SaveProgress3D
dk_settle_done:
ret

View file

@ -265,6 +265,10 @@ poker_fold_player:
poker_fold_settled:
mov pkState3D, PK_RESULT
call ClearKeys3D
xor eax,eax
cmp pkOutcome3D,0
sete al
invoke NpcRecordRound3D,2,eax
call SaveProgress3D
poker_fold_done:
ret
@ -362,6 +366,10 @@ poker_showdown_tie:
poker_showdown_settled:
mov pkState3D, PK_RESULT
call ClearKeys3D
xor eax,eax
cmp pkOutcome3D,0
sete al
invoke NpcRecordRound3D,2,eax
call SaveProgress3D
poker_showdown_done:
ret

View file

@ -135,6 +135,12 @@ SettleNativeSlots3D PROC uses esi
jne slot_settle_done
invoke SlotMultiplier3D,slotReel1,slotReel2,slotReel3
mul slotStake3D
mov ecx,1000000000
sub ecx,bankroll
cmp eax,ecx
jbe @F
mov eax,ecx
@@:
mov slotWinAmount,eax
add bankroll,eax
jc slot_settle_cap
@ -153,9 +159,19 @@ slot_settle_stops:
mov slotSpinTimer,3000
mov slotSpinState,SLOT_RESULT
mov slotEffects3D,0
xor eax,eax
mov ecx,slotWinAmount
cmp ecx,slotStake3D
seta al
invoke NpcRecordRound3D,4,eax
call SaveProgress3D
cmp slotWinAmount,0
je slot_settle_loss
mov eax,slotWinAmount
cmp eax,slotStake3D
jb slot_settle_loss
jne @F
invoke PlayCasinoSound3D,0,0,0
jmp slot_settle_exit
@@:
invoke PlayCasinoSound3D,OFFSET soundRoundWin,0,1
jmp slot_settle_exit
slot_settle_loss:
@ -476,8 +492,9 @@ slot_render_reels:
invoke DrawBox,232,94,4,3,00E9CE71h
cmp slotSpinState,SLOT_RESULT
jne slot_render_header
cmp slotWinAmount,0
je slot_render_header
mov eax,slotWinAmount
cmp eax,slotStake3D
jbe slot_render_header
mov eax,slotEffects3D
xor edx,edx
mov ecx,200
@ -501,7 +518,10 @@ slot_render_header:
cmp slotSpinState,SLOT_RESULT
jne @F
mov ebx,OFFSET slotLoseText3D
cmp slotWinAmount,0
mov eax,slotWinAmount
cmp eax,slotStake3D
jb @F
mov ebx,OFFSET slotRefundText3D
je @F
mov ebx,OFFSET slotWinText3D
@@:
@ -518,15 +538,22 @@ slot_render_header:
invoke DrawString8x8,104,149,ebx,00E8D7ADh
cmp slotSpinState,SLOT_RESULT
jne slot_render_controls
cmp slotWinAmount,0
je slot_render_controls
cmp ebx,OFFSET slotWinText3D
je @F
cmp ebx,OFFSET slotRefundText3D
jne slot_render_controls
@@:
invoke FormatNumber,slotWinAmount,OFFSET hudNumBuffer
invoke DrawString8x8,164,149,OFFSET hudNumBuffer,00A8DDB9h
slot_render_controls:
invoke DrawBox,0,202,320,38,00151D20h
invoke DrawString8x8,11,207,OFFSET slotBetText3D,00E8D7ADh
invoke DrawBox,0,198,320,42,00151D20h
invoke DrawString8x8,11,201,OFFSET slotBetText3D,00E8D7ADh
invoke FormatNumber,slotBet,OFFSET hudNumBuffer
invoke DrawString8x8,83,207,OFFSET hudNumBuffer,00F4F3E8h
invoke DrawString8x8,75,201,OFFSET hudNumBuffer,00F4F3E8h
cmp slotSpinState,SLOT_RESULT
jne @F
invoke DrawSlotTotals3D,slotWinAmount,slotStake3D,120
@@:
mov ebx,1
cmp slotSpinState,SLOT_SPIN
jne @F
@ -547,12 +574,12 @@ slot_render_controls:
je slot_render_done
invoke DrawBoxBorder,30,23,260,198,00151D20h,00D1B16Eh,1
invoke DrawString8x8,76,31,OFFSET slotPayTitle3D,00E8D7ADh
invoke DrawString8x8,66,48,OFFSET slotTripleText3D,00A8DDB9h
invoke DrawString8x8,42,48,OFFSET slotTripleText3D,00A8DDB9h
xor esi,esi
slot_render_pay_row:
mov edi,esi
imul edi,14
add edi,66
imul edi,12
add edi,62
mov eax,slotSymbolNames3D[esi*4]
invoke DrawString8x8,62,edi,eax,00F4F3E8h
invoke FormatNumber,slotTriple3D[esi*4],OFFSET hudNumBuffer
@ -561,10 +588,39 @@ slot_render_pay_row:
inc esi
cmp esi,8
jb slot_render_pay_row
invoke DrawString8x8,62,181,OFFSET slotPairText3D,00A8DDB9h
invoke DrawString8x8,62,192,OFFSET slotCherryPair3D,00A8DDB9h
invoke DrawString8x8,62,160,OFFSET slotPairText3D,00A8DDB9h
invoke DrawString8x8,62,173,OFFSET slotCherryPair3D,00A8DDB9h
invoke DrawString8x8,42,188,OFFSET slotPayNote3D,00A8DDB9h
invoke DrawBoxBorder,112,204,96,15,003B6C59h,00E9D8B0h,1
invoke DrawString8x8,128,207,OFFSET slotCloseText3D,00E9D8B0h
slot_render_done:
ret
RenderNativeSlots3D ENDP
; Gross is the actual credited sum, including any returned stake.
DrawSlotTotals3D PROC uses ebx esi edi gross:DWORD,stake:DWORD,x:DWORD
mov ebx,x
lea edi,[ebx+72]
invoke DrawString8x8,ebx,201,OFFSET slotWinText3D,00E8D7ADh
invoke FormatNumber,gross,OFFSET hudNumBuffer
invoke DrawString8x8,edi,201,OFFSET hudNumBuffer,00F4F3E8h
invoke DrawString8x8,ebx,211,OFFSET slotNetText3D,00E8D7ADh
mov eax,gross
sub eax,stake
mov esi,00D0D4D0h
lea ebx,slotNetBuffer3D
test eax,eax
jz totals_format
mov BYTE PTR [ebx],'+'
mov esi,00A8DDB9h
jns @F
mov BYTE PTR [ebx],'-'
mov esi,00EE9988h
neg eax
@@:
inc ebx
totals_format:
invoke FormatNumber,eax,ebx
invoke DrawString8x8,edi,211,OFFSET slotNetBuffer3D,esi
ret
DrawSlotTotals3D ENDP

View file

@ -17,6 +17,7 @@ SlotExitMouse3D PROTO
SlotReelPosition3D PROTO :DWORD
DrawSlotReel3D PROTO :DWORD
DrawSlotSymbol3D PROTO :DWORD,:DWORD,:DWORD
DrawSlotTotals3D PROTO :DWORD,:DWORD,:DWORD
.data
slotCabinetPack3D AnimPack3D <>
slotSymbolsPack3D AnimPack3D <>
@ -38,6 +39,9 @@ slotLeaveAfter3D dd 0
slotPaytable3D dd 0
slotError3D dd 0
slotEffects3D dd 0
slotNetBuffer3D db 16 dup(0)
slotRefundText3D db 194,206,199,194,208,192,210,0
slotNetText3D db 200,210,206,195,0
slotTitle3D db "777",0
slotSpinText3D db 194,208,192,217,192,210,220,0
slotPayText3D db 194,219,207,203,192,210,219,0
@ -50,7 +54,7 @@ slotRollingText3D db 194,208,192,217,197,205,200,197,0
slotExitText3D db 199,192,194,197,208,216,197,205,200,197,0
slotLoseText3D db 207,208,206,200,195,208,219,216,0
slotNoMoney3D db 205,197,210,32,196,197,205,197,195,0
slotTripleText3D db 210,208,200,32,209,200,204,194,206,203,192,32,40,209,206,32,209,210,192,194,202,206,201,41,0
slotTripleText3D db 214,197,205,210,208,58,32,210,208,200,32,206,196,200,205,192,202,206,194,219,213,0
strMultiplier3D db "x",0
slotPairText3D db 203,222,193,192,223,32,207,192,208,192,32,32,32,32,"x1",0
slotCherryPair3D db 207,192,208,192,32,194,200,216,197,205,32,32,32,32,"x2",0

View file

@ -132,6 +132,11 @@ wheel_bank_cap:
mov bankroll, 1000000000
@@:
mov roulState, 2
xor eax,eax
mov ecx,wheelReturn3D
cmp ecx,wheelStake3D
seta al
invoke NpcRecordRound3D,3,eax
call SaveProgress3D
wheel_settle_done:
ret
@ -740,7 +745,7 @@ wheel_ball_place:
cmp roulState,2
jne wheel_scene_done
invoke DrawWheelLabels3D,centerX,centerY,radiusX,radiusY,wheelRotorAngle3D
invoke DrawBuckshotPanel3D,278,245,84,61,002D211Bh
invoke DrawWheelBox3D,278,245,84,61,002D211Bh
invoke DrawBuckshotText3D,296,253,OFFSET wheelLandedText3D,00E5D29Eh
mov ecx,00F4E7C8h
cmp wheelResult3D,0
@ -755,9 +760,56 @@ wheel_scene_done:
ret
RenderWheelScene3D ENDP
; Exact framebuffer rectangles avoid the two-pixel rounding of the logical UI grid.
DrawWheelBox3D PROC uses ebx esi edi x:DWORD,y:DWORD,w:DWORD,h:DWORD,ink:DWORD
local bottom:DWORD
mov esi,x
test esi,esi
jns @F
xor esi,esi
@@:
mov eax,x
add eax,w
cmp eax,SCREEN_W
jle @F
mov eax,SCREEN_W
@@:
sub eax,esi
jle wheel_box_done
mov ebx,eax
mov edx,y
test edx,edx
jns @F
xor edx,edx
@@:
mov eax,y
add eax,h
cmp eax,SCREEN_H
jle @F
mov eax,SCREEN_H
@@:
cmp eax,edx
jle wheel_box_done
mov bottom,eax
wheel_box_row:
imul edi,edx,SCREEN_W
add edi,esi
lea edi,screenBuffer[edi*4]
mov eax,ink
mov ecx,ebx
rep stosd
inc edx
cmp edx,bottom
jl wheel_box_row
wheel_box_done:
ret
DrawWheelBox3D ENDP
; Upright screen-space digits retain their shape when the wheel is projected.
DrawWheelDigit3D PROC uses ebx esi edi x:DWORD,y:DWORD,digit:DWORD,scale:DWORD,ink:DWORD
local row:DWORD,column:DWORD,bits:DWORD,px:DWORD,py:DWORD
cmp scale,0
je wheel_digit_done
cmp digit,9
ja wheel_digit_done
mov eax,digit
@ -780,7 +832,7 @@ wheel_digit_column:
imul eax,scale
add eax,y
mov py,eax
invoke DrawBuckshotPanel3D,px,py,scale,scale,ink
invoke DrawWheelBox3D,px,py,scale,scale,ink
wheel_digit_next:
shl bits,1
inc column
@ -885,10 +937,10 @@ wheel_label_highlight:
mov edi,wheelNumberY3D[esi*4]
sub ebx,9
sub edi,6
invoke DrawBuckshotPanel3D,ebx,edi,18,12,00E5C978h
invoke DrawWheelBox3D,ebx,edi,18,12,00E5C978h
inc ebx
inc edi
invoke DrawBuckshotPanel3D,ebx,edi,16,10,00121916h
invoke DrawWheelBox3D,ebx,edi,16,10,00121916h
mov eax,wheelNumberY3D[esi*4]
sub eax,5
invoke DrawWheelValue3D,wheelNumberX3D[esi*4],eax,wheelResult3D,2,00FFF8DEh

View file

@ -10,6 +10,7 @@ WheelTravel3D PROTO :DWORD,:DWORD,:DWORD
DrawNativeWheelDisc3D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
RenderWheelScene3D PROTO
DrawWheelDigit3D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
DrawWheelBox3D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
DrawWheelValue3D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
DrawWheelLabels3D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
.data

View file

@ -0,0 +1,437 @@
NpcDate3D PROC
IFDEF CASINO_ACTION_TEST
mov eax,npcDateOverride3D
test eax,eax
jnz npc_date_done
ENDIF
invoke GetLocalTime,OFFSET npcSystemTime3D
movzx eax,npcSystemTime3D
imul eax,10000
movzx ecx,npcSystemTime3D[2]
imul ecx,100
add eax,ecx
movzx ecx,npcSystemTime3D[6]
add eax,ecx
npc_date_done:
ret
NpcDate3D ENDP
NpcJournalSignature3D PROC uses esi source:DWORD
mov esi,source
mov ecx,(NPC_JOURNAL_SIZE-4)/4
mov eax,4E504351h
npc_signature_word:
rol eax,5
xor eax,[esi]
add eax,9E3779B9h
add esi,4
loop npc_signature_word
ret
NpcJournalSignature3D ENDP
; Failed, truncated or mismatched journals cannot change the player's balance.
NpcEnsureJournal3D PROC uses ebx esi edi
local date:DWORD,io:DWORD
call NpcDate3D
mov date,eax
mov esi,OFFSET savePath3D
cmp nativeProfileActive3D,0
je @F
mov esi,OFFSET saveFullPath
@@:
IFDEF CASINO_ACTION_TEST
cmp npcJournalBasePtr3D,0
je @F
mov esi,npcJournalBasePtr3D
@@:
ENDIF
xor ecx,ecx
npc_journal_path_length:
cmp ecx,250
jae npc_journal_invalid_path
cmp BYTE PTR [esi+ecx],0
je npc_journal_build_path
inc ecx
jmp npc_journal_path_length
npc_journal_build_path:
invoke CopyCString,OFFSET npcJournalNextPath3D,esi
invoke AppendCString,OFFSET npcJournalNextPath3D,OFFSET npcJournalSuffix3D
invoke lstrcmpiA,OFFSET npcJournalPath3D,OFFSET npcJournalNextPath3D
test eax,eax
jnz npc_journal_reset
cmp npcJournalLoaded3D,1
jne npc_journal_reset
mov eax,date
cmp eax,npcJournalDay3D
je npc_journal_ready
npc_journal_reset:
mov npcAccepted3D,0
mov npcClaimed3D,0
lea edi,npcProgress3D
mov ecx,NPC_IDENTITY_COUNT
xor eax,eax
rep stosd
mov eax,date
mov npcJournalDay3D,eax
invoke CopyCString,OFFSET npcJournalPath3D,OFFSET npcJournalNextPath3D
mov npcJournalLoaded3D,1
invoke CreateFileA,OFFSET npcJournalPath3D,80000000h,1,0,3,80h,0
cmp eax,-1
je npc_journal_ready
mov ebx,eax
invoke GetFileSize,ebx,0
cmp eax,NPC_JOURNAL_SIZE
jne npc_journal_close
invoke ReadFile,ebx,OFFSET npcJournalRead3D,NPC_JOURNAL_SIZE,ADDR io,0
test eax,eax
jz npc_journal_close
cmp io,NPC_JOURNAL_SIZE
jne npc_journal_close
cmp DWORD PTR npcJournalRead3D,3151504Eh
jne npc_journal_close
mov eax,date
cmp DWORD PTR npcJournalRead3D[4],eax
jne npc_journal_close
mov eax,DWORD PTR npcJournalRead3D[8]
or eax,DWORD PTR npcJournalRead3D[12]
test eax,0FFFFF000h
jnz npc_journal_close
mov eax,DWORD PTR npcJournalRead3D[12]
not eax
or eax,DWORD PTR npcJournalRead3D[8]
cmp eax,-1
jne npc_journal_close
invoke NpcJournalSignature3D,OFFSET npcJournalRead3D
cmp eax,DWORD PTR npcJournalRead3D[NPC_JOURNAL_SIZE-4]
jne npc_journal_close
xor esi,esi
npc_journal_validate_progress:
invoke NpcContractType3D,esi
mov eax,npcQuestGoals3D[eax*4]
cmp DWORD PTR npcJournalRead3D[16+esi*4],eax
ja npc_journal_close
inc esi
cmp esi,NPC_IDENTITY_COUNT
jb npc_journal_validate_progress
lea esi,npcJournalRead3D
lea edi,npcJournal3D
mov ecx,NPC_JOURNAL_SIZE/4
rep movsd
npc_journal_close:
invoke CloseHandle,ebx
npc_journal_ready:
mov eax,1
ret
npc_journal_invalid_path:
mov npcJournalLoaded3D,0
mov npcJournalIoError3D,1
xor eax,eax
ret
NpcEnsureJournal3D ENDP
NpcSaveJournal3D PROC uses ebx
local io:DWORD,ok:DWORD
mov npcJournalIoError3D,0
cmp npcJournalLoaded3D,1
jne npc_journal_write_fail
invoke NpcJournalSignature3D,OFFSET npcJournal3D
mov npcJournalChecksum3D,eax
invoke CreateDirectoryA,OFFSET runtime3D,0
invoke CreateFileA,OFFSET npcJournalPath3D,40000000h,0,0,2,80h,0
cmp eax,-1
je npc_journal_write_fail
mov ebx,eax
invoke WriteFile,ebx,OFFSET npcJournal3D,NPC_JOURNAL_SIZE,ADDR io,0
mov ok,eax
invoke CloseHandle,ebx
cmp ok,0
je npc_journal_write_fail
cmp io,NPC_JOURNAL_SIZE
jne npc_journal_write_fail
mov eax,1
ret
npc_journal_write_fail:
mov npcJournalIoError3D,1
xor eax,eax
ret
NpcSaveJournal3D ENDP
NpcContractType3D PROC identity:DWORD
mov eax,npcJournalDay3D
mov ecx,identity
imul ecx,7
add eax,ecx
xor edx,edx
mov ecx,NPC_IDENTITY_COUNT
div ecx
mov eax,edx
ret
NpcContractType3D ENDP
NpcAcceptContract3D PROC uses ebx identity:DWORD
mov npcJournalIoError3D,0
cmp identity,NPC_IDENTITY_COUNT
jae npc_accept_fail
call NpcEnsureJournal3D
test eax,eax
jz npc_accept_fail
mov ebx,identity
bt npcAccepted3D,ebx
jc npc_accept_fail
bts npcAccepted3D,ebx
call NpcSaveJournal3D
test eax,eax
jnz npc_accept_done
btr npcAccepted3D,ebx
npc_accept_fail:
xor eax,eax
npc_accept_done:
ret
NpcAcceptContract3D ENDP
NpcRecordRound3D PROC uses ebx esi edi game:DWORD,won:DWORD
call NpcEnsureJournal3D
test eax,eax
jz npc_event_done
cmp npcAccepted3D,0
je npc_event_done
xor esi,esi
xor edi,edi
npc_event_contract:
bt npcAccepted3D,esi
jnc npc_event_next
bt npcClaimed3D,esi
jc npc_event_next
invoke NpcContractType3D,esi
mov ebx,eax
mov eax,npcQuestGames3D[ebx*4]
cmp eax,-1
je @F
cmp eax,game
jne npc_event_next
@@:
cmp npcQuestWins3D[ebx*4],0
je @F
cmp won,0
je npc_event_next
@@:
mov eax,npcQuestGoals3D[ebx*4]
cmp npcProgress3D[esi*4],eax
jae npc_event_next
inc npcProgress3D[esi*4]
mov edi,1
npc_event_next:
inc esi
cmp esi,NPC_IDENTITY_COUNT
jb npc_event_contract
test edi,edi
jz npc_event_done
call NpcSaveJournal3D
npc_event_done:
ret
NpcRecordRound3D ENDP
NpcClaimContract3D PROC uses ebx esi identity:DWORD
mov npcJournalIoError3D,0
cmp identity,NPC_IDENTITY_COUNT
jae npc_claim_fail
call NpcEnsureJournal3D
test eax,eax
jz npc_claim_fail
mov ebx,identity
bt npcAccepted3D,ebx
jnc npc_claim_fail
bt npcClaimed3D,ebx
jc npc_claim_fail
invoke NpcContractType3D,ebx
mov ecx,npcQuestGoals3D[eax*4]
cmp npcProgress3D[ebx*4],ecx
jb npc_claim_fail
mov esi,npcQuestRewards3D[eax*4]
bts npcClaimed3D,ebx
call NpcSaveJournal3D
test eax,eax
jnz npc_claim_credit
btr npcClaimed3D,ebx
jmp npc_claim_fail
npc_claim_credit:
add bankroll,esi
jc @F
cmp bankroll,1000000000
jbe npc_claim_save
@@:
mov bankroll,1000000000
npc_claim_save:
call SaveProgress3D
mov eax,1
ret
npc_claim_fail:
xor eax,eax
ret
NpcClaimContract3D ENDP
OpenNpcDialog3D PROC uses ebx
cmp npcSelected3D,NPC_LIFE_COUNT
jae npc_dialog_open_fail
imul ebx,npcSelected3D,SIZEOF NpcLife3D
cmp npcLife3D[ebx].state,NPC_AWAY
je npc_dialog_open_fail
mov eax,npcLife3D[ebx].identity
mov npcDialogIdentity3D,eax
mov npcDialogError3D,0
call NpcEnsureJournal3D
test eax,eax
jnz @F
mov npcDialogError3D,1
@@:
mov engineMode,MODE_NPC_DIALOG
call ClearKeys3D
mov eax,1
ret
npc_dialog_open_fail:
xor eax,eax
ret
OpenNpcDialog3D ENDP
CloseNpcDialog3D PROC
mov engineMode,MODE_WALK
mov paused3D,0
call ClearKeys3D
ret
CloseNpcDialog3D ENDP
HandleNpcDialog3D PROC
cmp engineMode,MODE_NPC_DIALOG
jne npc_dialog_handle_done
call NpcEnsureJournal3D
cmp mouseClick3D,0
je npc_dialog_keys
mov mouseClick3D,0
cmp mouseY3D,169
jb npc_dialog_keys
cmp mouseY3D,190
jae npc_dialog_keys
cmp mouseX3D,40
jb npc_dialog_keys
cmp mouseX3D,112
jb npc_dialog_accept
cmp mouseX3D,120
jb npc_dialog_keys
cmp mouseX3D,198
jb npc_dialog_claim
cmp mouseX3D,208
jb npc_dialog_keys
cmp mouseX3D,280
jb npc_dialog_close
npc_dialog_keys:
invoke Pressed3D,30h
test eax,eax
jnz npc_dialog_close
invoke Pressed3D,31h
test eax,eax
jnz npc_dialog_accept
invoke Pressed3D,32h
test eax,eax
jnz npc_dialog_claim
invoke Pressed3D,0Dh
test eax,eax
jz npc_dialog_handle_done
mov ecx,npcDialogIdentity3D
bt npcAccepted3D,ecx
jnc npc_dialog_accept
jmp npc_dialog_claim
npc_dialog_accept:
invoke NpcAcceptContract3D,npcDialogIdentity3D
jmp npc_dialog_result
npc_dialog_claim:
invoke NpcClaimContract3D,npcDialogIdentity3D
npc_dialog_result:
mov eax,npcJournalIoError3D
mov npcDialogError3D,eax
ret
npc_dialog_close:
call CloseNpcDialog3D
npc_dialog_handle_done:
ret
HandleNpcDialog3D ENDP
; Preserve original personality names, removing unsupported quote glyphs for the HUD.
NpcNameText3D PROC uses esi edi identity:DWORD
mov eax,identity
cmp eax,NPC_IDENTITY_COUNT
jae npc_name_empty
mov esi,npcNames3D[eax*4]
lea edi,npcNameBuffer3D
mov ecx,60
npc_name_copy:
lodsb
test al,al
jz npc_name_finish
cmp al,0ABh
je npc_name_copy
cmp al,0BBh
je npc_name_copy
stosb
loop npc_name_copy
npc_name_finish:
mov BYTE PTR [edi],0
mov eax,OFFSET npcNameBuffer3D
ret
npc_name_empty:
mov npcNameBuffer3D,0
mov eax,OFFSET npcNameBuffer3D
ret
NpcNameText3D ENDP
RenderNpcDialog3D PROC uses ebx esi
cmp engineMode,MODE_NPC_DIALOG
jne npc_dialog_render_done
call RenderCeilingAndFloor
call RenderWalls
call PrepareCityDepth3D
call RenderSprites
call RenderHall3D
invoke DrawBoxBorder,30,48,260,151,00141B1Dh,00BA9955h,1
mov ebx,npcDialogIdentity3D
invoke NpcNameText3D,ebx
invoke DrawString8x8,42,59,eax,00F4DF9Eh
mov eax,ebx
xor edx,edx
mov ecx,6
div ecx
invoke DrawString8x8,42,76,npcGreetings3D[edx*4],00CED8D5h
invoke DrawString8x8,42,97,OFFSET npcContractTitle3D,00D3C08Fh
invoke NpcContractType3D,ebx
mov esi,eax
invoke DrawString8x8,42,112,npcQuestTexts3D[esi*4],00FFFFFFh
invoke DrawString8x8,42,130,OFFSET npcProgressText3D,00CED8D5h
invoke DrawNumber,112,130,npcProgress3D[ebx*4],00FFFFFFh
invoke DrawString8x8,123,130,OFFSET npcSlashText3D,00CED8D5h
invoke DrawNumber,149,130,npcQuestGoals3D[esi*4],00FFFFFFh
invoke DrawString8x8,178,130,OFFSET npcRewardText3D,00D3C08Fh
invoke DrawNumber,244,130,npcQuestRewards3D[esi*4],00FFFFFFh
mov eax,OFFSET npcAvailableText3D
bt npcAccepted3D,ebx
jnc npc_dialog_status
mov eax,OFFSET npcActiveText3D
mov ecx,npcQuestGoals3D[esi*4]
cmp npcProgress3D[ebx*4],ecx
jb npc_dialog_status
mov eax,OFFSET npcReadyText3D
bt npcClaimed3D,ebx
jnc npc_dialog_status
mov eax,OFFSET npcDoneText3D
npc_dialog_status:
invoke DrawString8x8,42,150,eax,0087D4A5h
invoke DrawBoxBorder,40,170,72,18,002D3A32h,0087AA8Eh,1
invoke DrawString8x8,51,175,OFFSET npcAcceptText3D,00FFFFFFh
invoke DrawBoxBorder,120,170,78,18,003B3024h,00BA9955h,1
invoke DrawString8x8,131,175,OFFSET npcClaimText3D,00FFFFFFh
invoke DrawBoxBorder,208,170,72,18,00212A2Dh,008B999Ah,1
invoke DrawString8x8,216,175,OFFSET npcCloseText3D,00FFFFFFh
cmp npcDialogError3D,0
je npc_dialog_render_done
invoke DrawString8x8,42,150,OFFSET npcIoErrorText3D,00F89880h
npc_dialog_render_done:
ret
RenderNpcDialog3D ENDP

581
src_graphics/npc_life.inc Normal file
View file

@ -0,0 +1,581 @@
; The visible people and the user's personality records share the same positions.
InitNpcLife3D PROC uses ebx esi edi
xor esi,esi
mov npcLifeClock3D,0
mov npcSelected3D,-1
npc_life_init:
imul ebx,esi,SIZEOF NpcLife3D
mov npcLife3D[ebx].identity,esi
mov npcLife3D[ebx].state,NPC_STAND
mov eax,esi
imul eax,900
add eax,1800
mov npcLife3D[ebx].remaining,eax
mov npcLife3D[ebx].routeStep,esi
mov npcLife3D[ebx].pathCount,0
mov npcLife3D[ebx].remainder,0
mov npcLife3D[ebx].poseClock,0
mov eax,npcStartDest3D[esi*4]
mov npcLife3D[ebx].destination,eax
imul edi,esi,SIZEOF NpcMindEntity
mov ecx,npcDestX3D[eax*4]
mov casinoNpcs3D[edi].posX,ecx
mov ecx,npcDestY3D[eax*4]
mov casinoNpcs3D[edi].posY,ecx
cmp eax,7
je @F
cmp eax,8
jne npc_life_init_next
@@:
mov npcLife3D[ebx].state,NPC_SIT
npc_life_init_next:
inc esi
cmp esi,NPC_LIFE_COUNT
jb npc_life_init
mov npcLifeReady3D,1
ret
InitNpcLife3D ENDP
; BFS visits each tile at most once. Body clearance includes furniture and the stage.
NpcTryCell3D PROC uses ebx esi cell:DWORD,parent:DWORD
mov esi,cell
cmp esi,NPC_PATH_CAP
jae npc_cell_done
cmp npcParents3D[esi*4],-1
jne npc_cell_done
mov eax,esi
and eax,31
shl eax,16
add eax,FP_HALF
mov edx,esi
shr edx,5
shl edx,16
add edx,FP_HALF
invoke CanWalk3D,eax,edx
test eax,eax
jz npc_cell_done
mov eax,parent
mov npcParents3D[esi*4],eax
mov ebx,npcSearchTail3D
cmp ebx,NPC_PATH_CAP
jae npc_cell_done
mov npcQueue3D[ebx*4],esi
inc npcSearchTail3D
npc_cell_done:
ret
NpcTryCell3D ENDP
NpcPlanPath3D PROC uses ebx esi edi slot:DWORD,destination:DWORD
local firstCell:DWORD,currentCell:DWORD,pathBase:DWORD
cmp slot,NPC_LIFE_COUNT
jae npc_plan_fail
cmp destination,NPC_DEST_COUNT
jae npc_plan_fail
imul ebx,slot,SIZEOF NpcLife3D
mov npcLife3D[ebx].pathCount,0
imul esi,slot,SIZEOF NpcMindEntity
mov eax,casinoNpcs3D[esi].posY
sar eax,16
shl eax,5
mov ecx,casinoNpcs3D[esi].posX
sar ecx,16
add eax,ecx
cmp eax,NPC_PATH_CAP
jae npc_plan_fail
mov firstCell,eax
mov ecx,destination
mov eax,npcDestY3D[ecx*4]
sar eax,16
shl eax,5
mov edx,npcDestX3D[ecx*4]
sar edx,16
add eax,edx
mov npcSearchGoal3D,eax
lea edi,npcParents3D
mov ecx,NPC_PATH_CAP
mov eax,-1
rep stosd
mov eax,firstCell
mov npcParents3D[eax*4],eax
mov npcQueue3D,eax
mov npcSearchHead3D,0
mov npcSearchTail3D,1
npc_plan_search:
mov eax,npcSearchHead3D
cmp eax,npcSearchTail3D
jae npc_plan_fail
mov esi,npcQueue3D[eax*4]
inc npcSearchHead3D
cmp esi,npcSearchGoal3D
je npc_plan_found
mov eax,esi
and eax,31
test eax,eax
jz @F
lea eax,[esi-1]
invoke NpcTryCell3D,eax,esi
@@:
mov eax,esi
and eax,31
cmp eax,31
je @F
lea eax,[esi+1]
invoke NpcTryCell3D,eax,esi
@@:
cmp esi,32
jb @F
lea eax,[esi-32]
invoke NpcTryCell3D,eax,esi
@@:
cmp esi,NPC_PATH_CAP-32
jae npc_plan_search
lea eax,[esi+32]
invoke NpcTryCell3D,eax,esi
jmp npc_plan_search
npc_plan_found:
mov eax,slot
imul eax,NPC_PATH_CAP*4
add eax,OFFSET npcPaths3D
mov pathBase,eax
xor edi,edi
npc_plan_reverse:
cmp esi,firstCell
je npc_plan_success
cmp edi,NPC_PATH_CAP
jae npc_plan_fail
mov edx,pathBase
mov [edx+edi*4],esi
inc edi
mov esi,npcParents3D[esi*4]
jmp npc_plan_reverse
npc_plan_success:
mov npcLife3D[ebx].pathCount,edi
mov eax,destination
mov npcLife3D[ebx].destination,eax
mov eax,1
ret
npc_plan_fail:
xor eax,eax
ret
NpcPlanPath3D ENDP
NpcCanStep3D PROC uses ebx esi slot:DWORD,px:DWORD,py:DWORD
invoke CanWalk3D,px,py
test eax,eax
jz npc_space_done
mov eax,px
sub eax,playerX
jns @F
neg eax
@@:
cmp eax,FP_1*3/10
jae npc_space_people
mov eax,py
sub eax,playerY
jns @F
neg eax
@@:
cmp eax,FP_1*3/10
jb npc_space_blocked
npc_space_people:
xor esi,esi
npc_space_person:
cmp esi,slot
je npc_space_next
imul ebx,esi,SIZEOF NpcLife3D
cmp npcLife3D[ebx].state,NPC_AWAY
je npc_space_next
imul ebx,esi,SIZEOF NpcMindEntity
mov eax,px
sub eax,casinoNpcs3D[ebx].posX
jns @F
neg eax
@@:
cmp eax,FP_1*2/5
jae npc_space_next
mov eax,py
sub eax,casinoNpcs3D[ebx].posY
jns @F
neg eax
@@:
cmp eax,FP_1*2/5
jb npc_space_blocked
npc_space_next:
inc esi
cmp esi,NPC_LIFE_COUNT
jb npc_space_person
mov eax,1
ret
npc_space_blocked:
xor eax,eax
npc_space_done:
ret
NpcCanStep3D ENDP
; Millisecond movement, carrying the remainder and consuming whole route cells.
NpcMove3D PROC uses ebx esi edi slot:DWORD,milliseconds:DWORD
local budget:DWORD,wx:DWORD,wy:DWORD,mind:DWORD,pathBase:DWORD
imul ebx,slot,SIZEOF NpcLife3D
imul eax,slot,SIZEOF NpcMindEntity
lea esi,casinoNpcs3D[eax]
mov mind,esi
mov eax,slot
imul eax,NPC_PATH_CAP*4
add eax,OFFSET npcPaths3D
mov pathBase,eax
mov eax,milliseconds
mov ecx,55000
mul ecx
add eax,npcLife3D[ebx].remainder
adc edx,0
mov ecx,1000
div ecx
mov npcLife3D[ebx].remainder,edx
mov budget,eax
npc_move_cell:
mov eax,npcLife3D[ebx].pathCount
test eax,eax
jz npc_move_arrived
dec eax
mov edx,pathBase
mov edi,[edx+eax*4]
mov eax,edi
and eax,31
shl eax,16
add eax,FP_HALF
mov wx,eax
mov eax,edi
shr eax,5
shl eax,16
add eax,FP_HALF
mov wy,eax
mov esi,mind
mov eax,wx
sub eax,[esi].NpcMindEntity.posX
mov edi,1
test eax,eax
jns @F
neg eax
mov edi,-1
@@:
test eax,eax
jz npc_move_y
cmp eax,budget
jbe @F
mov eax,budget
@@:
sub budget,eax
imul eax,edi
add eax,[esi].NpcMindEntity.posX
mov wx,eax
invoke NpcCanStep3D,slot,wx,[esi].NpcMindEntity.posY
test eax,eax
jz npc_move_blocked
mov eax,wx
mov [esi].NpcMindEntity.posX,eax
npc_move_y:
mov eax,wy
sub eax,[esi].NpcMindEntity.posY
mov edi,1
test eax,eax
jns @F
neg eax
mov edi,-1
@@:
cmp eax,budget
jbe @F
mov eax,budget
@@:
sub budget,eax
imul eax,edi
add eax,[esi].NpcMindEntity.posY
mov wy,eax
invoke NpcCanStep3D,slot,[esi].NpcMindEntity.posX,wy
test eax,eax
jz npc_move_blocked
mov eax,wy
mov [esi].NpcMindEntity.posY,eax
mov edx,pathBase
mov eax,npcLife3D[ebx].pathCount
dec eax
mov edi,[edx+eax*4]
mov eax,[esi].NpcMindEntity.posX
sub eax,FP_HALF
sar eax,16
mov edx,[esi].NpcMindEntity.posY
sub edx,FP_HALF
sar edx,16
shl edx,5
add eax,edx
cmp eax,edi
jne npc_move_done
; Integer cell membership is insufficient: finish at the exact cell centre.
mov eax,edi
and eax,31
shl eax,16
add eax,FP_HALF
cmp eax,[esi].NpcMindEntity.posX
jne npc_move_done
mov eax,edi
shr eax,5
shl eax,16
add eax,FP_HALF
cmp eax,[esi].NpcMindEntity.posY
jne npc_move_done
dec npcLife3D[ebx].pathCount
cmp budget,0
jne npc_move_cell
cmp npcLife3D[ebx].pathCount,0
jne npc_move_done
npc_move_arrived:
mov eax,npcLife3D[ebx].destination
mov npcLife3D[ebx].state,NPC_STAND
mov npcLife3D[ebx].remaining,3500
cmp eax,0
jne @F
mov npcLife3D[ebx].state,NPC_AWAY
mov npcLife3D[ebx].remaining,4500
jmp npc_move_done
@@:
cmp eax,7
je @F
cmp eax,8
jne npc_move_done
@@:
mov npcLife3D[ebx].state,NPC_SIT
mov npcLife3D[ebx].remaining,12000
jmp npc_move_done
npc_move_blocked:
mov npcLife3D[ebx].state,NPC_STAND
mov npcLife3D[ebx].remaining,1000
mov npcLife3D[ebx].pathCount,0
npc_move_done:
ret
NpcMove3D ENDP
UpdateNpcLife3D PROC uses ebx esi edi
local milliseconds:DWORD,destination:DWORD
cmp npcLifeReady3D,1
jne npc_update_done
cmp hallLevel3D,0
jne npc_update_done
mov eax,deltaTime3D
cmp eax,250
jbe @F
mov eax,250
@@:
mov milliseconds,eax
add npcLifeClock3D,eax
xor esi,esi
npc_update_person:
imul ebx,esi,SIZEOF NpcLife3D
mov eax,milliseconds
add npcLife3D[ebx].poseClock,eax
cmp npcLife3D[ebx].state,NPC_WALK
jne npc_update_wait
invoke NpcMove3D,esi,milliseconds
jmp npc_update_sync
npc_update_wait:
sub npcLife3D[ebx].remaining,eax
ja npc_update_sync
cmp npcLife3D[ebx].state,NPC_AWAY
jne npc_update_choose
mov eax,npcLife3D[ebx].identity
add eax,6
cmp eax,NPC_IDENTITY_COUNT
jb @F
sub eax,NPC_IDENTITY_COUNT
@@:
mov npcLife3D[ebx].identity,eax
mov npcLife3D[ebx].state,NPC_STAND
npc_update_choose:
mov eax,npcLife3D[ebx].routeStep
xor edx,edx
mov ecx,NPC_DEST_COUNT
div ecx
mov eax,npcVisitRoute3D[edx*4]
mov destination,eax
inc npcLife3D[ebx].routeStep
invoke NpcPlanPath3D,esi,destination
test eax,eax
jz npc_update_retry
mov npcLife3D[ebx].state,NPC_WALK
jmp npc_update_sync
npc_update_retry:
mov npcLife3D[ebx].state,NPC_STAND
mov npcLife3D[ebx].remaining,1000
npc_update_sync:
imul edi,esi,SIZEOF NpcMindEntity
mov eax,npcLife3D[ebx].state
mov casinoNpcs3D[edi].state,eax
mov eax,npcLife3D[ebx].remaining
mov casinoNpcs3D[edi].stateTimer,eax
; Keep the old public position array coherent for existing world checks.
imul ecx,esi,SIZEOF CityProp3D
mov eax,casinoNpcs3D[edi].posX
mov hallGuests3D[ecx].posX,eax
mov eax,casinoNpcs3D[edi].posY
mov hallGuests3D[ecx].posY,eax
inc esi
cmp esi,NPC_LIFE_COUNT
jb npc_update_person
npc_update_done:
ret
UpdateNpcLife3D ENDP
RenderNpcLife3D PROC uses ebx esi edi
cmp npcLifeReady3D,1
jne npc_draw_done
xor esi,esi
npc_draw_depth:
imul ebx,esi,SIZEOF NpcMindEntity
invoke ProjectCityPoint3D,casinoNpcs3D[ebx].posX,casinoNpcs3D[ebx].posY
mov npcDrawDepth3D[esi*4],edx
mov npcDrawOrder3D[esi*4],esi
inc esi
cmp esi,NPC_LIFE_COUNT
jb npc_draw_depth
xor esi,esi
npc_draw_sort:
lea edi,[esi+1]
npc_draw_compare:
mov eax,npcDrawOrder3D[esi*4]
mov ebx,npcDrawDepth3D[eax*4]
mov ecx,npcDrawOrder3D[edi*4]
cmp ebx,npcDrawDepth3D[ecx*4]
jge @F
mov npcDrawOrder3D[esi*4],ecx
mov npcDrawOrder3D[edi*4],eax
@@:
inc edi
cmp edi,NPC_LIFE_COUNT
jb npc_draw_compare
inc esi
cmp esi,NPC_LIFE_COUNT-1
jb npc_draw_sort
xor esi,esi
npc_draw_person:
mov eax,npcDrawOrder3D[esi*4]
imul ebx,eax,SIZEOF NpcLife3D
cmp npcLife3D[ebx].state,NPC_AWAY
je npc_draw_next
imul edi,eax,SIZEOF NpcMindEntity
mov eax,casinoNpcs3D[edi].posX
mov npcSprite3D.posX,eax
mov eax,casinoNpcs3D[edi].posY
mov npcSprite3D.posY,eax
mov npcSprite3D.worldW,FP_1*3/5
mov npcSprite3D.worldH,FP_1*6/5
mov npcSprite3D.frame,0
cmp npcLife3D[ebx].identity,11
jne @F
mov npcSprite3D.frame,4
@@:
cmp npcLife3D[ebx].state,NPC_SIT
jne npc_draw_walk
mov npcSprite3D.frame,8
cmp npcLife3D[ebx].identity,11
jne @F
mov npcSprite3D.frame,10
@@:
mov npcSprite3D.worldW,FP_1*4/5
mov npcSprite3D.worldH,FP_1
jmp npc_draw_sprite
npc_draw_walk:
cmp npcLife3D[ebx].state,NPC_WALK
jne npc_draw_sprite
mov eax,npcLife3D[ebx].poseClock
xor edx,edx
mov ecx,180
div ecx
and eax,3
add npcSprite3D.frame,eax
npc_draw_sprite:
invoke DrawCityObject3D,OFFSET npcSprite3D,0
npc_draw_next:
inc esi
cmp esi,NPC_LIFE_COUNT
jb npc_draw_person
npc_draw_done:
ret
RenderNpcLife3D ENDP
CheckNpcInteractions3D PROC uses ebx esi edi
local distance:DWORD,nearest:DWORD,deltaX:DWORD,deltaY:DWORD
cmp npcLifeReady3D,1
jne npc_interact_done
cmp hallLevel3D,0
jne npc_interact_done
cmp interactTargetId,0
jne npc_interact_done
mov npcSelected3D,-1
mov nearest,165000
xor esi,esi
npc_interact_person:
imul ebx,esi,SIZEOF NpcLife3D
cmp npcLife3D[ebx].state,NPC_AWAY
je npc_interact_next
imul edi,esi,SIZEOF NpcMindEntity
mov eax,casinoNpcs3D[edi].posX
sub eax,playerX
sar eax,8
mov deltaX,eax
imul eax,eax
mov distance,eax
mov eax,casinoNpcs3D[edi].posY
sub eax,playerY
sar eax,8
mov deltaY,eax
imul eax,eax
add eax,distance
cmp eax,nearest
jae npc_interact_next
mov distance,eax
mov eax,playerDirX
sar eax,8
imul eax,deltaX
mov edx,playerDirY
sar edx,8
imul edx,deltaY
add eax,edx
cmp eax,6000
jl npc_interact_next
; Short, bounded line-of-sight test prevents talking through a wall.
mov ebx,1
npc_interact_ray:
mov eax,deltaX
imul eax,ebx
shl eax,5
add eax,playerX
sar eax,16
mov edx,deltaY
imul edx,ebx
shl edx,5
add edx,playerY
sar edx,16
cmp eax,32
jae npc_interact_next
cmp edx,32
jae npc_interact_next
shl edx,5
add edx,eax
mov ecx,worldMap3D
cmp BYTE PTR [ecx+edx],0
jne npc_interact_next
inc ebx
cmp ebx,8
jb npc_interact_ray
mov eax,distance
mov nearest,eax
mov npcSelected3D,esi
npc_interact_next:
inc esi
cmp esi,NPC_LIFE_COUNT
jb npc_interact_person
cmp npcSelected3D,-1
je npc_interact_done
mov interactTargetId,INTERACT_NPC
mov interactPromptPtr,OFFSET npcTalkPrompt3D
npc_interact_done:
ret
CheckNpcInteractions3D ENDP

View file

@ -0,0 +1,140 @@
MODE_NPC_DIALOG equ 20
IFDEF CASINO_ACTION_TEST
DeleteFileA PROTO :DWORD
ENDIF
INTERACT_NPC equ 40
NPC_LIFE_COUNT equ 6
NPC_IDENTITY_COUNT equ 12
NPC_DEST_COUNT equ 10
NPC_STAND equ 0
NPC_WALK equ 1
NPC_SIT equ 2
NPC_AWAY equ 3
NPC_PATH_CAP equ 1024
InitNpcLife3D PROTO
UpdateNpcLife3D PROTO
RenderNpcLife3D PROTO
CheckNpcInteractions3D PROTO
OpenNpcDialog3D PROTO
HandleNpcDialog3D PROTO
RenderNpcDialog3D PROTO
CloseNpcDialog3D PROTO
NpcPlanPath3D PROTO :DWORD,:DWORD
NpcTryCell3D PROTO :DWORD,:DWORD
NpcMove3D PROTO :DWORD,:DWORD
NpcCanStep3D PROTO :DWORD,:DWORD,:DWORD
NpcRecordRound3D PROTO :DWORD,:DWORD
NpcEnsureJournal3D PROTO
NpcSaveJournal3D PROTO
NpcAcceptContract3D PROTO :DWORD
NpcClaimContract3D PROTO :DWORD
NpcContractType3D PROTO :DWORD
NpcDate3D PROTO
NpcNameText3D PROTO :DWORD
NpcLife3D STRUCT
identity dd ?
state dd ?
remaining dd ?
destination dd ?
routeStep dd ?
pathCount dd ?
remainder dd ?
poseClock dd ?
NpcLife3D ENDS
.data
npcLife3D NpcLife3D NPC_LIFE_COUNT dup(<>)
npcLifeReady3D dd 0
npcLifeClock3D dd 0
npcSelected3D dd -1
npcDialogIdentity3D dd 0
npcDialogError3D dd 0
npcNameBuffer3D db 64 dup(0)
npcJournalIoError3D dd 0
npcDestX3D dd 16*FP_1+FP_HALF,16*FP_1+FP_HALF,10*FP_1+FP_HALF,6*FP_1+FP_HALF,13*FP_1+FP_HALF
dd 21*FP_1+FP_HALF,7*FP_1+FP_HALF,6*FP_1+FP_HALF,16*FP_1+FP_HALF,16*FP_1+FP_HALF
npcDestY3D dd 28*FP_1+FP_HALF,17*FP_1+FP_HALF,13*FP_1+FP_HALF,14*FP_1+FP_HALF,9*FP_1+FP_HALF
dd 10*FP_1+FP_HALF,25*FP_1+FP_HALF,27*FP_1+FP_HALF,10*FP_1+FP_HALF,7*FP_1+FP_HALF
npcVisitRoute3D dd 1,5,8,2,3,6,7,9,1,0
npcStartDest3D dd 1,2,3,6,8,7
npcSprite3D CityProp3D <0,0,FP_1*3/5,FP_1*6/5,0,OFFSET hallPatrons3D>
npcDrawOrder3D dd NPC_LIFE_COUNT dup(0)
npcDrawDepth3D dd NPC_LIFE_COUNT dup(0)
npcJournalLoaded3D dd 0
npcJournalPath3D db 260 dup(0)
npcJournalNextPath3D db 260 dup(0)
npcJournalSuffix3D db ".npc",0
IFDEF CASINO_ACTION_TEST
npcJournalBasePtr3D dd OFFSET npcJournalTestBase3D
npcJournalTestBase3D db "build\arrival\fixtures\npc-default.dat",0
ELSE
npcJournalBasePtr3D dd 0
ENDIF
npcDateOverride3D dd 0
npcSystemTime3D dw 8 dup(0)
; One bounded journal per profile; visitors 0..5 and 6..11 alternate at the entrance.
npcJournal3D LABEL DWORD
npcJournalMagic3D dd 3151504Eh
npcJournalDay3D dd 0
npcAccepted3D dd 0
npcClaimed3D dd 0
npcProgress3D dd NPC_IDENTITY_COUNT dup(0)
npcJournalChecksum3D dd 0
NPC_JOURNAL_SIZE equ $-npcJournal3D
npcJournalRead3D db NPC_JOURNAL_SIZE dup(0)
npcQuestGames3D dd -1,0,0,1,1,2,2,3,3,4,5,5
npcQuestWins3D dd 0,0,1,0,1,0,1,0,1,0,0,1
npcQuestGoals3D dd 3,3,1,2,1,2,1,3,1,3,1,1
npcQuestRewards3D dd 150,150,350,250,500,350,700,250,500,150,300,700
npcQuestTexts3D dd OFFSET npcQuest0,OFFSET npcQuest1,OFFSET npcQuest2,OFFSET npcQuest3
dd OFFSET npcQuest4,OFFSET npcQuest5,OFFSET npcQuest6,OFFSET npcQuest7
dd OFFSET npcQuest8,OFFSET npcQuest9,OFFSET npcQuest10,OFFSET npcQuest11
npcNames3D dd OFFSET npcName0,OFFSET npcName1,OFFSET npcName2,OFFSET npcName3,OFFSET npcName4,OFFSET npcName5
dd OFFSET npcName6,OFFSET npcName7,OFFSET npcName8,OFFSET npcName9,OFFSET npcName10,OFFSET npcName11
npcGreetings3D dd OFFSET npcGreeting0,OFFSET npcGreeting1,OFFSET npcGreeting2
dd OFFSET npcGreeting3,OFFSET npcGreeting4,OFFSET npcGreeting5
npcName6 db 196,236,232,242,240,232,233,32,209,229,240,238,226,0
npcName7 db 194,232,234,242,238,240,32,195,240,238,236,238,226,0
npcName8 db 195,240,232,227,238,240,232,233,32,203,232,241,0
npcName9 db 207,224,226,229,235,32,204,232,240,237,251,233,0
npcName10 db 192,237,242,238,237,32,193,238,227,224,242,251,233,0
npcName11 db 192,235,232,241,224,32,194,229,242,240,238,226,224,0
npcGreeting0 db 207,240,238,226,229,240,232,236,32,242,226,238,254,32,226,251,228,229,240,230,234,243,63,0
npcGreeting1 db 209,229,227,238,228,237,255,32,255,32,232,227,240,224,254,32,241,236,229,235,238,46,0
npcGreeting2 db 211,228,224,247,224,32,235,254,225,232,242,32,242,229,240,239,229,235,232,226,251,245,46,0
npcGreeting3 db 196,224,226,224,233,32,241,251,227,240,224,229,236,32,225,229,231,32,241,239,229,248,234,232,46,0
npcGreeting4 db 197,241,242,252,32,228,229,235,238,32,241,32,237,224,227,240,224,228,238,233,46,0
npcGreeting5 db 207,238,239,240,238,225,243,229,236,32,241,226,238,232,32,241,232,235,251,63,0
npcQuest0 db 199,224,226,229,240,248,232,32,235,254,225,251,229,32,51,32,240,224,243,237,228,224,0
npcQuest1 db 207,238,228,225,240,238,241,252,32,236,238,237,229,242,234,243,32,51,32,240,224,231,224,0
npcQuest2 db 194,251,232,227,240,224,233,32,226,32,236,238,237,229,242,234,243,0
npcQuest3 db 209,251,227,240,224,233,32,50,32,240,224,243,237,228,224,32,226,32,225,235,253,234,228,230,229,234,0
npcQuest4 db 194,251,232,227,240,224,233,32,226,32,225,235,253,234,228,230,229,234,0
npcQuest5 db 199,224,226,229,240,248,232,32,50,32,240,224,231,228,224,247,232,32,239,238,234,229,240,224,0
npcQuest6 db 194,251,232,227,240,224,233,32,240,224,231,228,224,247,243,32,239,238,234,229,240,224,0
npcQuest7 db 209,251,227,240,224,233,32,51,32,240,224,243,237,228,224,32,226,32,240,243,235,229,242,234,243,0
npcQuest8 db 194,251,232,227,240,224,233,32,226,32,240,243,235,229,242,234,243,0
npcQuest9 db 202,240,243,242,232,32,224,226,242,238,236,224,242,32,51,32,240,224,231,224,0
npcQuest10 db 199,224,226,229,240,248,232,32,239,224,240,242,232,254,32,226,32,228,243,240,224,234,224,0
npcQuest11 db 194,251,232,227,240,224,233,32,239,224,240,242,232,254,32,226,32,228,243,240,224,234,224,0
npcTalkPrompt3D db 91,69,93,32,207,206,195,206,194,206,208,200,210,220,0
npcContractTitle3D db 215,197,203,203,197,205,196,198,32,205,192,32,209,197,195,206,196,205,223,0
npcProgressText3D db 207,240,238,227,240,229,241,241,0
npcRewardText3D db 205,224,227,240,224,228,224,0
npcAcceptText3D db 194,199,223,210,220,0
npcClaimText3D db 205,192,195,208,192,196,192,0
npcCloseText3D db 199,192,202,208,219,210,220,0
npcAvailableText3D db 204,238,230,237,238,32,239,240,232,237,255,242,252,0
npcActiveText3D db 194,251,239,238,235,237,255,229,242,241,255,0
npcReadyText3D db 195,238,242,238,226,238,33,32,199,224,225,229,240,232,32,237,224,227,240,224,228,243,0
npcDoneText3D db 205,224,227,240,224,228,224,32,243,230,229,32,239,238,235,243,247,229,237,224,0
npcIoErrorText3D db 205,229,32,243,228,224,235,238,241,252,32,241,238,245,240,224,237,232,242,252,32,231,224,228,224,237,232,229,0
npcSlashText3D db " / ",0
.data?
npcPaths3D dd NPC_LIFE_COUNT*NPC_PATH_CAP dup(?)
npcParents3D dd NPC_PATH_CAP dup(?)
npcQueue3D dd NPC_PATH_CAP dup(?)
.data
npcSearchHead3D dd 0
npcSearchTail3D dd 0
npcSearchGoal3D dd 0
.code

1005
src_graphics/npc_mind.inc Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,258 @@
; ==============================================================================
; NPC MIND DATA - MASM32 / CP1251
; Multi-agent AI: Open-space Navigation, Personalities & Multi-seat Poker
; ==============================================================================
NPCAI_ARCHETYPE_SHARK equ 0 ; Акула (Tight-Aggressive)
NPCAI_ARCHETYPE_MANIAC equ 1 ; Лудоман (Loose-Aggressive)
NPCAI_ARCHETYPE_ROCK equ 2 ; Скала (Tight-Passive)
NPCAI_ARCHETYPE_CALLING_STATION equ 3 ; Автоответчик (Loose-Passive)
NPCAI_ARCHETYPE_WHALE equ 4 ; Мажор (Высокий банкролл, хаос)
NPCAI_ARCHETYPE_ROOKIE equ 5 ; Новичок (Ошибки, неуверенность)
NPCAI_STATE_IDLE equ 0 ; Стоит, осматривается
NPCAI_STATE_WALK equ 1 ; Идет по вейпоинтам
NPCAI_STATE_GAMBLE equ 2 ; Играет за столом
NPCAI_STATE_BAR equ 3 ; У бара
NPCAI_STATE_ATM equ 4 ; У банкомата
NPCAI_STATE_STAGE equ 5 ; Смотрит шоу
MPK_SEAT_EMPTY equ 0
MPK_SEAT_ACTIVE equ 1
MPK_SEAT_FOLDED equ 2
MPK_SEAT_ALLIN equ 3
MPK_SEAT_BUST equ 4
MPK_ACT_NONE equ 0
MPK_ACT_FOLD equ 1
MPK_ACT_CHECK equ 2
MPK_ACT_CALL equ 3
MPK_ACT_RAISE equ 4
MPK_ACT_ALLIN equ 5
MPK_STAGE_PREFLOP equ 0
MPK_STAGE_FLOP equ 1
MPK_STAGE_TURN equ 2
MPK_STAGE_RIVER equ 3
MPK_STAGE_SHOWDOWN equ 4
NPCAI_MAX_NPCS equ 6
MPK_MAX_SEATS equ 4
NPCAI_WAYPOINT_COUNT equ 16
; ------------------------------------------------------------------------------
; Структуры данных
; ------------------------------------------------------------------------------
NpcPersonality STRUCT
namePtr dd ?
titlePtr dd ?
archetypeId dd ?
baseAggression dd ? ; 0..100
baseTightness dd ? ; 0..100
baseBluffRate dd ? ; 0..100
baseCourage dd ? ; 0..100
tiltSens dd ? ; 0..100
defaultBank dd ?
phraseWinPtr dd ?
phraseLosePtr dd ?
phraseBluffPtr dd ?
phraseBustPtr dd ?
NpcPersonality ENDS
NpcMindEntity STRUCT
posX dd ? ; 16.16 FP
posY dd ? ; 16.16 FP
dirX dd ? ; 16.16 FP
dirY dd ? ; 16.16 FP
speed dd ? ; 16.16 FP
state dd ? ; NPCAI_STATE_*
stateTimer dd ?
currentNode dd ?
targetNode dd ?
profileId dd ?
currentChips dd ?
currentTilt dd ? ; 0..100
thoughtPtr dd ?
reactionPtr dd ?
NpcMindEntity ENDS
MpkSeat STRUCT
isHuman dd ? ; 1=Player, 0=NPC
profileId dd ? ; Индекс NpcPersonality (0..5)
chips dd ?
roundBet dd ?
totalBet dd ?
card1 dd ?
card2 dd ?
evalScore dd ?
seatStatus dd ? ; MPK_SEAT_*
lastAction dd ? ; MPK_ACT_*
lastRaiseAmount dd ?
thoughtPtr dd ?
reactionPtr dd ?
MpkSeat ENDS
MultiPokerTable STRUCT
seats MpkSeat MPK_MAX_SEATS dup(<>)
commCards dd 5 dup(?)
commCount dd ?
mainPot dd ?
currentHighBet dd ?
minRaise dd ?
dealerButton dd ?
currentTurn dd ?
activeSeats dd ?
smallBlind dd ?
bigBlind dd ?
stage dd ?
MultiPokerTable ENDS
NpcWaypoint STRUCT
posX dd ? ; 16.16 FP
posY dd ? ; 16.16 FP
neighbor0 dd ?
neighbor1 dd ?
neighbor2 dd ?
neighbor3 dd ?
zoneType dd ?
NpcWaypoint ENDS
; ------------------------------------------------------------------------------
; Строковые константы и описания (CP1251)
; ------------------------------------------------------------------------------
.data
; Имена и титулы 6 уникальных персонажей
npcName0 db "Виктор «Акула»",0
npcTitle0 db "Профессионал (Tight-Aggressive)",0
npcName1 db "Сергей «Лудоман»",0
npcTitle1 db "Маньяк (Loose-Aggressive, ТИЛЬТ)",0
npcName2 db "Дядя Толя «Скала»",0
npcTitle2 db "Ветеран (Tight-Passive)",0
npcName3 db "Степан «Автоответчик»",0
npcTitle3 db "Любитель (Calling Station)",0
npcName4 db "Мажор Артём",0
npcTitle4 db "Хайроллер (Whale, Безумные ставки)",0
npcName5 db "Влад «Новичок»",0
npcTitle5 db "Студент (Rookie, Неопытный)",0
; Фразы победы
npcWin0 db "«Холодный расчет и никакой магии. Банк мой.»",0
npcWin1 db "«ХА-ХА! Кто тут король казино?! Все фишки сюда!»",0
npcWin2 db "«Тише едешь — дальше будешь, молодежь.»",0
npcWin3 db "«Опа, закрылся стрит! А вы не верили!»",0
npcWin4 db "«Официант, всем шампанского за мой счет!»",0
npcWin5 db "«Ого! Неужели я правда победил?! Вот это да!»",0
; Фразы поражения / бэдбита
npcLose0 db "«Бывает. Математическая дисперсия на дистанции вернет всё.»",0
npcLose1 db "«ДА КАК ТАК-ТО?! Опять ривером переехали, сука!»",0
npcLose2 db "«Знал ведь, что надо было сбрасывать еще на флопе...»",0
npcLose3 db "«Эх, чутка не хватило до флеша... в следующий раз повезет.»",0
npcLose4 db "«Пф-ф, копейки. Сейчас сниму еще пачку в банкомате.»",0
npcLose5 db "«Я так и знал... Надо было внимательнее читать правила.»",0
; Фразы блефа / рейза
npcBluff0 db "«Твоя рука слабее. Сэкономь фишки и пасуй.»",0
npcBluff1 db "«Слабаки! Вам не по карману этот банк, двигайте пас!»",0
npcBluff2 db "«У меня отличная карта. Не советую лезть под колеса.»",0
npcBluff3 db "«А я просто хочу посмотреть следующую карту!»",0
npcBluff4 db "«Вы ставите копейки! Давайте играть по-взрослому!»",0
npcBluff5 db "«Э-э... кажется, я поднимаю ставку. Вы со мной?»",0
; Фразы банкротства
npcBust0 db "«Лимит на сегодня исчерпан. Пора делать паузу.»",0
npcBust1 db "«Всё в ноль! Бегу к банкомату, я обязан отыграться!»",0
npcBust2 db "«Пенсия кончилась... Пойду подышу на свежем воздухе.»",0
npcBust3 db "«Ой, фишки кончились... Пойду в бар за пивом.»",0
npcBust4 db "«Опять обнулился? Ерунда, сейчас сниму еще $50,000.»",0
npcBust5 db "«Кажется, на метро мне уже не хватает...»",0
; Мысли в свободном перемещении по казино
thWalkWander db "Осматривает игровой зал в поисках интересной игры",0
thWalkTable db "Направляется к покерному столу испытать удачу",0
thWalkBar db "Решил заглянуть в бар за напитком",0
thWalkAtm db "Закончились фишки, направляется к банкомату",0
thWalkStage db "Подходит к сцене посмотреть на танцовщиц",0
thIdleBored db "Стоит у колонны, подсчитывает оставшиеся деньги",0
; Мысли за покерным столом
thEvalStrong db "Чувствует натс! Готовит ловушку для оппонентов.",0
thEvalGood db "Рука сильная, отличные шансы забрать этот банк.",0
thEvalDraw db "Считает ауты: шансы банка выгодны для колла.",0
thEvalTrash db "Карты — мусор. Смысла рисковать фишками нет.",0
thEvalTilted db "[ТИЛЬТ!] Злится после проигрыша и идет на таран!",0
thEvalBluffing db "БЛЕФ! Изображает монстра и давит ставкой!",0
; Описания действий
thActFold db "Сбрасывает карты в пас",0
thActCheck db "Чекает, пропуская ход",0
thActCall db "Коллирует (уравнивает) текущую ставку",0
thActRaise db "Повышает ставку (рейз!)",0
thActAllIn db "Двигает все фишки вперед — ВА-БАНК (ОЛЛ-ИН)!",0
; Названия улиц покера
strStagePreflop db "Префлоп (Карманные карты)",0
strStageFlop db "Флоп (3 карты стола)",0
strStageTurn db "Терн (4 карты стола)",0
strStageRiver db "Ривер (5 карт стола)",0
strStageShow db "Вскрытие карт (Showdown)",0
; Тексты интерфейса мульти-покера
mpkTitleStr db "=== ТЕХАССКИЙ ХОЛДЕМ: СТОЛ НА 4 ИГРОКА ===",13,10,0
mpkPotStr db "Банк стола: $",0
mpkStreetStr db " | Раунд: ",0
mpkCommCardsStr db "Общие карты на столе: ",0
mpkPromptStr db "Ваш ход: [1] Чек/Колл | [2] Рейз | [0] Пас -> ",0
mpkWaitNextStr db 13,10,"[Нажмите любую клавишу для следующей раздачи, 0 для выхода]...",0
mpkChipsPrefix db " Стек: $",0
mpkThoughtPrefix db " [Мысли]: ",0
mpkGameOverStr db 13,10,"Раздача окончена. Победитель: ",0
mpkWinChipsStr db " выиграл банк $",0
ALIGN 4
; База профилей 6 уникальных личностей
casinoNpcProfiles NpcPersonality <OFFSET npcName0, OFFSET npcTitle0, NPCAI_ARCHETYPE_SHARK, 75, 85, 20, 85, 15, 5000, OFFSET npcWin0, OFFSET npcLose0, OFFSET npcBluff0, OFFSET npcBust0>
NpcPersonality <OFFSET npcName1, OFFSET npcTitle1, NPCAI_ARCHETYPE_MANIAC, 95, 20, 80, 90, 85, 3500, OFFSET npcWin1, OFFSET npcLose1, OFFSET npcBluff1, OFFSET npcBust1>
NpcPersonality <OFFSET npcName2, OFFSET npcTitle2, NPCAI_ARCHETYPE_ROCK, 25, 90, 5, 30, 20, 4000, OFFSET npcWin2, OFFSET npcLose2, OFFSET npcBluff2, OFFSET npcBust2>
NpcPersonality <OFFSET npcName3, OFFSET npcTitle3, NPCAI_ARCHETYPE_CALLING_STATION, 15, 30, 10, 75, 40, 3000, OFFSET npcWin3, OFFSET npcLose3, OFFSET npcBluff3, OFFSET npcBust3>
NpcPersonality <OFFSET npcName4, OFFSET npcTitle4, NPCAI_ARCHETYPE_WHALE, 70, 45, 55, 70, 50, 10000, OFFSET npcWin4, OFFSET npcLose4, OFFSET npcBluff4, OFFSET npcBust4>
NpcPersonality <OFFSET npcName5, OFFSET npcTitle5, NPCAI_ARCHETYPE_ROOKIE, 40, 50, 25, 40, 60, 2000, OFFSET npcWin5, OFFSET npcLose5, OFFSET npcBluff5, OFFSET npcBust5>
; Граф вейпоинтов казино (координаты 16.16 FP и связи)
casinoWaypoints3D NpcWaypoint <16*FP_1, 28*FP_1, 1, 4, 8, 0, 0> ; 0: Вход в казино
NpcWaypoint <16*FP_1, 20*FP_1, 0, 2, 4, 8, 0> ; 1: Центр холла (юг)
NpcWaypoint <16*FP_1, 14*FP_1, 1, 3, 10, 12, 0> ; 2: Центр холла (север)
NpcWaypoint <16*FP_1, 6*FP_1, 2, 10, 12, 3, 7> ; 3: Перед сценой танцовщиц
NpcWaypoint < 8*FP_1, 20*FP_1, 0, 1, 5, 7, 0> ; 4: Западный коридор
NpcWaypoint < 6*FP_1, 14*FP_1, 4, 6, 2, 5, 4> ; 5: Автоматы 777
NpcWaypoint < 6*FP_1, 9*FP_1, 5, 10, 2, 6, 4> ; 6: Автоматы CloverPit
NpcWaypoint < 4*FP_1, 25*FP_1, 4, 0, 7, 7, 5> ; 7: Барная стойка
NpcWaypoint <24*FP_1, 20*FP_1, 0, 1, 9, 14, 0> ; 8: Восточный коридор
NpcWaypoint <26*FP_1, 25*FP_1, 8, 0, 9, 9, 0> ; 9: Зона отдыха VIP
NpcWaypoint <12*FP_1, 9*FP_1, 2, 3, 6, 11, 2> ; 10: Стол Блэкджека
NpcWaypoint <16*FP_1, 11*FP_1, 2, 10, 12, 11, 3> ; 11: Стол Рулетки
NpcWaypoint <20*FP_1, 9*FP_1, 2, 3, 11, 13, 1> ; 12: Стол Покера (Холдем)
NpcWaypoint <24*FP_1, 9*FP_1, 12, 8, 14, 13, 1> ; 13: Стол Дурака
NpcWaypoint <26*FP_1, 15*FP_1, 8, 13, 14, 14, 6> ; 14: Банкомат (ATM)
NpcWaypoint <10*FP_1, 15*FP_1, 2, 4, 5, 10, 0> ; 15: Зона костей/черепов
; 6 активных персонажей, гуляющих по залу казино
casinoNpcs3D NpcMindEntity NPCAI_MAX_NPCS dup(<>)
; Экземпляр стола мульти-покера
multiPokerTable MultiPokerTable <>
; Колода и рабочий буфер для мульти-покера
multiDeck dd 52 dup(0)
multiDeckIndex dd 0
multiEvalHand dd 7 dup(0)
multiActionWait dd 0

View file

@ -8,6 +8,12 @@ WindowEvents3D PROC uses ebx esi edi hwnd:DWORD, uMsg:DWORD, wp:DWORD, lp:DWORD
@@:
cmp uMsg, 201h
je event_mouse_down
cmp uMsg,203h
jne @F
cmp engineMode,MODE_POOL
je event_mouse_down
jmp event_default
@@:
cmp uMsg, 200h
jne @F
cmp engineMode, MODE_POOL
@ -61,9 +67,18 @@ event_mouse_down:
mov mouseY3D, eax
cmp uMsg,200h
jne @F
cmp eax,50
jb event_done
cmp eax,211
jae event_done
@@:
cmp uMsg,203h
je pyr_event_pick
mov pyrPickClick3D,0
jmp pyr_event_pick_done
pyr_event_pick:
mov pyrPickClick3D,1
pyr_event_pick_done:
mov mouseClick3D, 1
jmp event_done
event_key_down:

View file

@ -0,0 +1,71 @@
InitPyramidState3D PROTO
PyramidContact3D PROTO :DWORD,:DWORD
PyramidContactEnd3D PROTO :DWORD,:DWORD
PyramidCross3D PROTO :DWORD
PyramidRail3D PROTO :DWORD,:DWORD
PyramidDistance3D PROTO :DWORD,:DWORD
PyramidDirection3D PROTO
PyramidSelect3D PROTO :DWORD
PyramidCanPlace3D PROTO :DWORD,:DWORD,:DWORD
PyramidRespot3D PROTO :DWORD
PyramidPenalty3D PROTO :DWORD
PyramidChoice3D PROTO :DWORD
PyramidRack3D PROTO
PyramidPick3D PROTO :DWORD,:DWORD
PyramidPathClear3D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
PyramidGuide3D PROTO
PyramidBall3D PROTO :DWORD,:DWORD,:DWORD,:DWORD
FreePyramidAssets3D PROTO
.data
pyrTablePack3D AnimPack3D <>
pyrBallsPack3D AnimPack3D <>
pyrTablePath3D db "assets\animations\pyramid\table-v2.bin",0
pyrBallsPath3D db "assets\animations\pyramid\balls-v1.bin",0
pyrCue3D dd 0
pyrScore3D dd 0,0
pyrOwner3D dd 16 dup(-1)
pyrRoll3D dd 16 dup(0)
pyrPreviousX3D dd 16 dup(0)
pyrHistoryRails3D dd 16 dup(0)
pyrHistoryCross3D dd 16 dup(0)
pyrRailHalf3D dd 16 dup(0)
pyrTouched3D dd 0
pyrValidEnd3D dd 0
pyrRailSide3D dd 0
pyrFoulReason3D dd 0
pyrIllegalBreak3D dd 0
pyrFine3D dd 0
pyrFineActive3D dd 0
pyrAimCarry3D dd 0
pyrPickClick3D dd 0
pyrDirX3D dd FP_1
pyrDirY3D dd 0
pyrAimFactor3D REAL8 651.8986469044033
pyrGuideOn3D dd 1
pyrTarget3D dd -1
pyrGuideX3D dd 0
pyrGuideY3D dd 0
pyrChoiceActor3D dd 0
pyrFouler3D dd 0
pyrSettled3D dd 0
pyrExit3D db 194,32,213,206,203,203,0
pyrTitle3D db 207,200,208,192,204,200,196,192,0
pyrBreakText3D db 208,192,199,193,200,210,200,197,0
pyrTurnText3D db 213,206,196,0
pyrCueText3D db 193,200,210,206,202,0
pyrGuideText3D db 207,208,200,214,197,203,0
pyrPenaltyText3D db 216,210,208,192,212,205,206,201,32,216,192,208,0
pyrChoiceText3D db 207,206,209,203,197,32,216,210,208,192,212,192,0
pyrPlayText3D db 200,195,208,192,210,220,0
pyrPassText3D db 207,197,208,197,196,192,210,220,0
pyrRackText3D db 207,197,208,197,193,200,210,220,0
pyrTheirBreakText3D db 197,195,206,32,211,196,192,208,0
pyrNoContactText3D db 205,197,210,32,202,192,209,192,205,200,223,0
pyrBadBreakText3D db 205,197,194,197,208,205,219,201,32,208,192,199,193,206,201,0
pyrBadEndText3D db 205,197,194,197,208,205,219,201,32,211,196,192,208,0
pyrPlayerName3D db 194,219,0
pyrAIName3D db 200,200,0
pyrSkipText3D db 207,208,206,207,211,209,202,0
pyrScoreSep3D db ":",0
pyrStatusNames3D dd OFFSET pyrTurnText3D,OFFSET pyrNoContactText3D,OFFSET pyrBadBreakText3D,OFFSET pyrBadEndText3D
.code

View file

@ -0,0 +1,688 @@
PyramidSelect3D PROC uses ebx ball:DWORD
cmp poolBreak3D,0
je @F
mov pyrCue3D,0
ret
@@:
mov ebx,ball
cmp ebx,15
ja pyr_select_default
cmp poolActive3D[ebx*4],1
jne pyr_select_default
mov pyrCue3D,ebx
mov poolCalled3D,ebx
ret
pyr_select_default:
mov ebx,pyrCue3D
cmp poolActive3D[ebx*4],1
je pyr_select_done
xor ebx,ebx
pyr_select_scan:
cmp poolActive3D[ebx*4],1
je pyr_select_found
inc ebx
cmp ebx,16
jb pyr_select_scan
ret
pyr_select_found:
mov pyrCue3D,ebx
mov poolCalled3D,ebx
pyr_select_done:
ret
PyramidSelect3D ENDP
PyramidPick3D PROC uses ebx esi px:DWORD,py:DWORD
xor esi,esi
pyr_pick_ball:
cmp poolActive3D[esi*4],1
jne pyr_pick_next
mov eax,px
sub eax,poolX3D[esi*4]
sar eax,12
imul eax,eax
mov ebx,eax
mov eax,py
sub eax,poolY3D[esi*4]
sar eax,12
imul eax,eax
add eax,ebx
cmp eax,36*16*16
ja pyr_pick_next
mov eax,esi
ret
pyr_pick_next:
inc esi
cmp esi,16
jb pyr_pick_ball
mov eax,-1
ret
PyramidPick3D ENDP
PyramidDirection3D PROC uses ebx esi
mov ecx,poolAngle3D
and ecx,511
cmp pyrFineActive3D,0
jne pyr_direction_fine
mov eax,tableCos[ecx*4]
mov pyrDirX3D,eax
mov eax,tableSin[ecx*4]
mov pyrDirY3D,eax
ret
pyr_direction_fine:
mov ecx,pyrFine3D
and ecx,4095
mov ebx,ecx
and ebx,7
shr ecx,3
lea esi,[ecx+1]
and esi,511
mov eax,tableCos[esi*4]
sub eax,tableCos[ecx*4]
imul eax,ebx
sar eax,3
add eax,tableCos[ecx*4]
mov pyrDirX3D,eax
mov eax,tableSin[esi*4]
sub eax,tableSin[ecx*4]
imul eax,ebx
sar eax,3
add eax,tableSin[ecx*4]
mov pyrDirY3D,eax
ret
PyramidDirection3D ENDP
PoolAimPoint3D PROC uses ebx px:DWORD,py:DWORD
local deltaX:DWORD,deltaY:DWORD,phase:DWORD
mov ebx,pyrCue3D
mov eax,px
sub eax,poolX3D[ebx*4]
mov deltaX,eax
mov ecx,py
sub ecx,poolY3D[ebx*4]
mov deltaY,ecx
or eax,ecx
jz pyr_aim_done
fild deltaY
fild deltaX
fpatan
fmul pyrAimFactor3D
fistp phase
mov eax,phase
and eax,4095
mov pyrFine3D,eax
mov pyrFineActive3D,1
call PyramidDirection3D
pyr_aim_done:
ret
PoolAimPoint3D ENDP
PoolShoot3D PROC uses ebx esi
cmp poolState3D,POOL_AIM
jne pyr_shoot_done
mov ebx,pyrCue3D
cmp ebx,15
ja pyr_shoot_done
cmp poolActive3D[ebx*4],1
jne pyr_shoot_done
cmp pyrTablePack3D.pixels,0
je pyr_shoot_done
cmp pyrBallsPack3D.pixels,0
je pyr_shoot_done
cmp poolBreak3D,0
je @F
test ebx,ebx
jnz pyr_shoot_done
cmp poolX3D,95*FP_1
jge pyr_shoot_done
@@:
mov poolFirst3D,-1
mov poolRail3D,0
mov poolBreakRails3D,0
mov poolPotted3D,0
mov poolFoul3D,0
mov pyrFoulReason3D,0
mov pyrValidEnd3D,0
mov pyrSettled3D,0
mov poolAccumulator3D,0
mov poolTime3D,0
mov poolState3D,POOL_STRIKE
xor esi,esi
pyr_shoot_history:
mov pyrHistoryRails3D[esi*4],0
mov pyrHistoryCross3D[esi*4],0
mov pyrRailHalf3D[esi*4],0
inc esi
cmp esi,16
jb pyr_shoot_history
call PyramidDirection3D
pyr_shoot_done:
ret
PoolShoot3D ENDP
; Obstruction checks use Q4 geometry with a 64-bit projection numerator.
PyramidPathClear3D PROC uses ebx esi edi sx:DWORD,sy:DWORD,ex:DWORD,ey:DWORD,skipA:DWORD,skipB:DWORD
local deltaX:DWORD,deltaY:DWORD,length2:DWORD,projection:DWORD,wx:DWORD,wy:DWORD
mov eax,ex
sub eax,sx
sar eax,12
mov deltaX,eax
imul eax,eax
mov ebx,eax
mov eax,ey
sub eax,sy
sar eax,12
mov deltaY,eax
imul eax,eax
add eax,ebx
mov length2,eax
test eax,eax
jz pyr_path_bad
xor esi,esi
pyr_path_ball:
cmp esi,skipA
je pyr_path_next
cmp esi,skipB
je pyr_path_next
cmp poolActive3D[esi*4],1
jne pyr_path_next
mov eax,poolX3D[esi*4]
sub eax,sx
sar eax,12
mov wx,eax
imul eax,deltaX
mov ebx,eax
mov eax,poolY3D[esi*4]
sub eax,sy
sar eax,12
mov wy,eax
imul eax,deltaY
add eax,ebx
test eax,eax
jle pyr_path_next
cmp eax,length2
jge pyr_path_next
mov ecx,FP_1
imul ecx
idiv length2
mov projection,eax
imul deltaX
shrd eax,edx,16
sub eax,wx
imul eax,eax
mov ebx,eax
mov eax,projection
imul deltaY
shrd eax,edx,16
sub eax,wy
imul eax,eax
add eax,ebx
cmp eax,25*16*16
jb pyr_path_bad
pyr_path_next:
inc esi
cmp esi,16
jb pyr_path_ball
mov eax,1
ret
pyr_path_bad:
xor eax,eax
ret
PyramidPathClear3D ENDP
; Search every cue/target/pocket triple; no hidden outcomes or teleported pots.
PoolChooseAI3D PROC uses ebx esi edi
local cue:DWORD,target:DWORD,pocket:DWORD,deltaX:DWORD,deltaY:DWORD,distance:DWORD
local gx:DWORD,gy:DWORD,best:DWORD,bestCue:DWORD,bestX:DWORD,bestY:DWORD,bestPower:DWORD
local shotLength:DWORD
cmp poolBreak3D,0
je pyr_ai_search
mov pyrCue3D,0
mov poolAngle3D,0
mov pyrFineActive3D,0
mov poolPower3D,85000
ret
pyr_ai_search:
mov best,7FFFFFFFh
mov bestCue,-1
mov cue,0
pyr_ai_cue:
mov ecx,cue
cmp poolActive3D[ecx*4],1
jne pyr_ai_next_cue
mov target,0
pyr_ai_target:
mov esi,target
cmp esi,cue
je pyr_ai_next_target
cmp poolActive3D[esi*4],1
jne pyr_ai_next_target
mov pocket,0
pyr_ai_pocket:
mov edi,pocket
mov eax,poolPocketX3D[edi*4]
sub eax,poolX3D[esi*4]
mov deltaX,eax
mov eax,poolPocketY3D[edi*4]
sub eax,poolY3D[esi*4]
mov deltaY,eax
invoke PyramidDistance3D,deltaX,deltaY
test eax,eax
jz pyr_ai_next_pocket
mov distance,eax
mov eax,deltaX
mov ecx,POOL_DIAMETER
imul ecx
idiv distance
mov ebx,poolX3D[esi*4]
sub ebx,eax
mov gx,ebx
mov eax,deltaY
mov ecx,POOL_DIAMETER
imul ecx
idiv distance
mov ebx,poolY3D[esi*4]
sub ebx,eax
mov gy,ebx
cmp gx,30*FP_1+POOL_RADIUS
jl pyr_ai_next_pocket
cmp gx,290*FP_1-POOL_RADIUS
jg pyr_ai_next_pocket
cmp gy,50*FP_1+POOL_RADIUS
jl pyr_ai_next_pocket
cmp gy,180*FP_1-POOL_RADIUS
jg pyr_ai_next_pocket
mov ecx,cue
mov eax,gx
sub eax,poolX3D[ecx*4]
sar eax,12
mov ebx,deltaX
sar ebx,12
imul eax,ebx
mov ebx,eax
mov eax,gy
sub eax,poolY3D[ecx*4]
sar eax,12
mov edx,deltaY
sar edx,12
imul eax,edx
add eax,ebx
test eax,eax
jle pyr_ai_next_pocket
invoke PyramidPathClear3D,poolX3D[ecx*4],poolY3D[ecx*4],gx,gy,cue,target
test eax,eax
jz pyr_ai_next_pocket
mov edi,pocket
invoke PyramidPathClear3D,poolX3D[esi*4],poolY3D[esi*4],poolPocketX3D[edi*4],poolPocketY3D[edi*4],cue,target
test eax,eax
jz pyr_ai_next_pocket
mov ecx,cue
mov eax,gx
sub eax,poolX3D[ecx*4]
mov ebx,gy
sub ebx,poolY3D[ecx*4]
invoke PyramidDistance3D,eax,ebx
mov shotLength,eax
add eax,distance
cmp eax,best
jae pyr_ai_next_pocket
mov best,eax
mov ecx,cue
mov bestCue,ecx
mov ecx,gx
mov bestX,ecx
mov ecx,gy
mov bestY,ecx
; v^2/(2*rolling loss) estimates the travel budget, with margin for a cut.
sar eax,16
imul eax,220
add eax,22000
cmp eax,85000
jbe @F
mov eax,85000
@@:
mov bestPower,eax
pyr_ai_next_pocket:
inc pocket
cmp pocket,6
jb pyr_ai_pocket
pyr_ai_next_target:
inc target
cmp target,16
jb pyr_ai_target
pyr_ai_next_cue:
inc cue
cmp cue,16
jb pyr_ai_cue
cmp bestCue,-1
je pyr_ai_safety
mov eax,bestCue
mov pyrCue3D,eax
invoke PoolAimPoint3D,bestX,bestY
mov eax,bestPower
mov poolPower3D,eax
ret
pyr_ai_safety:
invoke PyramidSelect3D,-1
mov esi,pyrCue3D
xor edi,edi
pyr_ai_safety_target:
cmp edi,esi
je @F
cmp poolActive3D[edi*4],1
je pyr_ai_safety_found
@@:
inc edi
cmp edi,16
jb pyr_ai_safety_target
ret
pyr_ai_safety_found:
invoke PoolAimPoint3D,poolX3D[edi*4],poolY3D[edi*4]
mov poolPower3D,65000
ret
PoolChooseAI3D ENDP
HandlePool3D PROC uses ebx esi edi
xor esi,esi
pyr_sink_tick:
mov eax,deltaTime3D
sub poolSinkTime3D[esi*4],eax
jnc @F
mov poolSinkTime3D[esi*4],0
@@:
inc esi
cmp esi,16
jb pyr_sink_tick
mov eax,deltaTime3D
add poolTime3D,eax
cmp mouseClick3D,0
je pyr_update
cmp mouseY3D,3
jb pyr_update
cmp mouseY3D,17
jae pyr_update
cmp mouseX3D,252
jb pyr_update
cmp mouseX3D,312
jae pyr_update
mov engineMode,MODE_WALK
call ClearKeys3D
call SaveProgress3D
ret
pyr_update:
cmp poolState3D,POOL_MOVING
jne pyr_strike
add poolAccumulator3D,eax
pyr_fixed_tick:
cmp poolAccumulator3D,2
jb pyr_handle_done
sub poolAccumulator3D,2
call PoolStep3D
cmp poolStill3D,0
je pyr_fixed_tick
call PoolFinishShot3D
ret
pyr_strike:
cmp poolState3D,POOL_STRIKE
jne pyr_idle
cmp poolTime3D,240
jb pyr_handle_done
mov ebx,pyrCue3D
mov eax,pyrDirX3D
imul poolPower3D
shrd eax,edx,16
mov poolVX3D[ebx*4],eax
mov eax,pyrDirY3D
imul poolPower3D
shrd eax,edx,16
mov poolVY3D[ebx*4],eax
mov poolState3D,POOL_MOVING
mov poolAccumulator3D,0
ret
pyr_idle:
cmp poolState3D,POOL_RESULT
jne pyr_actor
invoke Pressed3D,20h
test eax,eax
jnz pyr_restart
cmp mouseClick3D,0
je pyr_handle_done
cmp mouseY3D,211
jb pyr_handle_done
cmp mouseX3D,202
jb pyr_handle_done
pyr_restart:
call InitPool3D
ret
pyr_actor:
cmp poolPlayer3D,1
jne pyr_human
cmp poolAI3D,0
je pyr_human
cmp poolTime3D,1000
jb pyr_handle_done
cmp poolState3D,POOL_PENALTY
jne @F
xor esi,esi
pyr_ai_penalty:
cmp poolActive3D[esi*4],1
je pyr_ai_penalty_pick
inc esi
cmp esi,16
jb pyr_ai_penalty
ret
pyr_ai_penalty_pick:
invoke PyramidPenalty3D,esi
ret
@@:
cmp poolState3D,POOL_CHOICE
jne @F
invoke PyramidChoice3D,0
ret
@@:
call PoolChooseAI3D
call PoolShoot3D
ret
pyr_human:
cmp poolState3D,POOL_CHOICE
jne pyr_human_penalty
mov esi,0
pyr_choice_key:
lea eax,[esi+31h]
invoke Pressed3D,eax
test eax,eax
jnz pyr_choice_selected
inc esi
cmp esi,4
jb pyr_choice_key
invoke Pressed3D,0Dh
test eax,eax
jz @F
xor esi,esi
jmp pyr_choice_selected
@@:
cmp mouseClick3D,0
je pyr_handle_done
cmp mouseY3D,215
jb pyr_handle_done
cmp mouseY3D,237
jae pyr_handle_done
mov eax,mouseX3D
sub eax,8
js pyr_handle_done
xor edx,edx
mov ecx,78
div ecx
cmp edx,70
jae pyr_handle_done
mov esi,eax
pyr_choice_selected:
invoke PyramidChoice3D,esi
ret
pyr_human_penalty:
cmp poolState3D,POOL_PENALTY
jne pyr_human_aim
invoke Pressed3D,0Dh
test eax,eax
jnz pyr_penalty_skip
cmp mouseClick3D,0
je pyr_handle_done
cmp mouseY3D,215
jb @F
cmp mouseY3D,238
jae @F
cmp mouseX3D,202
jae pyr_penalty_skip
@@:
mov eax,mouseX3D
shl eax,16
mov ebx,mouseY3D
shl ebx,16
invoke PyramidPick3D,eax,ebx
cmp eax,-1
je pyr_handle_done
invoke PyramidPenalty3D,eax
ret
pyr_penalty_skip:
mov poolState3D,POOL_CHOICE
ret
pyr_human_aim:
cmp mouseClick3D,0
je pyr_keys
mov mouseClick3D,0
mov eax,mouseX3D
mov ebx,mouseY3D
cmp ebx,50
jb pyr_keys
cmp ebx,180
ja pyr_mouse_footer
shl eax,16
shl ebx,16
cmp pyrPickClick3D,0
je @F
mov pyrPickClick3D,0
invoke PyramidPick3D,eax,ebx
cmp eax,-1
je pyr_handle_done
invoke PyramidSelect3D,eax
ret
@@:
cmp poolBreak3D,0
je pyr_mouse_aim
cmp keys3D[10h],0
je pyr_mouse_aim
cmp eax,95*FP_1
jge pyr_handle_done
invoke PoolPlaceCue3D,eax,ebx
ret
pyr_mouse_aim:
invoke PoolAimPoint3D,eax,ebx
ret
pyr_mouse_footer:
cmp ebx,193
jb pyr_keys
cmp ebx,209
ja pyr_mouse_command
cmp eax,202
jb pyr_keys
cmp eax,308
ja pyr_keys
sub eax,202
imul eax,82000
xor edx,edx
mov ecx,106
div ecx
add eax,8000
mov poolPower3D,eax
ret
pyr_mouse_command:
cmp ebx,211
jb pyr_keys
cmp ebx,238
jae pyr_keys
cmp eax,86
jb pyr_cycle_cue
cmp eax,196
jb pyr_toggle_guide
call PoolShoot3D
ret
pyr_keys:
mov eax,deltaTime3D
imul eax,120
cmp keys3D[10h],0
je @F
mov eax,deltaTime3D
imul eax,30
@@:
add eax,pyrAimCarry3D
xor edx,edx
mov ecx,1000
div ecx
mov pyrAimCarry3D,edx
mov ebx,eax
cmp pyrFineActive3D,0
jne @F
mov eax,poolAngle3D
shl eax,3
mov pyrFine3D,eax
@@:
cmp keys3D[25h],0
je @F
sub pyrFine3D,ebx
mov pyrFineActive3D,1
@@:
cmp keys3D[27h],0
je @F
add pyrFine3D,ebx
mov pyrFineActive3D,1
@@:
and pyrFine3D,4095
mov eax,deltaTime3D
imul eax,100
cmp keys3D[26h],0
je @F
add poolPower3D,eax
@@:
cmp keys3D[28h],0
je @F
sub poolPower3D,eax
@@:
cmp poolPower3D,8000
jge @F
mov poolPower3D,8000
@@:
cmp poolPower3D,90000
jle @F
mov poolPower3D,90000
@@:
invoke Pressed3D,42h
test eax,eax
jnz pyr_cycle_cue
invoke Pressed3D,50h
test eax,eax
jnz pyr_toggle_guide
invoke Pressed3D,20h
test eax,eax
jz pyr_handle_done
call PoolShoot3D
ret
pyr_toggle_guide:
xor pyrGuideOn3D,1
ret
pyr_cycle_cue:
cmp poolBreak3D,0
jne pyr_handle_done
mov esi,pyrCue3D
mov edi,16
pyr_cycle_scan:
inc esi
and esi,15
cmp poolActive3D[esi*4],1
je pyr_cycle_found
dec edi
jnz pyr_cycle_scan
ret
pyr_cycle_found:
invoke PyramidSelect3D,esi
pyr_handle_done:
ret
HandlePool3D ENDP

View file

@ -0,0 +1,378 @@
PyramidBall3D PROC uses ebx esi ball:DWORD,x:DWORD,y:DWORD,sizePx:DWORD
mov ebx,ball
mov eax,pyrRoll3D[ebx*4]
shr eax,17
and eax,3
test ebx,ebx
jnz @F
add eax,4
@@:
mov esi,eax
mov eax,sizePx
shr eax,1
mov edx,x
sub edx,eax
mov ecx,y
sub ecx,eax
invoke DrawAnimSprite3D,OFFSET pyrBallsPack3D,esi,edx,ecx,sizePx,sizePx
cmp sizePx,12
jne pyr_ball_done
test ebx,ebx
jz pyr_ball_done
mov esi,x
sub esi,1
mov eax,y
sub eax,2
cmp ebx,10
jb @F
sub esi,2
invoke DrawWheelDigit3D,esi,eax,1,1,003B3933h
add esi,4
mov eax,y
sub eax,2
sub ebx,10
@@:
invoke DrawWheelDigit3D,esi,eax,ebx,1,003B3933h
pyr_ball_done:
ret
PyramidBall3D ENDP
; A swept aim preview terminates on the first real ball or cushion.
PyramidGuide3D PROC uses ebx esi edi
local px:DWORD,py:DWORD,pointX:DWORD,pointY:DWORD
mov pyrTarget3D,-1
mov ebx,pyrCue3D
mov eax,poolX3D[ebx*4]
mov px,eax
mov eax,poolY3D[ebx*4]
mov py,eax
mov esi,6
pyr_guide_step:
mov eax,pyrDirX3D
imul esi
sar eax,1
add eax,px
mov pointX,eax
mov eax,pyrDirY3D
imul esi
sar eax,1
add eax,py
mov pointY,eax
cmp pointX,30*FP_1+POOL_RADIUS
jl pyr_guide_done
cmp pointX,290*FP_1-POOL_RADIUS
jg pyr_guide_done
cmp pointY,50*FP_1+POOL_RADIUS
jl pyr_guide_done
cmp pointY,180*FP_1-POOL_RADIUS
jg pyr_guide_done
xor edi,edi
pyr_guide_obstacle:
cmp edi,pyrCue3D
je pyr_guide_next_obstacle
cmp poolActive3D[edi*4],1
jne pyr_guide_next_obstacle
mov eax,pointX
sub eax,poolX3D[edi*4]
sar eax,12
imul eax,eax
mov ebx,eax
mov eax,pointY
sub eax,poolY3D[edi*4]
sar eax,12
imul eax,eax
add eax,ebx
cmp eax,25*16*16
jb pyr_guide_hit
pyr_guide_next_obstacle:
inc edi
cmp edi,16
jb pyr_guide_obstacle
test esi,7
jnz pyr_guide_next
mov eax,pointX
sar eax,16
mov ebx,pointY
sar ebx,16
invoke DrawPixel,eax,ebx,00B1D7CFh
pyr_guide_next:
inc esi
cmp esi,560
jb pyr_guide_step
pyr_guide_done:
ret
pyr_guide_hit:
mov pyrTarget3D,edi
mov eax,pointX
mov pyrGuideX3D,eax
sar eax,16
mov ebx,pointY
mov pyrGuideY3D,ebx
sar ebx,16
invoke DrawCircle,eax,ebx,3,00B1D7CFh
mov eax,pointX
sar eax,16
mov ebx,pointY
sar ebx,16
invoke DrawCircle,eax,ebx,2,00295041h
ret
PyramidGuide3D ENDP
RenderPool3D PROC uses ebx esi edi
local px:DWORD,py:DWORD,tip:DWORD,dirx:DWORD,diry:DWORD
mov esi,pyrTablePack3D.pixels
test esi,esi
jz pyr_render_missing
lea edi,screenBuffer
mov ecx,SCREEN_SIZE
rep movsd
xor esi,esi
pyr_render_mouths:
mov eax,poolPocketX3D[esi*4]
sar eax,16
mov ebx,poolPocketY3D[esi*4]
sar ebx,16
invoke DrawCircle,eax,ebx,3,000B1010h
inc esi
cmp esi,6
jb pyr_render_mouths
invoke DrawPixel,95,115,00838C73h
invoke DrawPixel,160,115,00838C73h
invoke DrawPixel,225,115,00838C73h
cmp poolBreak3D,0
je @F
invoke DrawBox,95,53,1,124,003C7261h
@@:
cmp poolState3D,POOL_AIM
jne pyr_render_objects
call PyramidDirection3D
cmp pyrGuideOn3D,0
je pyr_render_objects
call PyramidGuide3D
pyr_render_objects:
xor esi,esi
pyr_render_ball:
cmp poolActive3D[esi*4],1
jne pyr_render_sunk
mov eax,poolX3D[esi*4]
sar eax,15
mov px,eax
mov eax,poolY3D[esi*4]
sar eax,15
mov py,eax
cmp esi,pyrCue3D
jne @F
cmp poolState3D,POOL_AIM
jne @F
mov eax,px
sar eax,1
mov ebx,py
sar ebx,1
invoke DrawCircle,eax,ebx,4,007BAFACh
@@:
invoke PyramidBall3D,esi,px,py,12
jmp pyr_render_next
pyr_render_sunk:
cmp poolSinkTime3D[esi*4],0
je pyr_render_next
mov ecx,poolPocket3D[esi*4]
cmp ecx,5
ja pyr_render_next
mov eax,poolPocketX3D[ecx*4]
sar eax,15
mov px,eax
mov eax,poolPocketY3D[ecx*4]
sar eax,15
mov py,eax
mov eax,poolSinkTime3D[esi*4]
imul eax,12
xor edx,edx
mov ecx,220
div ecx
test eax,eax
jz pyr_render_next
invoke PyramidBall3D,esi,px,py,eax
pyr_render_next:
inc esi
cmp esi,16
jb pyr_render_ball
cmp poolState3D,POOL_AIM
je pyr_render_cue
cmp poolState3D,POOL_STRIKE
jne pyr_render_hud
pyr_render_cue:
mov ebx,pyrCue3D
mov eax,poolX3D[ebx*4]
mov px,eax
mov eax,poolY3D[ebx*4]
mov py,eax
mov eax,pyrDirX3D
mov dirx,eax
mov eax,pyrDirY3D
mov diry,eax
mov tip,5
cmp poolState3D,POOL_STRIKE
jne @F
mov tip,3
mov eax,240
sub eax,poolTime3D
cmp eax,0
jge pyr_cue_time
xor eax,eax
pyr_cue_time:
shr eax,4
add tip,eax
@@:
mov esi,tip
mov edi,44
pyr_cue_draw:
mov eax,dirx
imul esi
neg eax
add eax,px
sar eax,16
mov ebx,eax
mov eax,diry
imul esi
neg eax
add eax,py
sar eax,16
mov tip,eax
invoke DrawCircle,ebx,eax,1,003E2E23h
invoke DrawPixel,ebx,tip,00DCC59Ah
inc esi
dec edi
jnz pyr_cue_draw
pyr_render_hud:
invoke DrawBox,0,0,320,20,00151D20h
invoke DrawString8x8,8,6,OFFSET pyrTitle3D,00E8D7ADh
invoke DrawNumber,112,6,pyrScore3D,00F4F3E8h
invoke DrawString8x8,128,6,OFFSET pyrScoreSep3D,00E8D7ADh
invoke DrawNumber,144,6,pyrScore3D[4],00F4F3E8h
mov ebx,OFFSET pyrPlayerName3D
cmp poolPlayer3D,0
je @F
mov ebx,OFFSET pyrAIName3D
@@:
invoke DrawString8x8,184,6,ebx,00A8DDB9h
invoke DrawBoxBorder,252,3,60,14,002C3637h,00687672h,1
invoke DrawString8x8,258,6,OFFSET cardExitButton3D,00E9D8B0h
mov ebx,OFFSET pyrTurnText3D
cmp poolBreak3D,0
je @F
mov ebx,OFFSET pyrBreakText3D
@@:
cmp poolState3D,POOL_PENALTY
jne @F
mov ebx,OFFSET pyrPenaltyText3D
@@:
cmp poolState3D,POOL_CHOICE
jne @F
mov ebx,OFFSET pyrChoiceText3D
@@:
cmp poolState3D,POOL_MOVING
jne @F
mov ebx,OFFSET poolMovingText3D
@@:
cmp poolState3D,POOL_STRIKE
jne @F
mov ebx,OFFSET poolShotText3D
@@:
invoke DrawBox,0,22,320,15,00151D20h
invoke DrawString8x8,8,26,ebx,00E8D7ADh
cmp poolState3D,POOL_PENALTY
je @F
cmp poolState3D,POOL_CHOICE
jne pyr_render_footer
@@:
mov ecx,pyrFoulReason3D
mov eax,pyrStatusNames3D[ecx*4]
invoke DrawString8x8,176,26,eax,00EAAD9Ah
pyr_render_footer:
invoke DrawBox,0,186,320,54,00151D20h
xor esi,esi
mov ebx,20
mov edi,174
pyr_render_shelves:
mov eax,pyrOwner3D[esi*4]
test eax,eax
js pyr_shelf_next
cmp eax,1
jne pyr_shelf_player
invoke PyramidBall3D,esi,edi,381,10
add edi,12
jmp pyr_shelf_next
pyr_shelf_player:
invoke PyramidBall3D,esi,ebx,381,10
add ebx,12
pyr_shelf_next:
inc esi
cmp esi,16
jb pyr_render_shelves
invoke DrawString8x8,202,187,OFFSET poolPowerText3D,00CCBE94h
mov eax,poolPower3D
sub eax,8000
imul eax,100
xor edx,edx
mov ecx,82000
div ecx
invoke DrawNumber,254,187,eax,00FFFFFFh
mov eax,poolPower3D
sub eax,8000
imul eax,106
xor edx,edx
mov ecx,82000
div ecx
mov tip,eax
invoke DrawBox,202,201,106,5,00363D36h
invoke DrawBox,202,201,tip,5,00D5BB73h
mov eax,tip
add eax,201
invoke DrawBox,eax,199,2,9,00F4F0DDh
invoke DrawBoxBorder,12,215,74,22,0039412Fh,00AC9862h,1
invoke DrawString8x8,18,222,OFFSET pyrCueText3D,00FFFFFFh
invoke DrawNumber,62,222,pyrCue3D,00EAD19Bh
invoke DrawBoxBorder,92,215,104,22,0039412Fh,00AC9862h,1
invoke DrawString8x8,106,222,OFFSET pyrGuideText3D,00FFFFFFh
invoke DrawBoxBorder,202,215,106,22,00425232h,00BBA76Fh,1
mov ebx,OFFSET poolShotText3D
cmp poolState3D,POOL_PENALTY
jne @F
mov ebx,OFFSET pyrSkipText3D
@@:
cmp poolState3D,POOL_RESULT
jne @F
mov ebx,OFFSET poolRackText3D
@@:
invoke DrawString8x8,226,222,ebx,00FFFFFFh
cmp poolState3D,POOL_CHOICE
jne pyr_render_result
invoke DrawBox,0,211,320,29,00151D20h
invoke DrawBoxBorder,8,215,70,22,0039412Fh,00AC9862h,1
invoke DrawString8x8,19,222,OFFSET pyrPlayText3D,00FFFFFFh
invoke DrawBoxBorder,86,215,70,22,0039412Fh,00AC9862h,1
invoke DrawString8x8,89,222,OFFSET pyrPassText3D,00FFFFFFh
cmp pyrIllegalBreak3D,0
je pyr_render_result
invoke DrawBoxBorder,164,215,70,22,0039412Fh,00AC9862h,1
invoke DrawString8x8,167,222,OFFSET pyrRackText3D,00FFFFFFh
invoke DrawBoxBorder,242,215,70,22,0039412Fh,00AC9862h,1
invoke DrawString8x8,249,222,OFFSET pyrTheirBreakText3D,00FFFFFFh
pyr_render_result:
cmp poolState3D,POOL_RESULT
jne pyr_render_done
invoke DrawBoxBorder,76,88,168,43,00192720h,00D6B971h,2
mov ebx,OFFSET pyrPlayerName3D
cmp poolWinner3D,0
je @F
mov ebx,OFFSET pyrAIName3D
@@:
invoke DrawString8x8,92,100,ebx,00FFFFFFh
invoke DrawString8x8,92,115,OFFSET poolWinText3D,00E8D7ADh
pyr_render_done:
ret
pyr_render_missing:
invoke DrawBox,0,0,320,240,00151D20h
invoke DrawString8x8,56,108,OFFSET unavailableText3D,00E8D7ADh
ret
RenderPool3D ENDP

View file

@ -0,0 +1,571 @@
InitPyramidState3D PROC uses esi edi
mov pyrCue3D,0
mov poolFirst3D,-1
mov pyrScore3D,0
mov pyrScore3D[4],0
mov pyrFine3D,0
mov pyrFineActive3D,0
mov pyrAimCarry3D,0
mov pyrPickClick3D,0
mov pyrSettled3D,0
mov pyrFoulReason3D,0
mov pyrIllegalBreak3D,0
mov pyrValidEnd3D,0
xor esi,esi
pyr_init_records:
mov pyrOwner3D[esi*4],-1
mov pyrRoll3D[esi*4],0
mov pyrHistoryRails3D[esi*4],0
mov pyrHistoryCross3D[esi*4],0
mov pyrRailHalf3D[esi*4],0
inc esi
cmp esi,16
jb pyr_init_records
invoke LoadAnimPack3D,OFFSET pyrTablePack3D,OFFSET pyrTablePath3D
invoke LoadAnimPack3D,OFFSET pyrBallsPack3D,OFFSET pyrBallsPath3D
ret
InitPyramidState3D ENDP
FreePyramidAssets3D PROC
invoke FreeAnimPack3D,OFFSET pyrTablePack3D
invoke FreeAnimPack3D,OFFSET pyrBallsPack3D
ret
FreePyramidAssets3D ENDP
; Q4 length calculation avoids the signed overflow of long Q8 table vectors.
PyramidDistance3D PROC uses ebx deltaX:DWORD,deltaY:DWORD
mov eax,deltaX
sar eax,12
imul eax,eax
mov ebx,eax
mov eax,deltaY
sar eax,12
imul eax,eax
add eax,ebx
invoke PoolSqrt3D,eax
shl eax,12
ret
PyramidDistance3D ENDP
PyramidContact3D PROC uses ebx esi edi a:DWORD,b:DWORD
mov esi,a
mov edi,b
cmp poolFirst3D,-1
jne pyr_contact_events
cmp esi,pyrCue3D
jne @F
mov poolFirst3D,edi
jmp pyr_contact_events
@@:
cmp edi,pyrCue3D
jne pyr_contact_done
mov poolFirst3D,esi
pyr_contact_events:
; Ordered cushion/crossing history follows impulse transfers, not all balls.
invoke PyramidContactEnd3D,esi,edi
invoke PyramidContactEnd3D,edi,esi
mov eax,pyrHistoryRails3D[esi*4]
or eax,pyrHistoryRails3D[edi*4]
mov pyrHistoryRails3D[esi*4],eax
mov pyrHistoryRails3D[edi*4],eax
mov eax,pyrHistoryCross3D[esi*4]
or eax,pyrHistoryCross3D[edi*4]
mov pyrHistoryCross3D[esi*4],eax
mov pyrHistoryCross3D[edi*4],eax
mov eax,pyrRailHalf3D[esi*4]
or eax,pyrRailHalf3D[edi*4]
mov pyrRailHalf3D[esi*4],eax
mov pyrRailHalf3D[edi*4],eax
pyr_contact_done:
ret
PyramidContact3D ENDP
PyramidContactEnd3D PROC uses ebx esi edi moving:DWORD,other:DWORD
mov esi,moving
mov edi,other
test pyrRailHalf3D[esi*4],1
jz @F
cmp poolX3D[edi*4],160*FP_1
jge pyr_contact_valid
@@:
test pyrRailHalf3D[esi*4],2
jz @F
cmp poolX3D[edi*4],160*FP_1
jle pyr_contact_valid
@@:
; A frozen-ball contact counts only against an eligible cushion history.
mov ebx,0
cmp poolY3D[edi*4],50*FP_1+POOL_RADIUS+256
jle pyr_contact_frozen
mov ebx,1
cmp poolY3D[edi*4],180*FP_1-POOL_RADIUS-256
jge pyr_contact_frozen
mov ebx,2
cmp poolX3D[edi*4],30*FP_1+POOL_RADIUS+256
jle pyr_contact_frozen
mov ebx,3
cmp poolX3D[edi*4],290*FP_1-POOL_RADIUS-256
jl pyr_contact_end_done
pyr_contact_frozen:
cmp pyrHistoryCross3D[esi*4],0
jne pyr_contact_valid
mov eax,pyrHistoryRails3D[esi*4]
mov ecx,ebx
btr eax,ecx
test eax,eax
jz pyr_contact_end_done
pyr_contact_valid:
mov pyrValidEnd3D,1
pyr_contact_end_done:
ret
PyramidContactEnd3D ENDP
PyramidRail3D PROC uses ebx ball:DWORD,side:DWORD
cmp poolFirst3D,-1
je pyr_rail_done
mov ebx,ball
mov eax,pyrHistoryRails3D[ebx*4]
mov ecx,side
btr eax,ecx
test eax,eax
jnz pyr_rail_valid
cmp pyrHistoryCross3D[ebx*4],0
je @F
pyr_rail_valid:
mov pyrValidEnd3D,1
@@:
mov ecx,side
bts pyrHistoryRails3D[ebx*4],ecx
mov eax,1
cmp poolX3D[ebx*4],160*FP_1
jl @F
mov eax,2
@@:
or pyrRailHalf3D[ebx*4],eax
pyr_rail_done:
ret
PyramidRail3D ENDP
PyramidCross3D PROC uses ebx ball:DWORD
cmp poolFirst3D,-1
je pyr_cross_done
mov ebx,ball
mov eax,pyrPreviousX3D[ebx*4]
cmp eax,160*FP_1
je pyr_cross_done
jl pyr_cross_from_left
cmp poolX3D[ebx*4],160*FP_1
jge pyr_cross_done
jmp pyr_cross_record
pyr_cross_from_left:
cmp poolX3D[ebx*4],160*FP_1
jle pyr_cross_done
pyr_cross_record:
mov pyrHistoryCross3D[ebx*4],1
cmp pyrHistoryRails3D[ebx*4],0
je pyr_cross_done
mov pyrValidEnd3D,1
pyr_cross_done:
ret
PyramidCross3D ENDP
PyramidCanPlace3D PROC uses ebx esi ball:DWORD,px:DWORD,py:DWORD
cmp px,30*FP_1+POOL_RADIUS
jl pyr_place_bad
cmp px,290*FP_1-POOL_RADIUS
jg pyr_place_bad
cmp py,50*FP_1+POOL_RADIUS
jl pyr_place_bad
cmp py,180*FP_1-POOL_RADIUS
jg pyr_place_bad
xor esi,esi
pyr_place_scan:
cmp esi,ball
je pyr_place_next
cmp poolActive3D[esi*4],0
je pyr_place_next
mov eax,px
sub eax,poolX3D[esi*4]
sar eax,12
imul eax,eax
mov ebx,eax
mov eax,py
sub eax,poolY3D[esi*4]
sar eax,12
imul eax,eax
add eax,ebx
cmp eax,25*16*16
jbe pyr_place_bad
pyr_place_next:
inc esi
cmp esi,16
jb pyr_place_scan
mov eax,1
ret
pyr_place_bad:
xor eax,eax
ret
PyramidCanPlace3D ENDP
PyramidRespot3D PROC uses ebx esi ball:DWORD
mov ebx,225*FP_1
pyr_respot_foot:
invoke PyramidCanPlace3D,ball,ebx,115*FP_1
test eax,eax
jnz pyr_respot_found
add ebx,FP_1*501/100
cmp ebx,287*FP_1
jle pyr_respot_foot
mov ebx,225*FP_1-FP_1*501/100
pyr_respot_front:
invoke PyramidCanPlace3D,ball,ebx,115*FP_1
test eax,eax
jnz pyr_respot_found
sub ebx,FP_1*501/100
cmp ebx,33*FP_1
jge pyr_respot_front
; Pathological layouts still keep every ball: bounded free-cloth fallback.
mov esi,55*FP_1
pyr_respot_row:
mov ebx,35*FP_1
pyr_respot_grid:
invoke PyramidCanPlace3D,ball,ebx,esi
test eax,eax
jnz pyr_respot_grid_found
add ebx,6*FP_1
cmp ebx,285*FP_1
jle pyr_respot_grid
add esi,6*FP_1
cmp esi,175*FP_1
jle pyr_respot_row
xor eax,eax
ret
pyr_respot_found:
mov esi,115*FP_1
pyr_respot_grid_found:
mov ecx,ball
mov poolX3D[ecx*4],ebx
mov poolY3D[ecx*4],esi
mov poolVX3D[ecx*4],0
mov poolVY3D[ecx*4],0
mov poolActive3D[ecx*4],1
mov poolPocket3D[ecx*4],-1
mov poolSinkTime3D[ecx*4],0
mov pyrOwner3D[ecx*4],-1
mov eax,1
ret
PyramidRespot3D ENDP
PoolPlaceCue3D PROC px:DWORD,py:DWORD
invoke PyramidCanPlace3D,0,px,py
test eax,eax
jz @F
mov eax,px
mov poolX3D,eax
mov eax,py
mov poolY3D,eax
mov poolActive3D,1
mov poolPocket3D,-1
mov poolSinkTime3D,0
mov poolVX3D,0
mov poolVY3D,0
mov eax,1
@@:
ret
PoolPlaceCue3D ENDP
PoolBallLegal3D PROC ball:DWORD
mov ecx,ball
xor eax,eax
cmp ecx,15
ja @F
cmp ecx,pyrCue3D
je @F
cmp poolActive3D[ecx*4],1
sete al
@@:
ret
PoolBallLegal3D ENDP
PoolCountGroup3D PROC
mov poolAtStart3D,0
ret
PoolCountGroup3D ENDP
PoolFinishShot3D PROC uses ebx esi edi
cmp pyrSettled3D,0
jne pyr_finish_done
mov pyrSettled3D,1
mov poolFoul3D,0
mov pyrFoulReason3D,0
mov pyrIllegalBreak3D,0
cmp poolFirst3D,-1
jne pyr_finish_contact
mov pyrFoulReason3D,1
jmp pyr_finish_foul
pyr_finish_contact:
cmp poolBreak3D,0
je pyr_finish_normal
cmp poolPotted3D,0
jne pyr_finish_score
mov eax,poolBreakRails3D
mov ecx,pyrCue3D
btr eax,ecx
xor ebx,ebx
pyr_finish_rail_count:
test eax,eax
jz @F
lea ecx,[eax-1]
and eax,ecx
inc ebx
jmp pyr_finish_rail_count
@@:
cmp ebx,3
jae pyr_finish_turn
cmp ebx,2
jb pyr_finish_bad_break
xor esi,esi
pyr_finish_break_cross:
cmp esi,pyrCue3D
je @F
cmp pyrHistoryCross3D[esi*4],0
jne pyr_finish_turn
@@:
inc esi
cmp esi,16
jb pyr_finish_break_cross
pyr_finish_bad_break:
mov pyrFoulReason3D,2
jmp pyr_finish_foul
pyr_finish_normal:
cmp poolPotted3D,0
jne pyr_finish_score
cmp pyrValidEnd3D,0
jne pyr_finish_turn
mov pyrFoulReason3D,3
pyr_finish_foul:
mov poolFoul3D,1
mov eax,poolBreak3D
mov pyrIllegalBreak3D,eax
xor esi,esi
pyr_finish_restore:
bt poolPotted3D,esi
jnc @F
invoke PyramidRespot3D,esi
@@:
inc esi
cmp esi,16
jb pyr_finish_restore
mov eax,poolPlayer3D
mov pyrFouler3D,eax
xor eax,1
mov poolPlayer3D,eax
mov pyrChoiceActor3D,eax
mov poolState3D,POOL_PENALTY
mov poolTime3D,0
mov poolBreak3D,0
ret
pyr_finish_score:
mov ebx,poolPlayer3D
xor esi,esi
pyr_finish_credit:
bt poolPotted3D,esi
jnc @F
mov pyrOwner3D[esi*4],ebx
inc pyrScore3D[ebx*4]
@@:
inc esi
cmp esi,16
jb pyr_finish_credit
cmp pyrScore3D[ebx*4],8
jb pyr_finish_aim
mov poolWinner3D,ebx
mov poolState3D,POOL_RESULT
ret
pyr_finish_turn:
xor poolPlayer3D,1
pyr_finish_aim:
mov poolBreak3D,0
mov poolState3D,POOL_AIM
mov poolTime3D,0
invoke PyramidSelect3D,-1
pyr_finish_done:
ret
PoolFinishShot3D ENDP
PyramidPenalty3D PROC uses ebx ball:DWORD
cmp poolState3D,POOL_PENALTY
jne pyr_penalty_done
mov ecx,ball
cmp ecx,15
ja pyr_penalty_done
cmp poolActive3D[ecx*4],1
jne pyr_penalty_done
mov ebx,poolPlayer3D
mov pyrOwner3D[ecx*4],ebx
mov poolActive3D[ecx*4],0
mov poolPocket3D[ecx*4],-2
mov poolSinkTime3D[ecx*4],0
mov poolVX3D[ecx*4],0
mov poolVY3D[ecx*4],0
inc pyrScore3D[ebx*4]
cmp pyrScore3D[ebx*4],8
jb @F
mov poolWinner3D,ebx
mov poolState3D,POOL_RESULT
ret
@@:
mov poolState3D,POOL_CHOICE
mov poolTime3D,0
pyr_penalty_done:
ret
PyramidPenalty3D ENDP
PyramidChoice3D PROC uses ebx esi edi choice:DWORD
cmp poolState3D,POOL_CHOICE
jne pyr_choice_done
cmp choice,3
ja pyr_choice_done
cmp choice,2
jb pyr_choice_actor
cmp pyrIllegalBreak3D,0
je pyr_choice_done
; The penalty score survives a partial re-rack.
cmp pyrOwner3D, -1
je @F
mov esi,1
pyr_rack_exchange:
cmp pyrOwner3D[esi*4],-1
je pyr_rack_swap
inc esi
cmp esi,16
jb pyr_rack_exchange
jmp pyr_choice_done
pyr_rack_swap:
mov eax,pyrOwner3D
mov pyrOwner3D[esi*4],eax
mov poolActive3D[esi*4],0
mov poolPocket3D[esi*4],-2
mov pyrOwner3D,-1
@@:
mov poolActive3D,1
mov poolPocket3D,-1
mov poolX3D,82*FP_1
mov poolY3D,115*FP_1
mov poolVX3D,0
mov poolVY3D,0
mov poolBreak3D,1
mov pyrCue3D,0
call PyramidRack3D
pyr_choice_actor:
mov eax,pyrChoiceActor3D
test choice,1
jz @F
xor eax,1
@@:
mov poolPlayer3D,eax
mov poolState3D,POOL_AIM
mov poolTime3D,0
mov pyrFineActive3D,0
mov poolAngle3D,0
invoke PyramidSelect3D,-1
pyr_choice_done:
ret
PyramidChoice3D ENDP
; Center a partial base; move the apex to the base if parity requires it.
PyramidRack3D PROC uses ebx esi edi
local remaining:DWORD,rowCount:DWORD,startSlot:DWORD,apex:DWORD
local apexX:DWORD,apexY:DWORD,moveApex:DWORD
mov remaining,0
mov apex,-1
mov moveApex,0
mov esi,1
pyr_rack_count:
cmp pyrOwner3D[esi*4],-1
jne @F
inc remaining
@@:
inc esi
cmp esi,16
jb pyr_rack_count
xor ebx,ebx
mov esi,1
pyr_rack_row:
lea eax,[ebx+1]
cmp eax,remaining
jbe @F
mov eax,remaining
@@:
mov rowCount,eax
mov ecx,ebx
inc ecx
sub ecx,eax
test ecx,1
jz pyr_rack_even
; One apex ball joins the last row instead of creating an asymmetric base.
dec ecx
shr ecx,1
mov startSlot,ecx
imul eax,ebx,FP_1*43302/10000
add eax,225*FP_1
mov apexX,eax
imul eax,ecx,5*FP_1
imul edx,ebx,FP_1*5/2
sub eax,edx
add eax,115*FP_1
mov apexY,eax
mov moveApex,1
inc startSlot
jmp pyr_rack_row_balls
pyr_rack_even:
shr ecx,1
mov startSlot,ecx
pyr_rack_row_balls:
xor edi,edi
pyr_rack_find:
cmp pyrOwner3D[esi*4],-1
je pyr_rack_place
inc esi
cmp esi,16
jb pyr_rack_find
jmp pyr_rack_complete
pyr_rack_place:
cmp apex,-1
jne @F
mov apex,esi
@@:
imul eax,ebx,FP_1*43302/10000
add eax,225*FP_1
mov poolX3D[esi*4],eax
mov eax,edi
add eax,startSlot
imul eax,5*FP_1
imul ecx,ebx,FP_1*5/2
sub eax,ecx
add eax,115*FP_1
mov poolY3D[esi*4],eax
mov poolActive3D[esi*4],1
mov poolPocket3D[esi*4],-1
mov poolVX3D[esi*4],0
mov poolVY3D[esi*4],0
mov poolSinkTime3D[esi*4],0
inc esi
inc edi
dec remaining
cmp edi,rowCount
jb pyr_rack_find
inc ebx
cmp remaining,0
jne pyr_rack_row
pyr_rack_complete:
cmp moveApex,0
je @F
mov ecx,apex
mov eax,apexX
mov poolX3D[ecx*4],eax
mov eax,apexY
mov poolY3D[ecx*4],eax
@@:
ret
PyramidRack3D ENDP

View file

@ -0,0 +1,254 @@
RRPartyReset3D PROC
cmp rrPartyCount3D,1
jb rr_party_default
cmp rrPartyCount3D,3
jbe @F
rr_party_default:
mov rrPartyCount3D,1
@@:
mov ecx,rrPartyCount3D
inc ecx
mov rrPartySize3D,ecx
mov eax,1
shl eax,cl
dec eax
mov rrPartyAlive3D,eax
mov rrPartyStake3D,0
mov rrPartySettled3D,1
mov rrPartyReloads3D,0
ret
RRPartyReset3D ENDP
RRPartyInit3D PROC
call RRPartyReset3D
invoke LoadAnimPack3D,OFFSET rrPartyArt3D,OFFSET rrPartyPath3D
cmp rrPartyArt3D.frames,6
jne rr_party_bad_art
cmp rrPartyArt3D.imageWidth,192
jne rr_party_bad_art
cmp rrPartyArt3D.height,192
je rr_party_init_done
rr_party_bad_art:
invoke FreeAnimPack3D,OFFSET rrPartyArt3D
rr_party_init_done:
mov eax,1
ret
RRPartyInit3D ENDP
RRPartySetCount3D PROC count:DWORD
cmp count,1
jb rr_party_count_done
cmp count,3
ja rr_party_count_done
cmp rr3d_state,RR_READY
je @F
cmp rr3d_state,RR_WIN
jne rr_party_count_done
@@:
mov eax,count
mov rrPartyCount3D,eax
mov rr3d_state,RR_READY
mov rr3d_timer,0
call RRPartyReset3D
call ClearKeys3D
rr_party_count_done:
ret
RRPartySetCount3D ENDP
; Called once at the initial deal, and later only when all loaded rounds are spent.
RRLoadCylinder3D PROC uses esi
mov rr3d_mask,0
xor esi,esi
rr_party_cartridge:
invoke RandomRange3D,6
bt rr3d_mask,eax
jc rr_party_cartridge
bts rr3d_mask,eax
inc esi
cmp esi,rr3d_bullets
jb rr_party_cartridge
invoke RandomRange3D,6
mov rr3d_chamber,eax
inc rrPartyReloads3D
ret
RRLoadCylinder3D ENDP
RRPartyWin3D PROC
cmp rrPartySettled3D,0
jne rr_party_win_done
mov rrPartySettled3D,1
mov eax,rrPartyStake3D
imul eax,rrPartySize3D
mov rr3d_payout,eax
add bankroll,eax
mov rr3d_state,RR_WIN
invoke PlayCasinoSound3D,OFFSET soundRoundWin,0,20003h
invoke NpcRecordRound3D,6,1
call SaveProgress3D
rr_party_win_done:
ret
RRPartyWin3D ENDP
RRPartyLose3D PROC
cmp rrPartySettled3D,0
jne rr_party_lose_done
mov rrPartySettled3D,1
mov rr3d_state,RR_GLITCH
invoke NpcRecordRound3D,6,0
call SaveProgress3D
rr_party_lose_done:
ret
RRPartyLose3D ENDP
; No selectable target: every participant takes their own turn with the same cylinder.
RRPartyHit3D PROC
cmp rrPartySettled3D,0
jne rr_party_hit_done
mov ecx,rr3d_chamber
btr rr3d_mask,ecx
mov ecx,rr3d_turn
cmp ecx,rrPartySize3D
jae rr_party_hit_done
btr rrPartyAlive3D,ecx
test ecx,ecx
jz rr_party_player_out
cmp rrPartyAlive3D,1
je rr_party_last_player
mov rr3d_state,RR_DRY
ret
rr_party_player_out:
call RRPartyLose3D
ret
rr_party_last_player:
call RRPartyWin3D
rr_party_hit_done:
ret
RRPartyHit3D ENDP
RRPartyNext3D PROC uses ebx
mov ebx,rrPartySize3D
rr_party_next_seat:
inc rr3d_turn
mov eax,rr3d_turn
cmp eax,rrPartySize3D
jb @F
mov rr3d_turn,0
xor eax,eax
@@:
bt rrPartyAlive3D,eax
jc rr_party_next_found
dec ebx
jnz rr_party_next_seat
call RRPartyLose3D
ret
rr_party_next_found:
cmp rr3d_mask,0
jne rr_party_same_cylinder
call RRLoadCylinder3D
mov rr3d_state,RR_LOAD
invoke PlayCasinoSound3D,OFFSET rrLoadSound3D,0,20003h
ret
rr_party_same_cylinder:
inc rr3d_chamber
cmp rr3d_chamber,6
jb @F
mov rr3d_chamber,0
@@:
mov rr3d_state,RR_PLAYER
cmp rr3d_turn,0
je @F
mov rr3d_state,RR_RAISE
@@:
ret
RRPartyNext3D ENDP
AbortRussianRound3D PROC
cmp rrPartySettled3D,0
jne @F
mov rrPartySettled3D,1
invoke NpcRecordRound3D,6,0
call SaveProgress3D
@@:
mov rr3d_state,RR_READY
mov rr3d_timer,0
ret
AbortRussianRound3D ENDP
RRPartyInput3D PROC uses ebx
cmp rr3d_state,RR_READY
je rr_party_ready_keys
cmp rr3d_state,RR_WIN
je rr_party_mouse
cmp rr3d_state,RR_PLAYER
je rr_party_mouse
jmp rr_party_mouse_exit
rr_party_ready_keys:
invoke Pressed3D,25h
test eax,eax
jz @F
mov eax,rrPartyCount3D
dec eax
invoke RRPartySetCount3D,eax
@@:
invoke Pressed3D,27h
test eax,eax
jz rr_party_mouse
mov eax,rrPartyCount3D
inc eax
invoke RRPartySetCount3D,eax
rr_party_mouse:
cmp mouseClick3D,0
je rr_party_input_done
cmp rr3d_state,RR_READY
jne rr_party_mouse_start
cmp mouseY3D,226
jb rr_party_mouse_start
cmp mouseY3D,239
jae rr_party_mouse_start
mov eax,mouseX3D
sub eax,188
jc rr_party_mouse_start
cmp eax,58
jae rr_party_mouse_start
xor edx,edx
mov ecx,20
div ecx
cmp edx,18
jae rr_party_mouse_start
inc eax
invoke RRPartySetCount3D,eax
mov mouseClick3D,0
ret
rr_party_mouse_start:
cmp mouseY3D,225
jb rr_party_mouse_exit
cmp mouseY3D,239
jae rr_party_mouse_exit
cmp mouseX3D,8
jb rr_party_mouse_exit
cmp mouseX3D,150
jae rr_party_mouse_exit
mov pressedKeys3D[20h],1
mov mouseClick3D,0
ret
rr_party_mouse_exit:
cmp mouseClick3D,0
je rr_party_input_done
cmp mouseY3D,3
jb rr_party_clear_mouse
cmp mouseY3D,17
jae rr_party_clear_mouse
cmp mouseX3D,252
jb rr_party_clear_mouse
cmp mouseX3D,312
jae rr_party_clear_mouse
mov mouseClick3D,0
call RequestCardExit3D
ret
rr_party_clear_mouse:
mov mouseClick3D,0
rr_party_input_done:
ret
RRPartyInput3D ENDP
include src_graphics\russian_party_render.inc

View file

@ -0,0 +1,36 @@
RRPartyInit3D PROTO
RRPartyReset3D PROTO
RRPartySetCount3D PROTO :DWORD
RRPartyInput3D PROTO
RRLoadCylinder3D PROTO
RRPartyHit3D PROTO
RRPartyNext3D PROTO
RRPartyWin3D PROTO
RRPartyLose3D PROTO
AbortRussianRound3D PROTO
RenderRRPartyCharacters3D PROTO
RenderRRPartyGun3D PROTO
RenderRRPartyControls3D PROTO
RenderRussianExit3D PROTO
.data
rrPartyCount3D dd 1
rrPartySize3D dd 2
rrPartyAlive3D dd 3
rrPartyStake3D dd 0
rrPartySettled3D dd 1
rrPartyReloads3D dd 0
rrPartyArt3D AnimPack3D <>
rrPartyPath3D db "assets\animations\roulette\participants-v1.bin",0
rrPartyNames3D dd OFFSET rrPartyName0,OFFSET rrPartyName1,OFFSET rrPartyName2
rrPartyName0 db 210,206,203,223,0
rrPartyName1 db 209,197,208,195,197,201,0
rrPartyName2 db 195,208,200,195,206,208,200,201,0
rrPartyCountText3D db 209,206,207,197,208,205,200,202,200,0
rrPartyOutText3D db 194,219,193,219,203,0
rrPartyTurnText3D db 213,206,196,0
rrPartyAliveText3D db 206,198,200,196,192,197,210,0
rrPartyPoolText3D db 193,192,205,202,0
rrPartyExitText3D db 208,224,243,237,228,32,229,249,184,32,237,229,32,231,224,234,238,237,247,229,237,46,0
rrPartyDigits3D db "1",0,"2",0,"3",0
rrPartyX3D dd 32,232,432
.code

View file

@ -0,0 +1,173 @@
RenderRRPartyCharacters3D PROC uses ebx esi edi
local px:DWORD,frame:DWORD,ink:DWORD
xor ebx,ebx
rr_party_draw_guest:
cmp ebx,rrPartyCount3D
jae rr_party_draw_done
mov esi,rrPartyX3D[ebx*4]
cmp rrPartyCount3D,2
jne @F
imul esi,ebx,200
add esi,132
@@:
mov px,esi
mov frame,ebx
lea edi,[ebx+1]
mov ink,00D7C7A4h
bt rrPartyAlive3D,edi
jnc rr_party_inactive
cmp rr3d_turn,edi
jne rr_party_draw_body
cmp rr3d_state,RR_RAISE
je rr_party_active_body
cmp rr3d_state,RR_FIRE
je rr_party_active_body
cmp rr3d_state,RR_DRY
jne rr_party_draw_body
rr_party_active_body:
add frame,3
mov ink,00FFE09Ah
rr_party_draw_body:
mov animOpacity3D,255
jmp rr_party_body
rr_party_inactive:
mov ink,00697873h
cmp rr3d_turn,edi
jne rr_party_caption
cmp rr3d_state,RR_DRY
jne rr_party_caption
mov eax,700
sub eax,rr3d_timer
jle rr_party_caption
imul eax,255
xor edx,edx
mov ecx,700
div ecx
mov animOpacity3D,eax
rr_party_body:
invoke DrawAnimSprite3D,OFFSET rrPartyArt3D,frame,px,48,176,176
rr_party_caption:
mov animOpacity3D,255
lea eax,[esi+16]
invoke DrawBuckshotText3D,eax,38,rrPartyNames3D[ebx*4],ink
mov ecx,OFFSET rrPartyOutText3D
bt rrPartyAlive3D,edi
jnc @F
mov ecx,OFFSET rrPartyAliveText3D
cmp rr3d_state,RR_READY
je @F
cmp rr3d_state,RR_WIN
je @F
cmp rr3d_turn,edi
jne @F
mov ecx,OFFSET rrPartyTurnText3D
@@:
lea eax,[esi+16]
invoke DrawBuckshotText3D,eax,230,ecx,ink
inc ebx
jmp rr_party_draw_guest
rr_party_draw_done:
mov animOpacity3D,255
ret
RenderRRPartyCharacters3D ENDP
; A shared tabletop revolver is passed to the highlighted seat; no extra arm layer.
RenderRRPartyGun3D PROC uses ebx
local target:DWORD,t:DWORD,px:DWORD,py:DWORD
mov ebx,rr3d_turn
dec ebx
cmp ebx,3
jae rr_party_gun_done
mov eax,rrPartyX3D[ebx*4]
cmp rrPartyCount3D,2
jne @F
imul eax,ebx,200
add eax,132
@@:
add eax,88
shr eax,1
mov target,eax
mov eax,750
cmp rr3d_state,RR_RAISE
jne @F
mov eax,rr3d_timer
cmp eax,750
jbe @F
mov eax,750
@@:
mov t,eax
mov eax,target
sub eax,160
imul eax,t
cdq
mov ecx,750
idiv ecx
add eax,160
mov px,eax
mov eax,19
imul eax,t
xor edx,edx
div ecx
mov edx,151
sub edx,eax
mov py,edx
invoke DrawRouletteSprite3D,0,px,py,98,52,0
rr_party_gun_done:
ret
RenderRRPartyGun3D ENDP
RenderRRPartyControls3D PROC uses ebx esi
cmp rrPartyCount3D,1
jbe @F
invoke DrawBuckshotText3D,244,414,OFFSET rrPartyPoolText3D,00EBD5A0h
mov eax,rrPartyStake3D
test eax,eax
jnz rr_party_pool_locked
mov eax,rr3d_bet
rr_party_pool_locked:
imul eax,rrPartySize3D
invoke DrawBuckshotNumber3D,296,414,eax,00FFFFFFh
@@:
cmp rr3d_state,RR_READY
jne rr_party_controls_done
invoke DrawBuckshotText3D,280,462,OFFSET rrPartyCountText3D,00D7C7A4h
xor ebx,ebx
rr_party_control:
mov esi,ebx
imul esi,20
add esi,188
mov eax,0025352Dh
mov ecx,00766B51h
lea edx,[ebx+1]
cmp edx,rrPartyCount3D
jne @F
mov eax,003D6546h
mov ecx,00EBD5A0h
@@:
invoke DrawBoxBorder,esi,226,18,13,eax,ecx,1
lea ecx,rrPartyDigits3D[ebx*2]
lea eax,[esi*2+14]
invoke DrawBuckshotText3D,eax,462,ecx,00EFE7D4h
inc ebx
cmp ebx,3
jb rr_party_control
rr_party_controls_done:
ret
RenderRRPartyControls3D ENDP
RenderRussianExit3D PROC
cmp engineMode,MODE_RUSSIAN
jne rr_party_exit_done
cmp cardExitActive3D,0
jne rr_party_exit_show
cmp rr3d_state,RR_READY
je rr_party_exit_show
cmp rr3d_state,RR_WIN
je rr_party_exit_show
cmp rr3d_state,RR_PLAYER
jne rr_party_exit_done
rr_party_exit_show:
call RenderCardExit3D
rr_party_exit_done:
ret
RenderRussianExit3D ENDP

View file

@ -1,6 +1,7 @@
; Native roulette: one cylinder, one paid stake, cinematic states in the same window.
.code
FreeRussianAssets3D PROC
invoke FreeAnimPack3D,OFFSET rrPartyArt3D
invoke FreeAnimPack3D,OFFSET rr3d_room
invoke FreeAnimPack3D,OFFSET rr3d_dealer
invoke FreeAnimPack3D,OFFSET rr3d_hands
@ -72,6 +73,8 @@ InitRussianGame3D PROC
mov rr3d_timer, 0
mov rr3d_payout, 0
mov rr3d_error, 0
mov cardExitActive3D,0
call RRPartyInit3D
@@:
ret
InitRussianGame3D ENDP
@ -84,22 +87,19 @@ StartRussianRound3D PROC uses ebx esi
jle rr_start_empty
cmp eax, bankroll
ja rr_start_empty
cmp rr3d_bullets, 1
jb rr_start_empty
cmp rr3d_bullets, 3
ja rr_start_empty
call RRPartyReset3D
mov eax,rr3d_bet
mov rrPartyStake3D,eax
mov rrPartySettled3D,0
sub bankroll, eax
mov rr3d_mask, 0
mov rr3d_payout, 0
mov rr3d_error, 0
xor esi, esi
rr_place_cartridge:
invoke RandomRange3D, 6
bt rr3d_mask, eax
jc rr_place_cartridge
bts rr3d_mask, eax
inc esi
cmp esi, rr3d_bullets
jb rr_place_cartridge
invoke RandomRange3D, 6
mov rr3d_chamber, eax
invoke RandomRange3D, 2
call RRLoadCylinder3D
invoke RandomRange3D, rrPartySize3D
mov rr3d_turn, eax
mov rr3d_state, RR_LOAD
mov rr3d_timer, 0
@ -163,6 +163,11 @@ rr_next_outcome:
mov rr3d_state, RR_DRY
ret
rr_live_round:
cmp rrPartyCount3D,1
jbe @F
call RRPartyHit3D
ret
@@:
cmp rr3d_turn, 0
je rr_player_lost
mov eax, rr3d_bet
@ -170,14 +175,23 @@ rr_live_round:
mov rr3d_payout, eax
add bankroll, eax
mov rr3d_state, RR_WIN
mov rrPartySettled3D,1
invoke PlayCasinoSound3D, OFFSET soundRoundWin, 0, 20003h
invoke NpcRecordRound3D,6,1
call SaveProgress3D
ret
rr_player_lost:
mov rrPartySettled3D,1
mov rr3d_state, RR_GLITCH
invoke NpcRecordRound3D,6,0
call SaveProgress3D
ret
rr_next_chamber:
cmp rrPartyCount3D,1
jbe @F
call RRPartyNext3D
ret
@@:
inc rr3d_chamber
cmp rr3d_chamber, 6
jb @F
@ -209,6 +223,14 @@ rr_next_casino:
AdvanceRussianScene3D ENDP
HandleRussianInput3D PROC uses ebx
call HandleCardExit3D
test eax,eax
jnz rr_input_done
call RRPartyInput3D
cmp cardExitActive3D,0
jne rr_input_done
cmp engineMode,MODE_RUSSIAN
jne rr_input_done
cmp rr3d_state, RR_READY
je rr_input_ready
cmp rr3d_state, RR_PLAYER
@ -295,6 +317,7 @@ rr_input_next:
jz rr_input_done
mov rr3d_state, RR_READY
mov rr3d_timer, 0
call RRPartyReset3D
rr_input_done:
ret
HandleRussianInput3D ENDP
@ -542,6 +565,7 @@ RenderRussianGame3D PROC uses ebx esi edi
cmp rr3d_state, RR_GMAN
je rr_render_gman
call RenderRussianTable3D
call RenderRussianExit3D
ret
invoke DrawBox, 0, 0, 320, 240, 00100D17h
invoke DrawBoxBorder, 7, 7, 306, 226, 00192320h, 00BE994Ah, 2
@ -717,5 +741,7 @@ rr_render_return:
rr_render_hall:
call RenderRussianHall3D
rr_render_done:
call RenderRussianExit3D
ret
RenderRussianGame3D ENDP
include src_graphics\russian_party.inc

View file

@ -9,6 +9,11 @@ RenderRussianTable3D PROC uses ebx esi edi
lea edi,screenBuffer
mov ecx,SCREEN_SIZE
rep movsd
cmp rrPartyCount3D,1
jbe rr_table_original_dealer
call RenderRRPartyCharacters3D
jmp rr_table_actions
rr_table_original_dealer:
mov eax,0
cmp rr3d_turn,1
jne @F
@ -22,6 +27,7 @@ rr_table_dealer_active:
mov eax,2
@@:
invoke DrawAnimSprite3D,OFFSET rr3d_dealer,eax,192,2,256,224
rr_table_actions:
mov eax,rr3d_state
cmp eax,RR_LOAD
je rr_table_load
@ -92,6 +98,13 @@ rr_table_dry:
mov pose,4
mov angle,128
rr_table_raised:
cmp rrPartyCount3D,1
jbe @F
cmp rr3d_turn,0
je @F
call RenderRRPartyGun3D
jmp rr_table_hud
@@:
cmp rr3d_turn,1
je rr_table_dealer_gun
; Only the player's hands/gun are visible, never a portrait of the player.
@ -107,17 +120,45 @@ rr_table_glitch:
rr_table_hud:
invoke DrawBuckshotPanel3D,0,0,640,32,00111116h
invoke DrawBuckshotText3D,14,12,OFFSET rrTitleText3D,00E9C47Ah
invoke DrawBuckshotText3D,488,12,OFFSET bs3d_textBank,00BCBCBCh
invoke DrawBuckshotNumber3D,540,12,bankroll,00F3E4BAh
invoke DrawBuckshotText3D,360,12,OFFSET bs3d_textBank,00BCBCBCh
invoke DrawBuckshotNumber3D,412,12,bankroll,00F3E4BAh
invoke DrawBuckshotPanel3D,0,400,640,80,00111617h
invoke DrawBuckshotText3D,16,414,OFFSET rrCartridgeText3D,00EBD5A0h
invoke DrawBuckshotNumber3D,160,414,rr3d_bullets,00FFFFFFh
mov ebx,rr3d_bullets
cmp rrPartyCount3D,1
jbe @F
cmp rr3d_state,RR_READY
je @F
xor ebx,ebx
mov edx,rr3d_mask
rr_table_remaining_rounds:
shr edx,1
adc ebx,0
test edx,edx
jnz rr_table_remaining_rounds
@@:
invoke DrawBuckshotNumber3D,160,414,ebx,00FFFFFFh
invoke DrawBuckshotText3D,456,414,OFFSET bs3d_textBet,00EBD5A0h
invoke DrawBuckshotNumber3D,528,414,rr3d_bet,00FFFFFFh
mov eax,rrPartyStake3D
cmp rr3d_state,RR_READY
je rr_table_selected_stake
test eax,eax
jnz rr_table_stake
rr_table_selected_stake:
mov eax,rr3d_bet
rr_table_stake:
invoke DrawBuckshotNumber3D,528,414,eax,00FFFFFFh
mov ebx,OFFSET rrPlayerText3D
cmp rr3d_turn,0
je @F
mov ebx,OFFSET rrDealerText3D
cmp rrPartyCount3D,1
jbe @F
mov eax,rr3d_turn
dec eax
cmp eax,3
jae @F
mov ebx,rrPartyNames3D[eax*4]
@@:
cmp rr3d_state,RR_LOAD
jne @F
@ -131,7 +172,10 @@ rr_table_hud:
jne @F
mov ebx,OFFSET rrClickText3D
@@:
cmp rr3d_state,RR_READY
je @F
invoke DrawBuckshotText3D,16,440,ebx,00E9C47Ah
@@:
cmp rr3d_state,RR_READY
jne rr_table_not_ready
invoke DrawBuckshotText3D,16,440,OFFSET rrBulletKeys3D,00EBD5A0h
@ -154,15 +198,9 @@ rr_table_not_ready:
invoke DrawBuckshotNumber3D,184,440,rr3d_payout,007FE395h
invoke DrawBuckshotText3D,16,462,OFFSET rrRoundText3D,00FFFFFFh
rr_table_exit_hint:
mov ebx,OFFSET bs3d_textPause
cmp rr3d_state,RR_READY
je rr_table_hall_hint
cmp rr3d_state,RR_WIN
jne @F
rr_table_hall_hint:
mov ebx,OFFSET bs3d_textExit
@@:
invoke DrawBuckshotText3D,530,462,ebx,00AFAFAFh
call RenderRRPartyControls3D
ret
rr_table_missing:
invoke DrawBuckshotPanel3D,0,0,640,480,00111719h

View file

@ -3,3 +3,4 @@ rr3d_room AnimPack3D <>
rr3d_dealer AnimPack3D <>
rr3d_hands AnimPack3D <>
rrHandsPath3D db "assets\animations\roulette\hands-v5.bin",0
include src_graphics\russian_party_data.inc

View file

@ -45,3 +45,4 @@ tableHandAnchorY3D dd 150,150,154,147,147,153,37,37
.data?
tableProtectedRows3D dd (640*(88+36)) dup(?)
.code
include src_graphics\native_blackjack_party_data.inc

View file

@ -0,0 +1,36 @@
diff --git a/poker.asm b/poker.asm
--- a/poker.asm
+++ b/poker.asm
@@ -165,1 +165,2 @@
slotComboMults dd 32 dup(0)
+slotComboMasks dd 32 dup(0)
@@ -11292,1 +11293,3 @@
mov slotComboMults[ebx * 4], edx
+ mov ecx,maskValue
+ mov slotComboMasks[ebx * 4],ecx
diff --git a/casino3d.inc b/casino3d.inc
--- a/casino3d.inc
+++ b/casino3d.inc
@@ -156,1 +156,2 @@
include src_graphics\native_slots_data.inc
+include src_graphics\native_clover_data.inc
@@ -4730,1 +4731,4 @@
- call BeginUnavailable3D
+ call InitNativeClover3D
+ test eax,eax
+ jz target_unported
+ mov engineMode,MODE_CLOVER
@@ -5131,1 +5135,2 @@
include src_graphics\native_slots.inc
+include src_graphics\native_clover.inc
@@ -5255,1 +5260,7 @@
check_slots_mode:
+ cmp engineMode,MODE_CLOVER
+ jne @F
+ call HandleNativeClover3D
+ call RenderNativeClover3D
+ jmp present_frame
+@@:
@@ -5485,1 +5496,2 @@
call FreeNativeSlots3D
+ call FreeNativeClover3D

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,52 @@
diff --git a/casino3d.inc b/casino3d.inc
--- a/casino3d.inc
+++ b/casino3d.inc
@@ -149,1 +149,2 @@
include src_graphics\npc_mind_data.inc
+include src_graphics\npc_life_data.inc
@@ -5124,1 +5125,3 @@
include src_graphics\npc_mind.inc
+include src_graphics\npc_life.inc
+include src_graphics\npc_contracts.inc
@@ -5240,1 +5243,7 @@
check_slots_mode:
+ cmp engineMode,MODE_NPC_DIALOG
+ jne @F
+ call HandleNpcDialog3D
+ call RenderNpcDialog3D
+ jmp present_frame
+@@:
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
@@ -281,1 +281,2 @@
call InitCasinoNpcMind
+ call InitNpcLife3D
@@ -624,2 +625,3 @@
invoke HallTarget3D, 27*FP_1,25*FP_1,INTERACT_BILLIARDS,OFFSET hallPoolPrompt3D
+ call CheckNpcInteractions3D
ret
@@ -640,1 +642,7 @@
TriggerHallInteraction3D PROC
+ cmp interactTargetId,INTERACT_NPC
+ jne @F
+ call OpenNpcDialog3D
+ ret
+@@:
+
@@ -1207,1 +1215,4 @@
UpdatePatrons3D PROC uses ebx esi edi
+ call UpdateNpcLife3D
+ ret
+IF 0
@@ -1281,1 +1292,2 @@
+ENDIF
UpdatePatrons3D ENDP
@@ -1284,2 +1296,3 @@
RenderPatrons3D PROC uses esi
- xor esi, esi
+ call RenderNpcLife3D
+ mov esi,8
@@ -1291,1 +1304,1 @@
- cmp esi, HALL_PATRON_COUNT
+ cmp esi,12

View file

@ -0,0 +1,37 @@
diff --git a/casino3d.inc b/casino3d.inc
--- a/casino3d.inc
+++ b/casino3d.inc
@@ -3128,2 +3128,3 @@
invoke PlayCasinoSound3D, OFFSET soundRoundWin, 0, 1
+ invoke NpcRecordRound3D,8,1
call SaveProgress3D
@@ -3135,2 +3136,3 @@
invoke PlayCasinoSound3D, OFFSET soundLose, 0, 1
+ invoke NpcRecordRound3D,8,0
call SaveProgress3D
@@ -3413,1 +3415,3 @@
FinalizeSkullsGuess PROC uses ebx
+ cmp skulls3d_state,3
+ jne skulls_guess_done
@@ -3423,2 +3427,4 @@
mov skulls3d_state, 4
invoke PlayCasinoSound3D, OFFSET soundRoundWin, 0, 1
+ invoke NpcRecordRound3D,9,1
+ call SaveProgress3D
@@ -3430,3 +3436,6 @@
mov skulls3d_state, 4
invoke PlayCasinoSound3D, OFFSET soundLose, 0, 1
+ invoke NpcRecordRound3D,9,0
+ call SaveProgress3D
+skulls_guess_done:
ret
@@ -4160,1 +4169,3 @@
mov skill3d_win_amount, eax
+ invoke NpcRecordRound3D,10,1
+ call SaveProgress3D
@@ -4168,3 +4179,5 @@
mov skill3d_state, 5
mov skill3d_timer, 0
+ invoke NpcRecordRound3D,10,0
+ call SaveProgress3D
invoke PlayCasinoSound3D, OFFSET soundLose, 0, 1

View file

@ -0,0 +1,36 @@
"""Offline sprite-sheet normalization; runtime rendering remains MASM."""
import json
import struct
from pathlib import Path
from PIL import Image
ROOT = Path(__file__).resolve().parents[2]
def main():
source = ROOT / "assets/sprites/tables/blackjack-guests-v1.png"
image = Image.open(source).convert("RGBA")
assert image.size == (1536, 1024)
frames = []
for row in range(2):
for column in range(3):
frame = image.crop((column * 512, row * 512, (column + 1) * 512, (row + 1) * 512))
frame = frame.resize((160, 160), Image.Resampling.NEAREST)
assert frame.getpixel((0, 0))[3] == 0
assert sum(alpha >= 240 for alpha in frame.getchannel("A").get_flattened_data()) > 6000
frames.append(frame.tobytes("raw", "BGRA"))
data = b"".join(frames)
path = ROOT / "assets/animations/tables/blackjack-guests-v1.bin"
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(struct.pack("<4sIIII", b"ANM1", 160, 160, 6, len(data)) + data)
room = Image.open(ROOT / "assets/sprites/tables/blackjack-room-v1.png").convert("RGBA")
room = room.resize((640, 480), Image.Resampling.NEAREST)
data = room.tobytes("raw", "BGRA")
room_path = ROOT / "assets/animations/tables/blackjack-room-v1.bin"
room_path.write_bytes(struct.pack("<4sIIII", b"ANM1", 640, 480, 1, len(data)) + data)
print(json.dumps({"guest_frames": 6, "size": [160, 160], "path": str(path), "room": str(room_path)}))
if __name__ == "__main__":
main()

View file

@ -0,0 +1,48 @@
"""Offline normalization of generated art; the game animates reels in MASM."""
import json
import struct
from pathlib import Path
from PIL import Image
ROOT = Path(__file__).resolve().parents[2]
SOURCE = ROOT / "assets/sprites/clover"
OUTPUT = ROOT / "assets/animations/clover"
def pack(name, frames):
width, height = frames[0].size
pixels = b"".join(frame.tobytes("raw", "BGRA") for frame in frames)
(OUTPUT / name).write_bytes(struct.pack("<4s4I", b"ANM1", width, height,
len(frames), len(pixels)) + pixels)
def main():
OUTPUT.mkdir(parents=True, exist_ok=True)
cabinet = Image.open(SOURCE / "cabinet-v1.png").convert("RGBA")
cabinet = cabinet.resize((640, 480), Image.Resampling.NEAREST)
cabinet.putalpha(255)
pack("cabinet-v1.bin", [cabinet])
sheet = Image.open(SOURCE / "symbols-v1.png").convert("RGBA")
assert 1.8 < sheet.width / sheet.height < 2.2
frames = []
for index in range(7):
column, row = index % 4, index // 4
cell = sheet.crop((round(column * sheet.width / 4), round(row * sheet.height / 2),
round((column + 1) * sheet.width / 4), round((row + 1) * sheet.height / 2)))
bounds = cell.getchannel("A").getbbox()
assert bounds, f"Empty symbol {index}"
cell = cell.crop(bounds)
cell.thumbnail((46, 46), Image.Resampling.NEAREST)
frame = Image.new("RGBA", (56, 56))
frame.paste(cell, ((56 - cell.width) // 2, (56 - cell.height) // 2))
frame.putalpha(frame.getchannel("A").point(lambda value: 255 if value >= 128 else 0))
frames.append(frame)
pack("symbols-v1.bin", frames)
(OUTPUT / "manifest.json").write_text(json.dumps({"symbols": ["clover", "lemon", "cherries",
"bell", "diamond", "chest", "seven"], "cabinet": [640, 480], "symbol": [56, 56],
"runtime": "MASM; five reels, three complete rows, legacy additive combos"}, indent=2) + "\n")
print("Packed Clover 5x3 cabinet and seven alpha-tested symbols")
if __name__ == "__main__":
main()

View file

@ -0,0 +1,56 @@
"""Offline art normalization to the exact assembly table and ball anchors."""
from pathlib import Path
import struct
from PIL import Image
ROOT = Path(__file__).resolve().parents[2]
SOURCE = ROOT / "assets/sprites/pyramid"
OUT = ROOT / "assets/animations/pyramid"
def pack(name, frames):
w, h = frames[0].size
payload = b"".join(frame.tobytes("raw", "BGRA") for frame in frames)
(OUT / name).write_bytes(struct.pack("<4s4I", b"ANM1", w, h, len(frames), len(payload)) + payload)
def main():
OUT.mkdir(parents=True, exist_ok=True)
source = Image.open(SOURCE / "table-v2.png").convert("RGBA")
# Nine-slice-style normalization keeps the mouths and cushion noses aligned
# with the shared logical cloth rectangle, rather than scaling the whole art.
source_x = [0, 128, 160, 723, 1290, 1320, 1448]
source_y = [0, 197, 220, 733, 747, 1086]
target_x = [0, 60, 61, 320, 579, 580, 640]
target_y = [0, 100, 101, 359, 360, 480]
frame = Image.new("RGBA", (640, 480))
for row in range(len(target_y) - 1):
for col in range(len(target_x) - 1):
cell = source.crop((round(source_x[col] * source.width / 1448),
round(source_y[row] * source.height / 1086),
round(source_x[col + 1] * source.width / 1448),
round(source_y[row + 1] * source.height / 1086)))
cell = cell.resize((target_x[col + 1] - target_x[col],
target_y[row + 1] - target_y[row]), Image.Resampling.NEAREST)
frame.paste(cell, (target_x[col], target_y[row]))
frame.putalpha(255)
pack("table-v2.bin", [frame])
sheet = Image.open(SOURCE / "balls-v1.png").convert("RGBA")
frames = []
for i in range(8):
col, row = i % 4, i // 4
cell = sheet.crop((round(col * sheet.width / 4), round(row * sheet.height / 2),
round((col + 1) * sheet.width / 4), round((row + 1) * sheet.height / 2)))
# Ignore faint generator fringe outside the actual circular ball.
cell.putalpha(cell.getchannel("A").point(lambda a: 255 if a >= 192 else 0))
cell = cell.crop(cell.getchannel("A").getbbox())
cell.thumbnail((10, 10), Image.Resampling.LANCZOS)
frame = Image.new("RGBA", (12, 12))
frame.paste(cell, ((12 - cell.width) // 2, (12 - cell.height) // 2))
frames.append(frame)
pack("balls-v1.bin", frames)
print("Packed anchored 640x480 table and eight 12x12 Russian ball poses")
if __name__ == "__main__":
main()

View file

@ -0,0 +1,28 @@
"""Offline packing of neutral seated guest artwork, not game logic."""
import struct
from pathlib import Path
from PIL import Image
ROOT = Path(__file__).resolve().parents[2]
def main():
image = Image.open(ROOT / "assets/sprites/roulette/participants-v1.png").convert("RGBA")
assert image.size == (1536, 1024)
frames = []
for row in range(2):
for col in range(3):
frame = image.crop((col * 512, row * 512, (col + 1) * 512, (row + 1) * 512))
frame = frame.resize((192, 192), Image.Resampling.NEAREST)
assert frame.getpixel((0, 0))[3] == 0
assert sum(a >= 240 for a in frame.getchannel("A").get_flattened_data()) > 8000
frames.append(frame.tobytes("raw", "BGRA"))
data = b"".join(frames)
output = ROOT / "assets/animations/roulette/participants-v1.bin"
output.write_bytes(struct.pack("<4sIIII", b"ANM1", 192, 192, 6, len(data)) + data)
print("Packed six neutral seated guest frames for native Russian roulette")
if __name__ == "__main__":
main()

View file

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
import sys
with open('tools/generators/generate_npc_mind.py', 'r', encoding='utf-8') as f:
c = f.read()
c = c.replace('invoke PrintStr, OFFSET chipText', 'invoke PrintStr, OFFSET mpkChipsPrefix')
c = c.replace('invoke PrintStr, OFFSET cheatCodePrompt', 'invoke PrintStr, OFFSET mpkThoughtPrefix')
old_str = 'mpkWaitNextStr db 13,10,"[Нажмите любую клавишу для следующей раздачи, 0 для выхода]...",0\n'
new_str = old_str + 'mpkChipsPrefix db " Стек: $",0\nmpkThoughtPrefix db " [Мысли]: ",0\n'
if old_str in c:
c = c.replace(old_str, new_str)
print("Added prefix strings")
else:
print("Target string not found in c")
with open('tools/generators/generate_npc_mind.py', 'w', encoding='utf-8') as f:
f.write(c)
print("Saved generate_npc_mind.py")

View file

@ -1,92 +1,46 @@
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[2]
with open(REPO_ROOT / 'poker.asm', 'r', encoding='cp1251') as f:
# -*- coding: utf-8 -*-
with open('poker.asm', 'r', encoding='cp1251') as f:
text = f.read()
# 1. Add defaultPlayerSave
if 'defaultPlayerSave' not in text:
target = 'saveFileName db 64 dup(0)'
replacement = 'saveFileName db 64 dup(0)\r\ndefaultPlayerSave db "Player.dat",0'
text = text.replace(target, replacement, 1)
target_str = 'pokerTitle db 13,10,"--- Poker ---",13,10,0\n'
new_str = target_str + '''pokerModeMenuText db "1. Классический покер 1 на 1 (против Дилера)",13,10
db "2. Стол на 4 персоны (Вы против посетителей казино)",13,10
db "0. Выход в главное меню",13,10
db "Выбор: ",0
'''
# 2. Remove include casino3d.inc from old position
old_inc = 'include casino3d.inc'
if old_inc in text:
text = text.replace(old_inc, '; include casino3d.inc moved below', 1)
target_proc = 'GamePoker PROC\nstart_poker:\n'
new_proc = '''GamePoker PROC
poker_choose_mode:
call ClearView
invoke PrintTitleColor, OFFSET pokerTitle
invoke PrintStr, OFFSET pokerModeMenuText
call ReadKey
cmp al, '1'
je start_poker
cmp al, '2'
je poker_launch_multitable
cmp al, '0'
je poker_mode_exit
jmp poker_choose_mode
# 3. Update run_casino_3d in MainMenu
old_run_3d = """run_casino_3d:
call StartCasino3D
jmp menu_loop"""
new_run_3d = """run_casino_3d:
invoke ShowWindow, hCasinoConsole, SW_HIDE
call StartCasino3D
invoke ShowWindow, hCasinoConsole, SW_SHOW
invoke SetForegroundWindow, hCasinoConsole
jmp menu_loop"""
if old_run_3d in text:
text = text.replace(old_run_3d, new_run_3d, 1)
else:
print('Notice: old_run_3d pattern not exact, searching line-by-line')
# 4. Insert include casino3d.inc after MainMenu ENDP and replace main PROC
pos = text.find('exit_menu:')
if pos != -1:
end_main_pos = text.find('END main')
if end_main_pos != -1:
new_main_block = """exit_menu:
poker_launch_multitable:
call GameMultiPoker
ret
MainMenu ENDP
include casino3d.inc
poker_mode_exit:
ret
; --- main: Unified Entry Point ---
main PROC
invoke SetConsoleOutputCP, 65001
invoke SetConsoleCP, 65001
invoke GetStdHandle, STD_OUTPUT_HANDLE
mov hOut, eax
call ConfigureConsoleSize
call EnableAnsi
invoke CreateDirectoryA, OFFSET runtimeDir, 0
invoke GetStdHandle, STD_INPUT_HANDLE
mov hIn, eax
invoke GetTickCount
mov seed, eax
call InitSounds
call InitPaths3D
call AutoLoadAccountFast
cmp accountCount, 0
jne @F
call ResetProfileDefaults
invoke CopyCString, OFFSET saveFileName, OFFSET defaultPlayerSave
call SaveGame
call CollectAccounts
@@:
call ApplySoundVolume
invoke GetConsoleWindow
mov hCasinoConsole, eax
invoke ShowWindow, hCasinoConsole, SW_HIDE
call StartCasino3D
cmp mutex3D, 0
je @F
invoke CloseHandle, mutex3D
@@:
invoke ExitProcess, 0
main ENDP
"""
text = text[:pos] + new_main_block + '\r\nEND main\r\n'
print('Successfully replaced main block!')
else:
print('Error: END main not found')
start_poker:
'''
if target_str not in text:
print("ERROR: target_str not found")
elif target_proc not in text:
print("ERROR: target_proc not found")
else:
print('Error: exit_menu: not found')
with open(REPO_ROOT / 'poker.asm', 'wb') as f:
f.write(text.encode('cp1251'))
print('poker.asm updated in CP1251!')
text = text.replace(target_str, new_str, 1)
text = text.replace(target_proc, new_proc, 1)
with open('poker.asm', 'w', encoding='cp1251') as f:
f.write(text)
print("poker.asm patched successfully")

View file

@ -0,0 +1,6 @@
diff --git a/casino3d.inc b/casino3d.inc
--- a/casino3d.inc
+++ b/casino3d.inc
@@ -5498,1 +5498,2 @@
call FreeNativeClover3D
+ call FreePyramidAssets3D

View file

@ -0,0 +1,16 @@
.386
.model flat,stdcall
option casemap:none
include F:\MASM32\masm32\include\windows.inc
include F:\MASM32\masm32\include\kernel32.inc
InitPaths3D PROTO
LoadArrivalAssets3D PROTO
RunBJPartyTest3D PROTO
.code
RunBJPartyProbe PROC
call InitPaths3D
call LoadArrivalAssets3D
call RunBJPartyTest3D
invoke ExitProcess,eax
RunBJPartyProbe ENDP
END

View file

@ -0,0 +1,364 @@
; Native state-machine regression. Tests never write the real player's profile.
.data
bjPartySeenLo dd 0
bjPartySeenHi dd 0
bjPartySeenCount dd 0
bjPartyShot1 db "build\actions\blackjack-party-1.bmp",0
bjPartyShot2 db "build\actions\blackjack-party-2.bmp",0
bjPartyShot3 db "build\actions\blackjack-party-3.bmp",0
bjPartyShots dd OFFSET bjPartyShot1,OFFSET bjPartyShot2,OFFSET bjPartyShot3
bjPartyFlightShot db "build\actions\blackjack-party-flight.bmp",0
bjPartyResultShot db "build\actions\blackjack-party-result.bmp",0
bjPartyExitShot db "build\actions\blackjack-party-exit.bmp",0
.code
CheckBJPartyHand3D PROC uses ebx esi hand:DWORD,count:DWORD
mov esi,hand
cmp count,2
jb bj_party_hand_bad
cmp count,12
ja bj_party_hand_bad
xor ebx,ebx
bj_party_hand_card:
mov eax,[esi+ebx*4]
cmp eax,52
jae bj_party_hand_bad
cmp eax,32
jb bj_party_hand_low
sub eax,32
bts bjPartySeenHi,eax
jc bj_party_hand_bad
jmp bj_party_hand_next
bj_party_hand_low:
bts bjPartySeenLo,eax
jc bj_party_hand_bad
bj_party_hand_next:
inc bjPartySeenCount
inc ebx
cmp ebx,count
jb bj_party_hand_card
xor eax,eax
ret
bj_party_hand_bad:
mov eax,8104
ret
CheckBJPartyHand3D ENDP
RunBJPartyTest3D PROC uses ebx esi edi
local rounds:DWORD,ticks:DWORD,savedBank:DWORD,savedSeed:DWORD,savedCursor:DWORD,fixtureSeed:DWORD
mov engineMode,MODE_BLACKJACK
mov cbBet3D,100
mov cbBotCount3D,0
call InitNativeBlackjack3D
cmp cbPartyArt3D.pixels,0
je bj_party_fail_art
cmp cbPartyRoom3D.pixels,0
je bj_party_fail_art
; Real mouse selection; changing seats resets the displayed hand, not money.
mov bankroll,10000
mov mouseClick3D,1
mov mouseX3D,270
mov mouseY3D,40
call BJPartyInput3D
cmp cbBotCount3D,2
jne bj_party_fail_input
cmp mouseClick3D,0
jne bj_party_fail_input
cmp bankroll,10000
jne bj_party_fail_input
call StartNativeBlackjack3D
invoke SetBJPartyCount3D,3
cmp cbBotCount3D,2
jne bj_party_fail_input
cmp cbCursor3D,8
jne bj_party_fail_deck
cmp bankroll,9900
jne bj_party_fail_wallet
; A bot decision may see its own total and the dealer's upcard, nothing else.
mov cbBots3D.score,16
mov cbBots3D.count,2
mov cbDealer3D,4
invoke BJBotDecision3D,0
mov savedCursor,eax
mov cbDealer3D[4],12
mov cbPlayer3D,51
mov cbDeck3D[120],33
invoke BJBotDecision3D,0
cmp eax,savedCursor
jne bj_party_fail_hidden
mov cbBots3D[SIZEOF BJBot3D].score,18
mov cbBots3D[SIZEOF BJBot3D].count,2
invoke BJBotDecision3D,1
cmp eax,1
jne bj_party_fail_hidden
mov cbBots3D[SIZEOF BJBot3D*2].score,13
mov cbBots3D[SIZEOF BJBot3D*2].count,2
invoke BJBotDecision3D,2
test eax,eax
jnz bj_party_fail_hidden
; Natural beats a drawn 21, but a dealer natural beats a drawn guest 21.
mov cbBotCount3D,3
mov cbBotsSettled3D,0
mov cbDealerScore3D,21
mov cbDealerCount3D,2
mov cbBots3D.score,21
mov cbBots3D.count,2
mov cbBots3D[SIZEOF BJBot3D].score,21
mov cbBots3D[SIZEOF BJBot3D].count,3
mov cbBots3D[SIZEOF BJBot3D*2].score,22
call ResolveBJParty3D
cmp cbBots3D.outcome,2
jne bj_party_fail_wallet
cmp cbBots3D[SIZEOF BJBot3D].outcome,1
jne bj_party_fail_wallet
cmp cbBots3D[SIZEOF BJBot3D*2].outcome,1
jne bj_party_fail_wallet
mov cbDealerCount3D,3
mov cbBotsSettled3D,0
call ResolveBJParty3D
cmp cbBots3D.outcome,3
jne bj_party_fail_wallet
; Run 200 full rounds with each party size, alternating hits and doubles.
mov edi,1
bj_party_test_size:
mov cbBotCount3D,edi
mov rounds,200
bj_party_test_round:
mov eax,rounds
imul eax,2137
add eax,edi
mov rngSeed3D,eax
mov bankroll,10000
mov cbState3D,CB_IDLE
mov engineMode,MODE_BLACKJACK
call StartNativeBlackjack3D
mov eax,edi
add eax,2
shl eax,1
cmp cbCursor3D,eax
jne bj_party_fail_deck
mov ticks,0
mov deltaTime3D,50
bj_party_test_tick:
cmp cbState3D,CB_RESULT
je bj_party_test_result
cmp cbState3D,CB_PLAYER
jne bj_party_test_handle
mov pressedKeys3D[53h],1
cmp cbPlayerScore3D,16
jae @F
mov pressedKeys3D[53h],0
mov pressedKeys3D[48h],1
@@:
cmp cbPlayerCount3D,2
jne bj_party_test_handle
test rounds,1
jz bj_party_test_handle
mov pressedKeys3D[44h],1
bj_party_test_handle:
call HandleNativeBlackjack3D
call ClearKeys3D
cmp cbBotCount3D,edi
jne bj_party_fail_input
cmp cbCursor3D,52
ja bj_party_fail_deck
inc ticks
cmp ticks,2000
jae bj_party_fail_stuck
jmp bj_party_test_tick
bj_party_test_result:
mov eax,10000
sub eax,cbStake3D
cmp cbOutcome3D,1
je bj_party_expected
add eax,cbStake3D
cmp cbOutcome3D,2
je bj_party_expected
add eax,cbStake3D
cmp cbOutcome3D,3
jne bj_party_expected
mov edx,cbStake3D
shr edx,1
add eax,edx
bj_party_expected:
cmp bankroll,eax
jne bj_party_fail_wallet
mov savedBank,eax
invoke NativeBJSettle3D,cbOutcome3D
mov eax,savedBank
cmp bankroll,eax
jne bj_party_fail_wallet
mov bjPartySeenLo,0
mov bjPartySeenHi,0
mov bjPartySeenCount,0
invoke CheckBJPartyHand3D,OFFSET cbPlayer3D,cbPlayerCount3D
test eax,eax
jnz bj_party_test_done
invoke CheckBJPartyHand3D,OFFSET cbDealer3D,cbDealerCount3D
test eax,eax
jnz bj_party_test_done
xor ebx,ebx
bj_party_check_guests:
mov esi,ebx
imul esi,SIZEOF BJBot3D
add esi,OFFSET cbBots3D
cmp [esi].BJBot3D.state,BJ_BOT_DONE
jne bj_party_fail_stuck
cmp [esi].BJBot3D.outcome,3
ja bj_party_fail_wallet
invoke CheckBJPartyHand3D,esi,[esi].BJBot3D.count
test eax,eax
jnz bj_party_test_done
inc ebx
cmp ebx,edi
jb bj_party_check_guests
mov eax,bjPartySeenCount
cmp cbCursor3D,eax
jne bj_party_fail_deck
xor ebx,ebx
bj_party_check_prefix:
mov eax,cbDeck3D[ebx*4]
cmp eax,32
jb @F
sub eax,32
bt bjPartySeenHi,eax
jnc bj_party_fail_deck
jmp bj_party_prefix_next
@@:
bt bjPartySeenLo,eax
jnc bj_party_fail_deck
bj_party_prefix_next:
inc ebx
cmp ebx,cbCursor3D
jb bj_party_check_prefix
dec rounds
jnz bj_party_test_round
cmp edi,3
jne @F
call RenderNativeBlackjack3D
invoke WriteActionSnapshot3D,OFFSET bjPartyResultShot
@@:
; Actual framebuffer for each seat count, then repeat without state changes.
mov cbState3D,CB_IDLE
mov bankroll,10000
call StartNativeBlackjack3D
mov cbState3D,CB_PLAYER
mov eax,cbInitialFlights3D
imul eax,TABLE_FLIGHT_MS
mov cbTimer3D,eax
mov eax,rngSeed3D
mov savedSeed,eax
mov eax,cbCursor3D
mov savedCursor,eax
mov eax,bankroll
mov savedBank,eax
call RenderNativeBlackjack3D
mov eax,edi
dec eax
invoke WriteActionSnapshot3D,bjPartyShots[eax*4]
mov ebx,8
bj_party_render_repeat:
call RenderNativeBlackjack3D
dec ebx
jnz bj_party_render_repeat
mov eax,savedSeed
cmp rngSeed3D,eax
jne bj_party_fail_render
mov eax,savedCursor
cmp cbCursor3D,eax
jne bj_party_fail_render
mov eax,savedBank
cmp bankroll,eax
jne bj_party_fail_render
inc edi
cmp edi,4
jb bj_party_test_size
; Player bust does not prematurely end or hide the other guests' turns.
invoke FinishBJPlayer3D,1
cmp cbState3D,CB_BOTS
jne bj_party_fail_stuck
; Choose a genuine deal that needs a guest card for the animation capture.
mov fixtureSeed,1001
bj_party_find_flight:
mov eax,fixtureSeed
mov rngSeed3D,eax
mov cbState3D,CB_IDLE
mov bankroll,10000
call StartNativeBlackjack3D
cmp cbPlayerScore3D,21
je bj_party_next_fixture
cmp cbDealerScore3D,21
je bj_party_next_fixture
cmp cbBots3D.score,17
jb bj_party_flight_found
bj_party_next_fixture:
inc fixtureSeed
cmp fixtureSeed,1101
jae bj_party_fail_stuck
jmp bj_party_find_flight
bj_party_flight_found:
invoke FinishBJPlayer3D,-1
mov deltaTime3D,50
mov cbBotTimer3D,BJ_PARTY_MS
call UpdateBJParty3D
cmp cbBots3D.state,BJ_BOT_FLIGHT
jne bj_party_fail_stuck
mov cbBotTimer3D,180
call RenderNativeBlackjack3D
invoke WriteActionSnapshot3D,OFFSET bjPartyFlightShot
; Confirmation pauses bot timers; leaving counts once as player's loss.
call RequestCardExit3D
cmp cardExitActive3D,1
jne bj_party_fail_input
mov savedCursor,180
call HandleNativeBlackjack3D
cmp cbBotTimer3D,180
jne bj_party_fail_input
call RenderNativeBlackjack3D
invoke WriteActionSnapshot3D,OFFSET bjPartyExitShot
mov eax,bankroll
mov savedBank,eax
mov pressedKeys3D[59h],1
call HandleNativeBlackjack3D
cmp engineMode,MODE_WALK
jne bj_party_fail_input
mov eax,savedBank
cmp bankroll,eax
jne bj_party_fail_wallet
mov engineMode,MODE_BLACKJACK
; A result table can return to solo by keyboard, without another settlement.
mov pressedKeys3D[30h],1
call BJPartyInput3D
cmp cbBotCount3D,0
jne bj_party_fail_input
cmp cbState3D,CB_IDLE
jne bj_party_fail_input
cmp cbPlayerCount3D,0
jne bj_party_fail_input
mov eax,savedBank
cmp bankroll,eax
jne bj_party_fail_wallet
xor eax,eax
ret
bj_party_fail_art:
mov eax,8101
ret
bj_party_fail_input:
mov eax,8102
ret
bj_party_fail_wallet:
mov eax,8103
ret
bj_party_fail_deck:
mov eax,8104
ret
bj_party_fail_hidden:
mov eax,8105
ret
bj_party_fail_stuck:
mov eax,8106
ret
bj_party_fail_render:
mov eax,8107
bj_party_test_done:
ret
RunBJPartyTest3D ENDP

View file

@ -527,9 +527,14 @@ table_bj_expected:
mov eax, savedBank
cmp bankroll, eax
jne table_fail_25
call RunBJPartyTest3D
test eax,eax
jnz table_bj_party_failed
call FreeCardTableAssets3D
xor eax, eax
ret
table_bj_party_failed:
ret
table_fail_20:
mov eax, 20
ret
@ -573,3 +578,4 @@ table_fail_33:
mov eax, 33
ret
RunCardTablesTest3D ENDP
include tools\tests\blackjack_party_test.inc

View file

@ -93,8 +93,11 @@ include tools\tests\card_exit_test.inc
include tools\tests\city_lighting_test.inc
include tools\tests\durak_test.inc
include tools\tests\slots_test.inc
include tools\tests\clover_test.inc
include tools\tests\pyramid_test.inc
include tools\tests\coin_detail_test.inc
include tools\tests\coin_test.inc
include tools\tests\russian_party_test.inc
RunCasinoActionsTest3D PROC uses ebx esi edi
local savedSeed:DWORD,iterations:DWORD,chamberMask:DWORD,value1:DWORD,value2:DWORD,count:DWORD
@ -473,6 +476,11 @@ action_round_done:
jne action_fail_12
cmp bankroll, 0
jne action_fail_12
call RunRRPartyTest3D
test eax,eax
jz @F
ret
@@:
call RunUnavailableTest3D
test eax,eax
jz @F
@ -512,6 +520,11 @@ action_round_done:
test eax,eax
jz @F
ret
@@:
call RunCloverTest3D
test eax,eax
jz @F
ret
@@:
call RunCoinTest3D
test eax,eax

View file

@ -0,0 +1,61 @@
"""Inspect native party table captures and compare tested/shipping MASM."""
import argparse
import hashlib
from pathlib import Path
from PIL import Image, ImageChops, ImageDraw, ImageStat
from check_card_exit_build import Coff
ROOT = Path(__file__).resolve().parents[2]
def frames():
names = ["1", "2", "3", "flight", "result", "exit"]
sheet = Image.new("RGB", (1280, 3 * 504), (20, 25, 29))
draw = ImageDraw.Draw(sheet)
signatures = []
captured = {}
for i, name in enumerate(names):
path = ROOT / "build/actions" / f"blackjack-party-{name}.bmp"
image = Image.open(path).convert("RGB")
captured[name] = image
assert image.size == (640, 480)
assert len(image.getcolors(640 * 480)) > 1000, name
assert sum(ImageStat.Stat(image.crop((0, 444, 640, 476))).stddev) > 10, name
signatures.append(hashlib.sha256(image.tobytes()).digest())
image.save(path.with_suffix(".png"))
x, y = (i % 2) * 640, (i // 2) * 504
sheet.paste(image, (x, y + 24))
draw.text((x + 8, y + 6), f"Native blackjack: {name}", fill="white")
assert len(set(signatures)) == len(names)
for number, box in (("2", (248, 132, 392, 240)), ("3", (472, 132, 616, 240))):
difference = ImageChops.difference(captured[number].crop(box), captured["1"].crop(box))
assert sum(ImageStat.Stat(difference).mean) > 15, f"Guest {number} did not render"
sheet.save(ROOT / "build/actions/blackjack-party-contact.png")
print("PASS: six distinct native party-table captures, visible portraits, cards, controls and exit modal")
def parity():
tested = Coff(ROOT / "build/actions/actions-test.obj")
shipping = Coff(ROOT / "build/graphics/poker.obj")
for name, size in (("InitBJParty3D", 0), ("FreeBJParty3D", 0), ("DealBJParty3D", 0),
("FinishBJPlayer3D", 4), ("BeginBJDealer3D", 0),
("BJBotDecision3D", 4), ("UpdateBJParty3D", 0),
("ResolveBJParty3D", 0), ("BJPartyInput3D", 0),
("SetBJPartyCount3D", 4), ("RenderBJParty3D", 0),
("DrawBJPartySelector3D", 0), ("DrawBJMiniCard3D", 16),
("DrawBJMiniFlight3D", 16), ("NativeBJSettle3D", 4),
("StartNativeBlackjack3D", 0), ("HandleNativeBlackjack3D", 0),
("RenderNativeBlackjack3D", 0)):
symbol = f"_{name}@{size}"
assert tested.signature(symbol) == shipping.signature(symbol), name
print("PASS: shipping party dealing, AI, settlement, animation and input match native-tested MASM")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--parity", action="store_true")
args = parser.parse_args()
frames()
if args.parity:
parity()

View file

@ -0,0 +1,82 @@
"""Verify real MASM Clover framebuffer captures and shipping-code parity."""
import argparse
import hashlib
import struct
from pathlib import Path
from PIL import Image, ImageChops
from check_card_exit_build import Coff
from check_coin_detail import font, text_matches
ROOT = Path(__file__).resolve().parents[2]
OUT = ROOT / "build/actions"
NAMES = ["clover-ready", "clover-spin", "clover-stopped", "clover-win", "clover-next", "clover-paytable"]
FUNCTIONS = {"InitNativeClover3D": 0, "FreeNativeClover3D": 0, "StartNativeClover3D": 0,
"CloverEvaluate3D": 0, "SettleNativeClover3D": 0, "CloverEscape3D": 0,
"CloverReelPosition3D": 4, "HandleNativeClover3D": 0,
"DrawCloverSymbol3D": 12, "DrawCloverReel3D": 4, "RenderNativeClover3D": 0,
"CloverComboText3D": 4, "DrawSlotTotals3D": 12, "EvaluateSlotCombos": 0,
"SlotApplyWin": 12, "CardTableEscape3D": 0, "TableBusy3D": 0}
def main(parity=False):
packs = ROOT / "assets/animations/clover"
raw = (packs / "cabinet-v1.bin").read_bytes()
assert struct.unpack_from("<4s4I", raw) == (b"ANM1", 640, 480, 1, 640 * 480 * 4)
assert set(raw[23::4]) == {255}
cabinet = Image.frombytes("RGBA", (640, 480), raw[20:], "raw", "BGRA").convert("RGB")
raw = (packs / "symbols-v1.bin").read_bytes()
stride = 56 * 56 * 4
assert struct.unpack_from("<4s4I", raw) == (b"ANM1", 56, 56, 7, stride * 7)
assert len(raw) == 20 + stride * 7
assert set(raw[23::4]) == {0, 255}
assert len({hashlib.sha256(raw[20 + i * stride:20 + (i + 1) * stride]).digest()
for i in range(7)}) == 7
frames = {}
for name in NAMES:
frame = Image.open(OUT / (name + ".bmp")).convert("RGB")
assert frame.size == (640, 480)
assert len(frame.getcolors(640 * 480)) > 2000, name
frames[name] = frame
frame.save(OUT / (name + ".png"))
if name == "clover-paytable":
continue
difference = ImageChops.difference(frame, cabinet)
for y in range(80, 318):
for x in range(110, 530):
in_reel = 126 <= y < 294 and any(left <= x < left + 56 for left in (144, 216, 288, 360, 432))
if not in_reel:
assert difference.getpixel((x, y)) == (0, 0, 0), (name, x, y)
assert len({hashlib.sha256(frame.tobytes()).digest() for frame in frames.values()}) == len(NAMES)
# Every complete cell contains a symbol, not a truncated or empty partial row.
for col in (144, 216, 288, 360, 432):
for top in (126, 182, 238):
cell = frames["clover-ready"].crop((col + 4, top + 4, col + 52, top + 52))
assert len(cell.getcolors(48 * 48)) > 20, (col, top)
selected = ImageChops.difference(frames["clover-win"], frames["clover-next"])
assert selected.crop((144, 126, 488, 294)).getbbox(), "Changing the paid combo must change the highlighted cells"
contact = Image.new("RGB", (1920, 960))
for i, name in enumerate(NAMES):
contact.paste(frames[name], ((i % 3) * 640, (i // 3) * 480))
contact.save(OUT / "clover-contact.png")
glyphs = font()
text_matches(frames["clover-ready"], "КЛЕВЕР 5x3", 16, 12, 2, (232, 215, 173), glyphs)
text_matches(frames["clover-win"], "27300", 384, 402, 2, (244, 243, 232), glyphs)
text_matches(frames["clover-win"], "+27200", 384, 422, 2, (168, 221, 185), glyphs)
text_matches(frames["clover-win"], "ДЖЕКПОТ", 144, 350, 2, (244, 243, 232), glyphs)
text_matches(frames["clover-next"], "ГЛАЗ", 144, 350, 2, (244, 243, 232), glyphs)
source = (ROOT / "src_graphics/native_clover.inc").read_text(encoding="ascii")
assert "call EvaluateSlotCombos" in source
assert "call GameSlot" not in source and "call PaySlotMultiplier" not in source
if parity:
tested = Coff(ROOT / "build/actions/actions-test.obj")
shipped = Coff(ROOT / "build/graphics/poker.obj")
for name, size in FUNCTIONS.items():
assert tested.signature(f"_{name}@{size}") == shipped.signature(f"_{name}@{size}"), name
print("PASS: 5x3 complete cells, 7 real sprites, clipping, six distinct native frames, paid-mask selection" +
(", tested/shipping ASM parity" if parity else ""))
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--parity", action="store_true")
main(parser.parse_args().parity)

View file

@ -4,6 +4,7 @@ import hashlib
import json
import re
import struct
from functools import lru_cache
from pathlib import Path
from PIL import Image, ImageDraw
@ -14,12 +15,93 @@ ORDER = [0, 32, 15, 19, 4, 21, 2, 25, 17, 34, 6, 27, 13, 36, 11, 30,
8, 23, 10, 5, 24, 16, 33, 1, 20, 14, 31, 9, 22, 18, 29, 7, 28,
12, 35, 3, 26]
RED = {1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21, 23, 25, 27, 30, 32, 34, 36}
DIGITS = ((7, 5, 5, 5, 7), (2, 6, 2, 2, 7), (7, 1, 7, 4, 7),
(7, 1, 7, 1, 7), (5, 5, 7, 1, 1), (7, 4, 7, 1, 7),
(7, 4, 7, 5, 7), (7, 1, 2, 2, 2), (7, 5, 7, 5, 7),
(7, 5, 7, 1, 7))
LABEL_INK = (255, 248, 222)
@lru_cache(maxsize=2)
def lookup_table(name):
lines = (ROOT / "casino3d_data.inc").read_text(encoding="cp1251").splitlines()
start = next(i for i, line in enumerate(lines) if line.startswith(name + " dd "))
values = []
for i, line in enumerate(lines[start:]):
if i and not line.strip().startswith("dd "):
break
for value in re.findall(r"([0-9A-F]+)h", line):
number = int(value, 16)
values.append(number if number < 2**31 else number - 2**32)
assert len(values) == 512, name
return values
def divide_signed(number, denominator):
return number // denominator if number >= 0 else -((-number) // denominator)
def label_positions(center_x, center_y, radius_x, radius_y, angle):
cosine, sine = lookup_table("tableCos"), lookup_table("tableSin")
return [(center_x + divide_signed((cosine[(i * 512 // 37 + angle) % 512] * 195 >> 16)
* radius_x, 224),
center_y + divide_signed((sine[(i * 512 // 37 + angle) % 512] * 195 >> 16)
* radius_y, 224)) for i in range(37)]
def value_pixels(center_x, y, number, scale):
text = str(number)
x = center_x - ((len(text) * 4 - 1) * scale // 2)
return {(x + (digit_index * 4 + column) * scale + dx, y + row * scale + dy)
for digit_index, digit in enumerate(text)
for row, bits in enumerate(DIGITS[int(digit)])
for column in range(3) if bits & (4 >> column)
for dx in range(scale) for dy in range(scale)}
def readable_labels():
# Independent pixel masks check the actual assembly framebuffer, not a mock renderer.
for angle in range(512):
for geometry, scale in (((206, 170, 142, 46, angle), 1),
((320, 278, 205, 88, angle), 2)):
occupied = set()
for number, (x, y) in zip(ORDER, label_positions(*geometry)):
pixels = value_pixels(x, y - 5 * scale // 2, number, scale)
assert not occupied.intersection(pixels), (angle, scale, number, "overlap")
occupied.update(pixels)
for name, geometry, scale in (
("small", (206, 170, 142, 46, 0), 1),
("rotated", (320, 278, 205, 88, 113), 2)):
frame = Image.open(ROOT / "build/actions" / f"wheel-label-{name}.bmp").convert("RGB")
expected = set()
for number, (x, y) in zip(ORDER, label_positions(*geometry)):
glyph = value_pixels(x, y - 5 * scale // 2, number, scale)
assert not expected.intersection(glyph), (name, number, "overlapping digits")
expected.update(glyph)
actual = {(x, y) for y in range(480) for x in range(640)
if frame.getpixel((x, y)) == LABEL_INK}
assert actual == expected, (name, "missing or distorted digit pixels",
len(expected - actual), len(actual - expected))
positions = label_positions(320, 278, 205, 88, 0)
for result in range(37):
frame = Image.open(ROOT / "build/actions" / f"wheel-label-{result:02}.bmp").convert("RGB")
for number, (x, y) in zip(ORDER, positions):
pixels = value_pixels(x, y - 5, number, 2)
missing = [point for point in pixels if frame.getpixel(point) != LABEL_INK]
assert not missing, (result, number, "ball/highlight obscures label", missing[:5])
ink = (244, 231, 200) if result not in RED else (255, 180, 173)
assert all(frame.getpixel(point) == ink
for point in value_pixels(320, 268, result, 6)), (result, "central result")
print("PASS: all 37 native results retain every upright pocket digit and large central result;")
print(" exact 1x/2x native glyphs and no overlap at any of 512 rotation angles")
def assets():
manifest = json.loads((ROOT / "assets/animations/wheel/manifest-v1.json").read_text())
assert manifest == {"order": ORDER, "zero_pockets": 1}
source = (ROOT / "src_graphics/native_wheel_data.inc").read_text(encoding="ascii")
assert 'wheelRotorPath3D db "assets\\animations\\wheel\\rotor-v2.bin",0' in source
order = re.search(r"wheelOrder3D dd ([0-9,]+)", source).group(1)
assert [int(value) for value in order.split(",")] == ORDER
low, high = re.search(r"wheelRedMask3D dd ([0-9A-F]+)h,([0-9A-F]+)h", source).groups()
@ -33,6 +115,9 @@ def assets():
assert len(data) == 20 + size[0] * size[1] * 4, name
alpha = set(data[23::4])
assert alpha == ({0, 255} if name == "rotor" else {255}), name
if name == "rotor":
assert not any(data[i + 2] > 245 and data[i + 1] > 225 and data[i] > 185
for i in range(20, len(data), 4)), "Baked-in numbers returned"
print("PASS: three valid packs; exact European order, one zero and correct red/black colors")
@ -65,7 +150,8 @@ def parity():
("WheelEscape3D", 0), ("WheelTravel3D", 12),
("DrawNativeWheelDisc3D", 20), ("RenderWheelScene3D", 0),
("LoadNativeWheelArt3D", 0), ("FreeNativeWheelArt3D", 0),
("DrawWheelDigit3D", 20), ("DrawWheelValue3D", 20), ("DrawWheelLabels3D", 20),
("DrawWheelBox3D", 20), ("DrawWheelDigit3D", 20),
("DrawWheelValue3D", 20), ("DrawWheelLabels3D", 20),
("AnimateCityPedestrians3D", 0), ("HandleArrival3D", 0))
for name, arguments in functions:
symbol = f"_{name}@{arguments}"
@ -81,5 +167,6 @@ if __name__ == "__main__":
assets()
if not options.assets_only:
captures()
readable_labels()
if options.parity:
parity()

View file

@ -0,0 +1,55 @@
"""Inspect actual MASM dialog frames and compare tested/shipping NPC procedures."""
import argparse
import hashlib
from pathlib import Path
from PIL import Image, ImageDraw
from check_card_exit_build import Coff
ROOT = Path(__file__).resolve().parents[2]
def captures():
sheet = Image.new("RGB", (640, 3 * 504), (20, 25, 29))
draw = ImageDraw.Draw(sheet)
hashes = []
for index, name in enumerate(("open", "ready", "done")):
path = ROOT / "build/actions" / f"npc-dialog-{name}.bmp"
frame = Image.open(path).convert("RGB")
assert frame.size == (640, 480)
assert len(frame.getcolors(640 * 480)) > 1000, name
assert frame.getpixel((60, 96)) == (186, 153, 85), "dialog border"
hashes.append(hashlib.sha256(frame.crop((60, 96, 580, 398)).tobytes()).digest())
frame.save(path.with_suffix(".png"))
sheet.paste(frame, (0, index * 504 + 24))
draw.text((8, index * 504 + 6), name, fill="white")
assert len(set(hashes)) == 3, "Contract status/reward must visibly change"
sheet.save(ROOT / "build/actions/npc-dialog-contact.png")
print("PASS: three distinct native NPC dialog frames, bounded modal and changing contract status")
def parity():
tested = Coff(ROOT / "build/actions/actions-test.obj")
shipped = Coff(ROOT / "build/graphics/poker.obj")
procedures = (("InitNpcLife3D", 0), ("UpdateNpcLife3D", 0), ("RenderNpcLife3D", 0),
("NpcPlanPath3D", 8), ("NpcTryCell3D", 8), ("NpcMove3D", 8),
("NpcCanStep3D", 12), ("CheckNpcInteractions3D", 0),
("OpenNpcDialog3D", 0), ("HandleNpcDialog3D", 0),
("RenderNpcDialog3D", 0), ("CloseNpcDialog3D", 0),
("NpcContractType3D", 4), ("NpcAcceptContract3D", 4),
("NpcClaimContract3D", 4), ("NpcRecordRound3D", 8),
("NpcSaveJournal3D", 0), ("NpcNameText3D", 4),
("AnimateCityPedestrians3D", 0), ("FinalizeSkullsGuess", 0))
for name, size in procedures:
symbol = f"_{name}@{size}"
assert tested.signature(symbol) == shipped.signature(symbol), name
print("PASS: shipping NPC navigation, poses, dialogs, rewards and street movement match tested MASM")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--parity", action="store_true")
args = parser.parse_args()
captures()
if args.parity:
parity()

View file

@ -0,0 +1,56 @@
"""Check actual native Russian-roulette party frames and shipping code parity."""
import argparse
import hashlib
from pathlib import Path
from PIL import Image, ImageDraw, ImageStat
from check_card_exit_build import Coff
ROOT = Path(__file__).resolve().parents[2]
def frames():
names = ["ready", "turn", "out", "win", "exit"]
sheet = Image.new("RGB", (1280, 3 * 504), (20, 25, 29))
draw = ImageDraw.Draw(sheet)
signatures = []
for i, name in enumerate(names):
path = ROOT / "build/actions" / f"rr-party-{name}.bmp"
image = Image.open(path).convert("RGB")
assert image.size == (640, 480)
assert len(image.getcolors(640 * 480)) > 1000, name
assert sum(ImageStat.Stat(image.crop((0, 400, 640, 480))).stddev) > 5, name
signatures.append(hashlib.sha256(image.tobytes()).digest())
image.save(path.with_suffix(".png"))
x, y = (i % 2) * 640, (i // 2) * 504
sheet.paste(image, (x, y + 24))
draw.text((x + 8, y + 6), f"Native Russian party: {name}", fill="white")
assert len(set(signatures)) == len(names)
sheet.save(ROOT / "build/actions/rr-party-contact.png")
print("PASS: five distinct native party frames: setup, active seat, elimination, win and exit dialog")
def parity():
tested = Coff(ROOT / "build/actions/actions-test.obj")
shipping = Coff(ROOT / "build/graphics/poker.obj")
procedures = (("RRPartyInit3D", 0), ("RRPartyReset3D", 0), ("RRPartySetCount3D", 4),
("RRPartyInput3D", 0), ("RRLoadCylinder3D", 0), ("RRPartyHit3D", 0),
("RRPartyNext3D", 0), ("RRPartyWin3D", 0), ("RRPartyLose3D", 0),
("AbortRussianRound3D", 0), ("RenderRRPartyCharacters3D", 0),
("RenderRRPartyGun3D", 0), ("RenderRRPartyControls3D", 0),
("RenderRussianExit3D", 0), ("StartRussianRound3D", 0),
("AdvanceRussianScene3D", 0), ("HandleRussianInput3D", 0),
("RenderRussianGame3D", 0), ("RenderRussianTable3D", 0))
for name, size in procedures:
symbol = f"_{name}@{size}"
assert tested.signature(symbol) == shipping.signature(symbol), name
print("PASS: shipping party turns, payouts, reloading, animation and exit match native-tested MASM")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--parity", action="store_true")
args = parser.parse_args()
frames()
if args.parity:
parity()

View file

@ -13,7 +13,7 @@ def main():
source = (ROOT / 'casino3d.inc').read_text(encoding='cp1251')
interaction = source.split('Trigger3DInteraction PROC', 1)[1].split('Trigger3DInteraction ENDP', 1)[0]
assert 'LaunchClassicGame' not in interaction
assert interaction.count('call BeginUnavailable3D') == 7
assert interaction.count('call BeginUnavailable3D') == 6
assert 'call InitNativeDurak3D' in interaction
assert 'call InitNativeSlots3D' in interaction
panel = Image.open(OUTPUT / 'unavailable.bmp').convert('RGB')

View file

@ -14,7 +14,7 @@ def main():
"RenderNativeSlots3D": 0, "SlotMultiplier3D": 12,
"SettleNativeSlots3D": 0, "SlotEscape3D": 0, "SlotExitMouse3D": 0,
"SlotReelPosition3D": 4, "DrawSlotReel3D": 4,
"DrawSlotSymbol3D": 12, "Trigger3DInteraction": 0,
"DrawSlotSymbol3D": 12, "DrawSlotTotals3D": 12, "Trigger3DInteraction": 0,
"StartCasino3D": 0, "CardTableEscape3D": 0}
for name, size in functions.items():
symbol = f"_{name}@{size}"

View file

@ -4,6 +4,7 @@ import struct
from pathlib import Path
from PIL import Image, ImageChops
from check_coin_detail import font, text_matches
ROOT = Path(__file__).resolve().parents[2]
OUTPUT = ROOT / "build/actions"
@ -58,7 +59,18 @@ def main():
for index, name in enumerate(NAMES):
contact.paste(frames[name], ((index % 4) * 640, (index // 4) * 480))
contact.save(OUTPUT / "slots-contact.png")
glyphs = font()
for name, gross, net, status, ink in (
("slots-refund", "100", "0", "ВОЗВРАТ", (208, 212, 208)),
("slots-net-win", "200", "+100", "ВЫПЛАТА", (168, 221, 185)),
("slots-loss", "0", "-100", "ПРОИГРЫШ", (238, 153, 136))):
frame = Image.open(OUTPUT / (name + ".bmp")).convert("RGB")
text_matches(frame, gross, 384, 402, 2, (244, 243, 232), glyphs)
text_matches(frame, net, 384, 422, 2, ink, glyphs)
text_matches(frame, status, 208, 298, 2, (232, 215, 173), glyphs)
frame.save(OUTPUT / (name + ".png"))
print("PASS: 8 distinct native slot frames; pixel-accurate reel clipping; sequential stops; opaque cabinet and 8 real symbol sprites")
print("PASS: exact native gross/net/refund/loss Russian glyphs; refund is not a net win")
if __name__ == "__main__":

View file

@ -0,0 +1,558 @@
; Mechanically generated offline reference vectors.
.data
clpFixtureCount3D equ 553
clpFixtures3D label DWORD
dd 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,117,6,32767,1
dd 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,78,6,32767,1
dd 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,78,6,32767,1
dd 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,117,6,32767,1
dd 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,195,6,32767,1
dd 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,195,6,32767,1
dd 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,273,6,32767,1
dd 6,3,6,2,6,2,5,6,4,0,6,0,3,6,6,56,1,17553,0
dd 6,6,6,6,6,2,5,1,4,0,4,0,3,6,2,49,1,31,0
dd 0,3,6,2,5,2,5,1,4,0,6,6,6,6,6,49,1,31744,0
dd 0,3,6,2,5,2,6,1,6,0,6,0,3,6,6,28,1,17732,0
dd 6,3,6,2,6,2,6,1,6,0,4,0,6,6,2,28,1,4433,0
dd 6,6,6,6,6,2,5,1,4,0,4,0,3,6,2,49,1,31,0
dd 0,3,6,2,5,6,6,6,6,6,4,0,3,6,2,21,1,992,0
dd 0,3,6,2,5,2,5,1,4,0,6,6,6,6,6,49,1,31744,0
dd 6,6,6,6,5,2,5,1,4,0,4,0,3,6,2,14,1,15,0
dd 0,6,6,6,6,2,5,1,4,0,4,0,3,6,2,14,1,30,0
dd 0,3,6,2,5,6,6,6,6,0,4,0,3,6,2,14,1,480,0
dd 0,3,6,2,5,2,6,6,6,6,4,0,3,6,2,14,1,960,0
dd 0,3,6,2,5,2,5,1,4,0,6,6,6,6,2,14,1,15360,0
dd 0,3,6,2,5,2,5,1,4,0,4,6,6,6,6,14,1,30720,0
dd 6,3,6,2,5,6,5,1,4,0,6,0,3,6,2,7,1,1057,0
dd 0,6,6,2,5,2,6,1,4,0,4,6,3,6,2,7,1,2114,0
dd 0,3,6,2,5,2,5,6,4,0,4,0,6,6,2,7,1,4228,0
dd 0,3,6,6,5,2,5,1,6,0,4,0,3,6,2,7,1,8456,0
dd 0,3,6,2,6,2,5,1,4,6,4,0,3,6,6,7,1,16912,0
dd 6,3,6,2,5,2,6,1,4,0,4,0,6,6,2,7,1,4161,0
dd 0,6,6,2,5,2,5,6,4,0,4,0,3,6,2,7,1,8322,0
dd 0,3,6,2,5,2,5,1,6,0,4,0,3,6,6,7,1,16644,0
dd 0,3,6,2,5,2,6,1,4,0,6,0,3,6,2,7,1,1092,0
dd 0,3,6,6,5,2,5,6,4,0,4,6,3,6,2,7,1,2184,0
dd 0,3,6,2,6,2,5,1,6,0,4,0,6,6,2,7,1,4368,0
dd 6,6,6,2,5,2,5,1,4,0,4,0,3,6,2,7,1,7,0
dd 0,6,6,6,5,2,5,1,4,0,4,0,3,6,2,7,1,14,0
dd 0,3,6,6,6,2,5,1,4,0,4,0,3,6,2,7,1,28,0
dd 0,3,6,2,5,6,6,6,4,0,4,0,3,6,2,7,1,224,0
dd 0,3,6,2,5,2,6,6,6,0,4,0,3,6,2,7,1,448,0
dd 0,3,6,2,5,2,5,6,6,6,4,0,3,6,2,7,1,896,0
dd 0,3,6,2,5,2,5,1,4,0,6,6,6,6,2,14,1,15360,0
dd 0,3,6,2,5,2,5,1,4,0,4,6,6,6,2,7,1,14336,0
dd 0,3,6,2,5,2,5,1,4,0,4,0,6,6,6,7,1,28672,0
dd 6,3,3,0,1,2,0,3,5,5,6,6,0,0,2,0,0,0,0
dd 2,6,3,6,2,6,2,4,4,5,3,2,4,6,2,0,0,0,0
dd 1,3,6,2,6,6,5,0,6,1,4,6,0,4,4,0,0,0,0
dd 3,3,0,0,1,0,3,3,5,2,3,2,0,6,1,0,0,0,0
dd 3,6,0,3,0,4,1,5,6,0,6,2,2,0,2,0,0,0,0
dd 3,2,6,0,5,0,5,5,5,1,2,6,4,4,6,5,1,448,0
dd 6,0,0,3,4,1,0,6,3,0,3,0,5,4,0,3,1,2114,0
dd 5,5,4,2,1,4,4,6,3,1,6,6,1,1,1,4,2,29200,0
dd 6,5,0,5,5,0,6,0,3,6,5,5,3,0,6,0,0,0,0
dd 4,0,4,6,5,3,1,3,4,3,5,1,3,3,6,0,0,0,0
dd 3,6,6,2,2,3,4,1,6,0,6,3,2,2,4,0,0,0,0
dd 5,0,1,2,6,6,2,2,6,2,5,6,6,1,6,7,1,4368,0
dd 4,3,1,6,3,6,6,5,5,4,1,3,3,6,2,0,0,0,0
dd 1,3,1,2,6,3,6,4,6,6,2,2,2,5,6,9,2,24080,0
dd 0,3,6,2,2,5,6,6,4,0,2,0,5,4,1,0,0,0,0
dd 4,5,3,0,1,5,4,0,2,6,6,3,4,2,0,5,1,4161,0
dd 0,1,3,1,4,3,3,3,2,6,0,0,4,6,1,3,1,224,0
dd 3,5,6,1,4,0,2,0,2,5,6,1,3,6,4,0,0,0,0
dd 6,1,6,6,5,6,4,0,5,4,4,3,4,5,3,0,0,0,0
dd 2,3,2,2,4,2,0,2,6,1,1,0,3,4,3,0,0,0,0
dd 4,3,6,6,2,3,2,6,5,3,0,2,6,2,3,7,1,4228,0
dd 1,3,0,4,5,3,2,1,5,1,2,1,4,4,4,5,1,28672,0
dd 1,1,3,0,1,6,5,4,1,3,0,3,0,1,2,0,0,0,0
dd 4,5,1,2,0,2,4,0,0,5,6,0,2,0,0,0,0,0,0
dd 6,0,2,1,5,4,0,2,0,0,1,5,5,4,0,0,0,0,0
dd 1,6,6,6,5,6,6,2,2,0,0,2,5,3,2,7,1,14,0
dd 6,6,6,1,6,0,5,1,3,5,3,4,5,4,5,7,1,7,0
dd 2,6,2,0,5,1,6,6,0,4,5,0,1,1,4,0,0,0,0
dd 1,2,5,2,0,2,3,1,2,5,6,6,1,2,4,2,1,8456,0
dd 3,0,2,4,0,4,3,0,1,3,1,3,6,2,1,0,0,0,0
dd 5,2,1,1,3,5,0,0,6,0,4,6,4,5,1,0,0,0,0
dd 5,3,5,4,6,3,4,2,6,4,4,1,6,4,4,7,1,4368,0
dd 4,4,3,6,5,3,5,0,5,0,2,0,4,6,3,0,0,0,0
dd 3,1,2,4,2,2,4,0,5,2,4,6,0,1,1,0,0,0,0
dd 1,4,4,4,3,4,5,2,6,4,3,6,2,5,2,5,1,14,0
dd 0,0,6,3,3,3,6,4,2,3,0,6,6,5,1,0,0,0,0
dd 4,2,4,0,4,4,6,0,0,1,5,4,5,2,6,0,0,0,0
dd 0,0,4,6,4,1,3,0,0,5,2,2,1,6,1,0,0,0,0
dd 4,6,0,2,1,6,3,3,4,1,4,4,4,2,0,5,1,7168,0
dd 5,1,6,0,6,2,6,5,6,1,4,3,1,6,0,0,0,0,0
dd 2,2,6,4,2,5,2,3,6,0,2,6,3,6,0,0,0,0,0
dd 3,1,3,5,1,5,3,5,4,4,0,5,5,5,3,10,2,14472,0
dd 0,0,6,6,4,3,2,0,6,5,2,4,3,2,4,0,0,0,0
dd 4,1,4,3,0,1,4,4,0,6,0,4,6,1,1,0,0,0,0
dd 5,6,0,5,2,3,2,1,3,2,4,4,5,2,4,0,0,0,0
dd 2,4,2,6,6,1,4,2,0,5,6,5,2,4,0,2,1,4228,0
dd 0,3,6,2,3,2,1,6,1,1,2,4,5,2,1,0,0,0,0
dd 3,3,5,3,6,0,6,5,4,2,3,2,5,4,5,5,1,4228,0
dd 6,6,5,1,4,4,5,6,2,2,2,4,6,4,6,0,0,0,0
dd 6,5,5,4,6,2,4,5,4,0,2,2,1,3,5,0,0,0,0
dd 3,2,2,4,3,2,0,1,1,5,2,1,4,6,2,0,0,0,0
dd 6,0,1,4,3,3,4,2,0,2,1,3,4,5,0,0,0,0,0
dd 0,2,1,4,3,4,1,5,6,1,3,2,4,3,1,0,0,0,0
dd 6,1,2,5,0,0,0,0,3,0,3,6,3,1,4,3,1,224,0
dd 5,0,0,5,0,1,3,6,5,3,3,3,5,6,3,0,0,0,0
dd 1,0,1,1,4,3,3,4,6,6,0,5,1,3,6,0,0,0,0
dd 0,1,0,4,2,3,5,5,1,2,4,3,4,2,6,0,0,0,0
dd 4,4,1,4,4,6,1,3,4,1,5,1,2,2,2,2,1,28672,0
dd 6,2,2,3,4,0,1,2,2,5,3,2,5,6,0,0,0,0,0
dd 5,3,0,4,1,5,1,0,0,1,4,1,6,2,4,0,0,0,0
dd 6,1,6,0,0,0,6,5,5,6,4,1,1,0,2,0,0,0,0
dd 2,6,0,3,1,3,6,0,2,2,5,1,5,1,0,0,0,0,0
dd 2,4,3,0,5,0,5,5,6,5,4,4,3,3,5,5,1,16912,0
dd 5,3,1,3,5,6,0,1,2,2,4,2,4,4,3,0,0,0,0
dd 4,1,1,4,4,4,0,0,6,3,5,4,1,4,0,0,0,0,0
dd 2,0,5,3,3,5,1,1,3,5,1,3,6,5,2,0,0,0,0
dd 3,5,5,6,4,6,6,1,1,0,5,2,2,2,1,2,1,14336,0
dd 1,1,5,0,5,0,0,3,6,1,5,2,5,6,1,0,0,0,0
dd 2,6,2,0,3,1,4,4,0,2,5,6,3,4,5,0,0,0,0
dd 0,4,6,6,4,4,2,1,4,6,5,5,5,1,2,5,1,7168,0
dd 2,5,4,2,1,2,2,6,2,4,0,0,1,4,5,0,0,0,0
dd 3,5,2,0,3,2,2,3,2,3,0,0,0,4,2,5,2,23812,0
dd 2,4,3,3,0,0,6,2,1,5,1,6,6,4,1,0,0,0,0
dd 2,0,6,6,6,4,4,3,2,0,0,4,1,4,2,7,1,28,0
dd 1,3,5,2,6,2,3,4,4,5,1,3,2,3,0,3,1,2114,0
dd 4,3,1,2,5,0,0,5,6,5,2,3,1,3,1,0,0,0,0
dd 0,4,2,5,1,0,2,6,0,2,1,6,2,4,5,0,0,0,0
dd 2,0,5,6,3,3,3,6,2,1,1,4,0,2,3,0,0,0,0
dd 3,5,5,3,5,2,0,3,2,0,2,3,2,2,2,5,2,30856,0
dd 3,0,5,4,5,3,2,1,2,1,4,3,1,4,4,0,0,0,0
dd 2,3,5,6,1,6,6,1,4,3,1,0,4,3,6,0,0,0,0
dd 1,6,0,5,3,5,0,2,3,3,3,1,0,2,5,0,0,0,0
dd 2,0,3,4,4,0,1,6,6,1,3,5,2,1,4,0,0,0,0
dd 4,4,1,3,4,2,0,6,0,1,6,3,4,1,3,0,0,0,0
dd 6,4,5,3,0,1,0,1,6,3,4,4,0,0,2,0,0,0,0
dd 1,1,3,6,0,6,4,0,4,3,5,0,0,2,5,0,0,0,0
dd 1,5,4,0,4,5,3,6,4,2,6,4,5,5,1,0,0,0,0
dd 0,4,0,1,0,5,5,3,0,2,3,4,2,4,5,0,0,0,0
dd 0,5,6,2,5,1,3,3,1,1,1,6,4,2,4,0,0,0,0
dd 6,0,4,6,2,4,4,3,5,4,2,0,5,4,2,0,0,0,0
dd 5,3,1,1,3,5,4,4,2,2,6,0,1,3,6,0,0,0,0
dd 6,4,3,3,0,4,4,2,1,6,1,4,0,4,5,5,1,2114,0
dd 4,4,3,0,4,0,2,1,2,5,5,5,5,2,4,5,1,7168,0
dd 2,3,6,6,2,1,1,6,0,4,4,5,2,1,4,0,0,0,0
dd 2,4,3,6,0,5,4,3,1,5,6,2,5,5,0,0,0,0,0
dd 0,0,6,0,0,2,0,0,5,2,5,1,4,5,3,0,0,0,0
dd 4,4,6,0,5,0,4,4,1,2,6,5,4,3,2,5,1,4161,0
dd 0,5,0,4,1,5,0,3,2,3,2,3,1,0,1,0,0,0,0
dd 6,3,3,2,3,4,1,4,1,4,1,6,0,5,0,0,0,0,0
dd 5,0,2,2,4,2,1,1,3,3,6,6,6,4,3,7,1,7168,0
dd 3,0,2,6,0,0,6,3,5,2,3,3,3,3,0,6,1,15360,0
dd 5,0,0,6,3,5,0,5,4,6,3,6,0,1,6,0,0,0,0
dd 2,5,2,5,1,1,4,4,3,4,1,1,2,2,1,0,0,0,0
dd 5,5,1,2,4,4,3,0,4,1,4,6,3,3,0,0,0,0,0
dd 3,2,0,2,0,0,6,6,1,5,4,2,6,5,4,0,0,0,0
dd 6,3,0,6,6,1,5,3,0,1,3,2,6,5,1,0,0,0,0
dd 2,1,6,0,6,3,6,1,5,4,5,3,1,6,6,0,0,0,0
dd 4,0,0,3,1,1,6,4,5,0,4,1,6,0,6,0,0,0,0
dd 2,4,2,3,6,0,6,0,5,3,3,1,2,0,5,0,0,0,0
dd 1,6,1,2,2,1,4,2,6,4,1,1,2,0,2,2,1,1057,0
dd 3,3,0,6,2,5,0,5,5,4,2,4,0,1,6,0,0,0,0
dd 5,0,4,4,3,4,3,3,6,1,1,0,0,0,6,3,1,14336,0
dd 5,5,4,6,2,4,0,1,6,0,3,6,0,0,5,0,0,0,0
dd 1,6,0,4,6,2,6,2,1,0,6,1,4,6,1,0,0,0,0
dd 3,3,6,1,2,1,3,1,5,5,0,2,2,6,0,0,0,0,0
dd 1,0,1,1,3,4,3,3,4,2,2,1,3,2,5,0,0,0,0
dd 0,1,4,6,1,5,4,3,4,1,1,5,3,4,3,0,0,0,0
dd 6,5,3,0,6,4,3,1,1,3,3,6,5,6,0,3,1,1092,0
dd 4,2,5,4,6,6,6,3,2,2,1,3,2,4,0,0,0,0,0
dd 2,1,5,5,4,5,0,2,4,3,6,2,3,2,2,0,0,0,0
dd 1,3,0,4,2,0,1,4,4,5,6,4,5,6,4,5,1,2184,0
dd 3,3,4,5,4,1,0,4,5,1,3,4,3,6,0,0,0,0,0
dd 1,6,4,6,2,1,1,2,0,0,0,6,0,3,4,0,0,0,0
dd 0,4,5,3,4,1,1,6,2,0,6,6,2,6,6,0,0,0,0
dd 1,5,2,1,5,1,6,4,1,0,6,4,0,6,4,0,0,0,0
dd 0,3,6,3,0,2,1,6,1,2,6,0,3,1,1,0,0,0,0
dd 4,0,2,3,0,2,6,4,1,3,0,6,1,4,3,0,0,0,0
dd 0,0,5,5,4,5,1,5,2,5,2,3,6,5,2,0,0,0,0
dd 5,5,4,1,5,5,2,5,4,6,3,1,4,6,5,0,0,0,0
dd 6,3,3,5,1,4,6,4,0,0,2,4,0,2,4,0,0,0,0
dd 1,2,6,2,4,5,0,4,3,0,3,0,1,3,2,0,0,0,0
dd 0,0,5,0,3,6,5,2,4,6,1,3,4,5,0,0,0,0,0
dd 0,4,3,4,0,0,1,5,3,5,3,2,6,5,6,0,0,0,0
dd 5,4,6,6,2,1,3,3,1,6,1,4,5,2,0,0,0,0,0
dd 4,4,2,1,3,5,1,4,6,6,1,2,1,5,2,0,0,0,0
dd 0,3,1,1,5,0,2,4,4,3,0,2,0,6,5,3,1,1057,0
dd 6,2,3,2,3,0,5,1,4,2,1,5,2,5,2,0,0,0,0
dd 2,6,4,1,6,4,5,1,4,0,6,5,5,2,1,0,0,0,0
dd 5,2,0,1,6,3,6,0,6,2,0,0,3,4,1,0,0,0,0
dd 5,0,3,0,3,0,2,4,6,0,2,6,1,6,6,0,0,0,0
dd 6,5,1,3,2,6,1,4,4,6,5,0,1,2,3,0,0,0,0
dd 2,4,6,0,4,3,0,6,3,1,4,2,4,2,4,0,0,0,0
dd 1,6,5,1,3,6,3,1,5,6,2,3,0,6,6,0,0,0,0
dd 5,6,6,5,6,1,0,5,6,0,6,0,0,1,0,0,0,0,0
dd 2,5,2,4,4,4,1,4,5,3,5,2,5,4,1,0,0,0,0
dd 4,3,5,1,4,6,5,5,5,6,5,6,0,2,1,10,2,1476,0
dd 5,3,6,1,6,5,2,0,6,1,0,4,6,4,1,7,1,4368,0
dd 6,3,4,1,1,1,0,5,3,1,5,5,3,1,1,2,1,16912,0
dd 0,3,5,5,3,6,3,1,5,4,2,5,4,3,5,5,1,16644,0
dd 6,1,1,1,6,4,3,5,1,5,3,1,4,3,0,2,1,14,0
dd 6,4,2,5,6,0,4,2,1,1,1,2,3,0,4,0,0,0,0
dd 6,6,3,6,2,4,2,0,0,0,3,0,0,3,4,3,1,896,0
dd 6,6,6,4,5,2,5,5,5,4,6,5,1,0,2,12,2,455,0
dd 2,1,0,1,6,4,0,5,1,0,2,0,1,1,4,2,1,8456,0
dd 5,6,4,5,2,5,5,2,1,0,0,3,5,1,0,5,1,4161,0
dd 0,6,4,5,5,3,5,0,1,5,3,2,1,0,5,5,1,16912,0
dd 6,2,0,0,6,6,1,6,6,2,1,2,2,4,6,0,0,0,0
dd 0,6,3,1,3,1,5,4,1,1,6,2,4,0,4,0,0,0,0
dd 1,1,1,1,6,2,6,0,4,1,2,3,5,5,2,4,1,15,0
dd 4,5,1,5,0,6,6,4,6,1,0,1,4,5,5,0,0,0,0
dd 6,3,3,4,6,6,1,6,0,2,5,2,2,5,1,0,0,0,0
dd 6,3,5,0,1,2,1,6,6,6,0,5,0,6,6,7,1,896,0
dd 0,2,5,1,2,4,5,0,1,0,3,2,2,2,5,2,1,14336,0
dd 0,5,1,5,5,1,1,0,3,3,6,1,5,1,2,0,0,0,0
dd 4,6,4,4,4,0,1,4,3,3,5,5,6,2,1,5,1,28,0
dd 2,2,2,3,0,3,3,1,2,0,0,6,4,4,2,4,2,16647,0
dd 2,6,5,2,0,2,6,6,4,0,5,0,5,3,5,0,0,0,0
dd 2,4,1,2,2,5,0,4,0,4,4,5,1,5,5,0,0,0,0
dd 0,0,3,2,4,5,5,5,4,5,0,2,4,1,5,10,2,4592,0
dd 2,2,3,6,3,4,0,2,4,2,5,4,6,6,4,0,0,0,0
dd 2,6,0,2,2,0,0,1,1,4,0,1,3,5,2,3,1,1092,0
dd 2,5,4,3,1,2,4,5,0,1,6,3,2,6,5,0,0,0,0
dd 3,3,4,1,4,4,5,5,0,0,2,3,4,4,6,0,0,0,0
dd 3,3,0,0,2,0,5,1,0,4,0,3,1,5,4,0,0,0,0
dd 6,2,2,3,5,4,1,3,0,0,0,0,2,5,1,0,0,0,0
dd 1,6,2,6,4,1,0,6,6,1,1,1,0,3,3,2,1,1057,0
dd 5,3,5,6,5,2,2,1,6,3,1,2,2,5,1,0,0,0,0
dd 6,1,3,5,4,5,0,5,3,3,3,6,1,6,0,0,0,0,0
dd 4,2,0,3,0,3,5,0,4,6,3,4,0,4,1,3,1,4228,0
dd 3,5,1,5,6,3,2,1,3,4,2,5,2,6,5,0,0,0,0
dd 0,1,0,5,5,6,0,5,0,6,4,2,0,6,1,3,1,4161,0
dd 5,0,2,0,0,3,4,3,6,1,5,5,3,5,0,0,0,0,0
dd 3,0,5,5,4,3,4,6,3,4,5,1,4,5,6,0,0,0,0
dd 3,4,3,4,0,0,0,0,1,3,0,1,2,0,2,3,1,224,0
dd 2,2,1,0,0,1,4,5,6,1,4,6,0,4,2,0,0,0,0
dd 1,1,5,2,0,2,2,6,1,1,0,5,0,2,4,0,0,0,0
dd 0,2,6,2,4,2,4,6,5,6,1,4,6,3,4,7,1,4228,0
dd 6,0,3,0,3,0,2,1,0,2,0,1,6,4,4,0,0,0,0
dd 1,2,1,4,3,6,1,4,4,5,5,6,0,3,0,0,0,0,0
dd 6,2,3,3,3,6,0,5,1,3,4,1,5,6,3,6,2,16924,0
dd 6,5,1,1,6,5,1,2,5,0,3,6,0,5,4,0,0,0,0
dd 4,4,2,3,5,0,3,0,1,2,1,6,6,3,3,0,0,0,0
dd 6,1,6,2,4,5,6,0,5,1,1,2,6,5,4,7,1,4161,0
dd 1,3,4,0,4,6,2,5,1,5,5,2,5,2,5,0,0,0,0
dd 1,1,4,1,6,4,2,5,1,5,4,5,6,5,6,0,0,0,0
dd 1,5,6,2,3,1,3,3,5,2,1,1,3,2,6,2,1,1057,0
dd 0,1,5,0,1,0,1,5,3,6,0,2,1,4,6,3,1,1057,0
dd 0,2,1,4,1,0,0,2,6,1,1,6,4,1,2,0,0,0,0
dd 0,4,6,0,3,6,6,4,0,6,0,3,5,3,1,0,0,0,0
dd 2,2,5,3,1,1,1,3,3,5,2,6,6,3,6,3,1,8456,0
dd 6,2,6,2,2,0,2,5,5,5,6,2,3,2,4,7,2,3010,0
dd 1,6,2,3,3,1,1,5,4,0,6,0,6,1,5,0,0,0,0
dd 2,5,4,1,4,4,2,1,3,0,5,6,4,1,3,0,0,0,0
dd 3,5,5,6,5,1,6,5,6,4,3,0,2,4,4,0,0,0,0
dd 5,2,0,2,3,2,0,2,5,1,4,2,5,2,6,4,2,10378,0
dd 4,5,5,6,1,1,3,2,4,5,5,3,0,3,5,0,0,0,0
dd 0,5,3,1,2,6,6,1,1,2,5,6,3,5,3,0,0,0,0
dd 5,5,2,6,5,1,2,0,6,4,1,4,6,2,3,0,0,0,0
dd 5,1,6,5,1,1,0,2,3,0,0,3,3,5,4,0,0,0,0
dd 1,1,2,0,5,0,2,1,5,6,4,4,1,0,0,0,0,0,0
dd 4,0,5,5,0,4,3,6,1,2,2,0,5,4,6,0,0,0,0
dd 5,0,6,3,1,5,3,6,1,5,4,2,5,1,0,0,0,0,0
dd 0,2,3,4,1,5,0,2,4,5,1,2,6,5,5,0,0,0,0
dd 1,2,6,2,3,6,2,5,0,6,5,0,0,2,1,0,0,0,0
dd 0,0,3,6,3,6,0,3,0,6,2,0,3,2,2,6,2,6342,0
dd 6,6,6,3,2,3,6,6,3,5,3,3,5,4,2,7,1,7,0
dd 4,2,1,6,5,4,0,3,5,5,5,0,4,6,0,0,0,0,0
dd 1,4,0,3,2,4,6,4,6,4,4,6,1,4,0,5,1,8322,0
dd 4,2,1,3,6,2,5,4,5,1,0,6,3,6,5,0,0,0,0
dd 2,5,4,3,3,3,3,5,5,4,1,0,0,5,1,5,1,8322,0
dd 1,6,0,3,6,4,0,2,5,6,5,2,3,3,6,7,1,16912,0
dd 4,2,3,6,6,0,1,2,5,2,4,6,4,4,0,0,0,0,0
dd 5,1,4,6,0,5,5,2,5,6,0,0,0,3,0,3,1,7168,0
dd 4,1,3,3,5,1,4,3,1,3,6,5,4,1,5,5,1,4161,0
dd 4,2,1,0,0,3,4,4,4,4,5,5,1,6,5,10,1,960,0
dd 0,2,1,3,0,3,0,0,1,1,4,5,0,2,0,3,1,4161,0
dd 6,5,1,6,3,4,4,3,5,3,0,3,4,6,6,0,0,0,0
dd 2,3,0,1,1,5,6,4,5,4,0,5,3,1,5,0,0,0,0
dd 6,3,6,1,1,4,3,6,2,0,2,2,1,0,2,0,0,0,0
dd 2,3,3,0,4,3,5,3,1,3,3,0,5,2,1,0,0,0,0
dd 2,1,0,6,1,0,4,1,3,3,5,5,3,2,2,0,0,0,0
dd 3,2,4,0,0,2,5,3,4,1,4,3,5,3,5,0,0,0,0
dd 1,4,6,1,5,5,3,0,2,2,6,0,4,6,6,0,0,0,0
dd 6,5,2,6,3,4,3,1,4,4,3,1,6,3,0,0,0,0,0
dd 1,4,3,6,0,2,2,3,6,2,3,3,0,0,5,0,0,0,0
dd 0,2,4,1,5,6,6,2,4,5,4,1,4,6,0,0,0,0,0
dd 1,2,1,3,0,0,3,0,5,2,2,5,6,5,6,0,0,0,0
dd 6,6,2,0,3,0,0,0,6,1,6,3,6,4,6,3,1,224,0
dd 0,6,5,0,3,6,4,0,4,3,0,5,0,3,0,0,0,0,0
dd 1,3,6,6,0,3,2,4,2,6,3,6,2,2,3,0,0,0,0
dd 5,4,1,2,6,0,1,3,0,2,3,1,4,2,6,0,0,0,0
dd 5,1,5,3,0,2,1,5,5,2,5,3,2,5,5,5,1,16644,0
dd 5,0,5,4,2,4,3,4,0,6,2,1,3,0,0,0,0,0,0
dd 6,3,2,4,1,6,6,4,5,0,2,4,4,1,1,5,1,2184,0
dd 3,1,3,4,3,2,3,2,0,0,0,3,2,5,3,0,0,0,0
dd 5,2,2,4,5,6,6,0,1,4,3,1,6,2,5,0,0,0,0
dd 1,4,0,6,2,4,1,6,0,4,6,5,2,3,5,0,0,0,0
dd 5,5,3,5,1,5,4,0,4,4,4,2,6,2,5,0,0,0,0
dd 6,3,2,2,1,2,6,6,3,1,3,1,4,4,0,0,0,0,0
dd 0,0,0,2,4,1,6,5,0,0,6,4,6,6,5,3,1,7,0
dd 5,3,2,1,4,1,5,6,1,0,1,1,5,2,6,5,1,4161,0
dd 4,5,2,1,6,5,2,0,2,4,1,5,6,5,0,0,0,0,0
dd 4,0,0,1,2,6,1,3,2,0,5,3,3,2,0,0,0,0,0
dd 6,2,5,4,2,5,2,1,5,3,0,0,4,2,5,5,1,16644,0
dd 4,6,2,0,5,1,1,1,0,3,1,5,3,0,4,5,2,8680,0
dd 6,6,4,1,6,1,5,0,5,3,6,1,2,4,6,0,0,0,0
dd 1,2,6,3,3,0,0,6,0,2,1,4,4,5,2,0,0,0,0
dd 0,1,0,3,6,0,5,3,3,4,2,0,0,3,0,3,1,8456,0
dd 3,2,2,2,0,5,4,1,3,5,4,1,4,0,6,2,1,14,0
dd 1,2,6,1,6,4,6,6,3,6,0,0,3,0,6,7,1,16912,0
dd 6,4,6,0,0,0,1,3,2,3,4,2,5,0,3,0,0,0,0
dd 2,4,3,4,4,0,6,1,6,0,2,5,6,6,4,0,0,0,0
dd 0,0,4,4,1,0,1,4,6,1,1,2,4,3,3,5,1,4228,0
dd 4,6,6,4,2,0,0,2,5,0,1,6,4,5,3,0,0,0,0
dd 3,0,4,3,4,4,3,3,1,3,1,0,4,6,1,0,0,0,0
dd 5,6,0,2,6,4,3,5,0,3,4,6,4,5,1,0,0,0,0
dd 1,1,6,2,5,0,6,1,6,5,2,3,6,4,2,0,0,0,0
dd 0,2,6,0,0,3,1,4,2,0,1,5,5,5,3,5,1,14336,0
dd 4,5,0,3,6,0,5,2,6,2,3,4,5,4,3,0,0,0,0
dd 1,3,5,3,4,2,2,2,0,0,5,3,6,1,3,2,1,224,0
dd 5,1,5,5,1,6,1,4,5,6,1,3,4,5,3,5,1,8456,0
dd 0,2,1,3,2,4,3,5,2,0,3,4,4,0,1,0,0,0,0
dd 3,2,4,6,5,4,3,2,0,0,4,4,2,1,0,0,0,0,0
dd 2,4,3,0,6,1,2,3,3,4,6,1,3,1,2,3,1,4228,0
dd 1,1,1,1,2,0,3,2,1,1,4,0,4,1,3,6,2,8463,0
dd 3,3,6,2,2,4,3,0,6,6,4,2,0,4,5,0,0,0,0
dd 3,3,3,4,2,5,6,0,0,3,1,1,5,3,3,3,1,7,0
dd 2,5,6,4,1,3,1,5,6,5,1,1,6,6,4,0,0,0,0
dd 3,6,4,3,5,6,0,0,5,4,6,2,0,5,3,0,0,0,0
dd 2,2,6,3,4,0,0,6,0,2,2,1,5,4,6,0,0,0,0
dd 4,2,0,3,6,3,6,6,5,1,6,6,0,4,3,0,0,0,0
dd 4,1,2,2,2,4,3,1,2,4,1,0,4,5,2,4,2,16668,0
dd 4,4,3,3,6,3,0,3,5,4,1,5,3,5,3,3,1,4228,0
dd 4,1,3,6,4,1,3,4,5,4,5,5,5,2,0,5,1,7168,0
dd 3,5,4,2,5,2,0,1,5,3,5,3,1,6,1,0,0,0,0
dd 1,5,4,3,6,3,3,3,3,0,5,4,5,1,5,6,1,480,0
dd 4,0,2,4,1,4,3,0,4,0,5,1,4,0,1,3,1,8322,0
dd 6,3,2,3,5,5,6,4,0,0,0,2,6,0,1,7,1,4161,0
dd 5,5,1,0,0,5,5,1,3,5,0,1,6,4,6,0,0,0,0
dd 5,0,1,1,6,1,4,5,5,6,5,5,6,1,0,0,0,0,0
dd 4,6,0,5,3,5,4,5,1,5,4,0,5,6,0,0,0,0,0
dd 5,3,5,4,0,6,3,1,1,3,2,1,5,3,2,0,0,0,0
dd 3,6,3,6,0,2,0,6,0,1,0,4,2,3,4,0,0,0,0
dd 1,0,6,3,2,1,5,1,6,4,2,1,3,2,4,0,0,0,0
dd 5,4,6,6,2,4,6,0,6,6,5,2,4,0,1,0,0,0,0
dd 6,6,6,1,4,0,2,3,0,6,3,3,4,6,3,7,1,7,0
dd 0,1,0,5,4,0,6,5,0,5,5,2,2,1,1,0,0,0,0
dd 0,4,1,6,1,5,4,3,5,0,1,6,5,2,3,0,0,0,0
dd 1,6,6,6,6,2,1,5,3,3,4,1,2,4,1,14,1,30,0
dd 5,2,5,3,1,6,3,2,6,5,2,4,1,4,1,0,0,0,0
dd 4,0,3,0,0,0,0,5,0,6,6,5,0,6,6,3,1,4368,0
dd 1,2,3,1,5,0,2,6,3,5,6,4,2,0,0,0,0,0,0
dd 6,6,6,6,5,3,1,3,2,4,1,5,5,6,3,14,1,15,0
dd 3,0,0,1,6,1,0,2,2,2,5,5,6,4,2,2,1,896,0
dd 4,3,0,2,3,4,1,5,6,1,0,6,3,3,6,0,0,0,0
dd 5,0,0,4,6,1,3,0,0,1,5,0,4,6,6,0,0,0,0
dd 5,4,2,2,3,6,3,4,4,0,4,0,3,6,2,0,0,0,0
dd 6,4,5,3,5,2,5,0,5,6,1,3,5,4,1,5,1,4368,0
dd 1,4,1,5,4,6,2,6,2,4,1,2,4,5,1,0,0,0,0
dd 0,1,0,6,5,1,2,4,5,2,3,1,5,3,5,5,1,4368,0
dd 2,4,1,6,1,3,1,6,1,3,0,2,0,4,5,0,0,0,0
dd 5,6,0,5,5,4,0,0,1,3,5,6,6,1,3,0,0,0,0
dd 1,2,3,3,4,5,4,5,0,5,0,2,6,6,6,7,1,28672,0
dd 1,0,3,2,5,6,5,3,1,3,1,5,0,5,1,0,0,0,0
dd 6,0,4,6,3,3,0,1,4,5,6,1,3,1,1,0,0,0,0
dd 4,3,1,4,1,1,5,6,1,4,4,0,0,4,6,0,0,0,0
dd 1,1,1,5,1,2,0,6,0,0,6,4,1,4,1,2,1,7,0
dd 4,2,0,2,0,4,4,4,4,3,0,3,4,3,0,15,2,4577,0
dd 6,5,3,5,3,1,2,3,4,2,1,2,3,2,1,3,1,4228,0
dd 1,6,4,6,3,6,1,1,1,3,4,2,4,3,6,2,1,448,0
dd 0,2,2,4,3,4,4,6,3,5,4,6,3,2,2,3,1,4368,0
dd 5,3,3,5,0,4,1,1,3,0,1,0,5,4,4,0,0,0,0
dd 3,2,5,6,6,5,6,0,5,6,5,6,6,2,0,0,0,0,0
dd 6,1,2,6,4,2,3,3,2,5,6,6,4,6,4,0,0,0,0
dd 0,4,0,2,0,0,4,5,1,1,4,5,0,1,5,0,0,0,0
dd 1,3,5,0,2,4,2,3,0,6,2,2,6,6,5,0,0,0,0
dd 0,1,4,3,2,2,1,1,1,0,4,5,5,0,6,2,1,448,0
dd 2,2,3,5,4,4,5,4,5,3,2,1,2,2,1,0,0,0,0
dd 2,1,3,2,4,5,6,3,2,6,0,2,2,3,5,0,0,0,0
dd 3,0,0,5,0,2,0,6,4,4,0,1,1,3,4,3,1,1092,0
dd 2,3,6,0,0,2,2,0,6,6,3,5,6,4,0,0,0,0,0
dd 4,2,5,3,0,5,5,5,0,2,4,2,4,2,4,5,1,224,0
dd 3,4,5,5,0,3,3,3,0,4,4,4,1,4,2,3,1,224,0
dd 5,1,3,3,1,0,6,3,3,3,2,4,5,3,4,6,2,9096,0
dd 1,2,0,2,0,5,1,5,6,6,1,6,4,0,2,0,0,0,0
dd 4,0,6,4,4,6,1,5,0,6,1,2,3,6,0,0,0,0,0
dd 3,6,1,2,4,5,6,6,1,5,0,0,3,3,4,0,0,0,0
dd 1,4,2,4,4,5,2,2,6,2,5,2,2,2,1,4,2,14468,0
dd 6,3,2,0,6,1,3,1,6,1,6,1,4,0,2,0,0,0,0
dd 4,2,6,3,2,6,5,1,0,0,6,5,0,4,4,0,0,0,0
dd 2,2,5,1,2,6,3,0,0,1,1,1,0,3,4,0,0,0,0
dd 4,5,0,2,2,0,5,6,4,2,0,5,6,6,4,5,1,2114,0
dd 3,5,4,6,1,6,0,2,6,0,3,0,0,5,0,0,0,0,0
dd 3,0,6,0,1,2,3,2,1,0,5,2,4,6,3,0,0,0,0
dd 6,3,1,4,6,4,0,4,6,0,2,4,6,0,3,12,2,6552,0
dd 1,6,0,1,1,5,1,2,1,0,0,2,1,1,6,10,2,12633,0
dd 1,0,4,5,6,0,3,0,2,6,3,3,4,0,0,3,1,8322,0
dd 6,0,2,6,0,5,5,3,1,2,1,0,2,4,0,0,0,0,0
dd 1,6,0,4,5,6,2,3,0,3,2,6,5,4,0,3,1,16644,0
dd 0,2,4,5,3,3,2,0,5,3,5,3,0,2,6,0,0,0,0
dd 6,6,2,2,1,3,3,5,5,2,4,4,3,4,2,0,0,0,0
dd 6,2,2,5,2,2,6,2,0,1,4,3,6,5,3,7,1,4161,0
dd 3,2,2,2,0,4,0,1,0,3,5,4,3,4,4,2,1,14,0
dd 3,2,6,2,1,4,4,0,1,4,1,1,2,6,6,0,0,0,0
dd 2,2,5,6,3,0,3,1,1,1,1,2,4,4,3,2,1,896,0
dd 6,0,0,0,2,4,4,1,5,4,3,6,5,1,2,3,1,14,0
dd 2,2,4,3,6,0,3,4,0,5,1,3,4,5,1,5,1,4228,0
dd 6,0,1,2,0,2,2,0,5,2,2,6,5,6,5,0,0,0,0
dd 6,2,6,5,5,6,6,3,4,4,6,4,5,0,6,14,2,1125,0
dd 3,6,6,4,2,1,5,3,5,5,4,0,5,1,2,0,0,0,0
dd 1,4,2,0,2,2,4,1,1,4,6,0,6,2,0,0,0,0,0
dd 2,3,1,3,5,4,4,5,1,6,6,5,5,4,1,2,1,16644,0
dd 6,6,5,6,6,6,2,6,2,6,0,3,1,4,0,0,0,0,0
dd 1,4,4,5,2,2,0,6,3,6,6,2,5,2,3,0,0,0,0
dd 1,1,4,6,5,6,0,6,3,2,4,5,6,2,6,0,0,0,0
dd 4,1,2,2,2,6,1,3,1,2,6,5,4,3,4,2,1,28,0
dd 0,2,5,4,3,6,5,5,4,3,5,1,1,3,4,5,1,1092,0
dd 2,0,3,1,0,5,0,1,2,2,4,1,4,5,4,2,1,2184,0
dd 2,5,3,0,4,2,2,1,2,6,1,2,4,1,1,0,0,0,0
dd 6,1,4,6,5,5,0,6,3,5,0,5,3,6,4,0,0,0,0
dd 1,1,2,5,5,5,1,3,1,2,3,5,1,2,2,2,1,4161,0
dd 6,4,1,0,6,4,5,1,6,0,0,6,6,2,5,7,1,4368,0
dd 0,2,6,1,2,5,4,5,5,3,4,2,4,2,5,0,0,0,0
dd 5,3,0,6,3,0,2,4,6,4,2,4,6,4,3,0,0,0,0
dd 4,0,6,6,0,1,5,5,5,1,5,6,6,6,0,12,2,14784,0
dd 4,4,0,3,3,2,1,0,6,1,1,4,5,5,6,0,0,0,0
dd 4,3,3,5,6,1,4,5,3,5,3,5,6,5,4,5,1,2184,0
dd 5,3,2,2,1,0,3,5,5,5,5,4,0,6,1,5,1,896,0
dd 6,3,5,3,4,0,0,0,1,3,3,2,4,3,6,3,1,224,0
dd 0,6,1,1,4,1,4,3,2,1,4,3,4,3,4,0,0,0,0
dd 3,4,6,6,6,1,2,3,1,2,5,0,2,1,5,7,1,28,0
dd 2,2,1,5,5,2,2,3,3,1,0,6,6,6,5,7,1,14336,0
dd 2,2,1,6,5,5,4,4,5,6,5,5,6,4,4,0,0,0,0
dd 5,4,1,6,5,0,0,2,0,5,2,3,3,4,4,0,0,0,0
dd 3,2,2,2,0,0,4,0,5,3,2,1,1,6,6,2,1,14,0
dd 3,4,1,4,6,5,2,2,6,5,5,6,4,0,5,0,0,0,0
dd 6,6,2,4,0,1,3,1,6,5,4,3,2,6,2,0,0,0,0
dd 0,6,0,5,1,6,5,2,0,2,2,5,6,0,4,0,0,0,0
dd 6,5,5,4,5,4,0,5,5,5,1,0,0,6,0,5,1,896,0
dd 2,3,0,1,6,4,0,0,1,0,1,1,0,5,5,3,1,4228,0
dd 5,5,3,6,3,5,5,3,0,5,6,4,0,0,1,0,0,0,0
dd 4,2,2,0,5,2,3,6,3,3,4,3,3,2,1,0,0,0,0
dd 0,0,0,0,2,0,4,1,1,2,2,3,4,4,6,6,1,15,0
dd 6,1,0,3,0,2,3,6,1,5,5,0,2,2,3,0,0,0,0
dd 3,6,5,3,6,4,6,6,5,4,6,1,6,1,4,0,0,0,0
dd 0,2,3,2,5,5,1,2,1,5,4,3,5,0,1,0,0,0,0
dd 4,0,2,5,3,6,3,3,6,4,3,3,0,4,5,0,0,0,0
dd 2,1,3,1,0,5,3,4,2,0,1,3,3,5,5,0,0,0,0
dd 1,2,1,6,2,5,0,2,2,0,0,5,5,1,3,0,0,0,0
dd 2,3,2,0,3,3,3,2,1,0,6,5,4,2,6,0,0,0,0
dd 3,0,1,6,6,5,4,0,4,2,0,4,2,6,4,0,0,0,0
dd 4,6,5,6,6,0,1,2,1,1,2,1,1,2,0,0,0,0,0
dd 2,2,4,6,5,4,6,5,6,5,3,1,2,6,6,7,1,8456,0
dd 2,1,1,3,1,0,3,5,6,6,0,3,4,2,6,0,0,0,0
dd 0,0,5,2,4,2,3,4,2,5,3,2,6,5,4,0,0,0,0
dd 1,5,1,0,5,3,1,0,3,0,2,6,2,1,6,0,0,0,0
dd 2,1,2,4,2,5,3,0,4,0,2,4,2,1,5,0,0,0,0
dd 0,4,3,6,0,3,2,3,5,4,3,6,1,3,3,0,0,0,0
dd 0,4,2,5,5,0,2,6,4,4,2,1,6,3,5,2,1,1092,0
dd 3,0,1,1,3,1,0,5,0,1,5,1,3,5,0,0,0,0,0
dd 6,0,0,1,6,1,5,1,6,3,4,0,0,6,5,0,0,0,0
dd 6,0,6,6,1,0,0,5,6,6,2,0,4,6,0,10,2,10570,0
dd 3,6,5,1,3,3,5,6,0,6,6,6,6,1,6,7,1,7168,0
dd 3,5,2,4,3,0,3,1,5,0,0,3,2,5,3,0,0,0,0
dd 0,4,2,4,2,3,3,1,4,4,0,1,6,2,3,0,0,0,0
dd 6,6,6,4,4,6,3,0,4,6,2,5,4,6,2,12,2,4375,0
dd 2,6,1,4,5,1,0,3,6,6,2,2,0,3,3,0,0,0,0
dd 0,5,4,1,0,0,5,2,4,4,2,0,0,0,6,3,1,14336,0
dd 2,1,6,3,2,4,6,2,6,5,1,0,6,6,3,0,0,0,0
dd 2,2,3,0,1,2,0,6,6,3,3,0,2,4,2,0,0,0,0
dd 0,3,1,3,0,3,4,4,4,4,6,3,6,4,1,10,1,960,0
dd 1,6,2,4,4,4,2,2,1,5,1,3,2,0,1,2,1,4228,0
dd 4,4,3,3,4,5,6,5,0,6,2,2,2,6,0,2,1,7168,0
dd 2,4,4,3,6,2,4,4,5,3,6,4,3,5,2,5,1,2114,0
dd 0,6,6,5,1,2,3,5,5,3,1,6,6,2,5,0,0,0,0
dd 3,4,2,2,6,2,5,0,1,3,6,3,2,1,0,0,0,0,0
dd 5,0,3,2,2,0,0,2,2,5,2,3,3,1,0,0,0,0,0
dd 4,1,1,3,2,1,5,6,3,5,1,2,1,4,6,0,0,0,0
dd 5,6,2,5,4,5,6,3,1,2,6,4,2,2,2,2,1,28672,0
dd 5,6,4,1,6,1,3,0,6,5,3,3,3,1,1,3,1,7168,0
dd 3,6,6,6,2,4,0,1,3,3,6,2,3,2,2,7,1,14,0
dd 2,0,0,1,6,6,3,2,0,1,0,1,3,0,3,0,0,0,0
dd 4,1,2,1,1,6,4,3,1,5,2,3,0,5,0,0,0,0,0
dd 0,1,0,0,1,0,1,1,5,3,1,2,0,1,2,2,1,8322,0
dd 3,6,4,5,5,6,4,6,0,3,6,5,4,6,6,7,1,8322,0
dd 5,5,4,1,4,3,2,5,0,1,5,1,6,0,6,0,0,0,0
dd 2,1,5,2,6,1,0,5,6,6,5,6,6,2,4,7,1,4368,0
dd 0,1,3,3,3,5,1,2,2,1,0,2,0,1,5,3,1,28,0
dd 2,4,2,3,3,0,6,3,0,2,6,5,6,6,3,0,0,0,0
dd 5,5,6,6,1,0,0,4,0,6,4,1,2,2,5,0,0,0,0
dd 4,4,4,0,6,3,1,6,1,1,4,2,3,5,6,5,1,7,0
dd 4,6,6,4,1,4,6,0,3,1,0,6,2,3,2,7,1,2114,0
dd 2,4,6,2,2,5,2,0,5,1,0,5,5,3,4,0,0,0,0
dd 0,5,1,0,4,1,0,3,0,3,0,4,1,5,6,0,0,0,0
dd 5,6,4,1,6,6,6,3,4,5,3,3,1,1,3,0,0,0,0
dd 2,1,1,6,3,1,3,6,2,4,4,3,1,4,2,0,0,0,0
dd 3,2,0,3,1,5,0,4,3,4,2,5,0,4,3,0,0,0,0
dd 1,3,1,1,6,0,5,0,0,1,2,2,2,0,0,2,1,7168,0
dd 4,2,5,5,2,4,2,6,6,2,5,4,6,2,5,0,0,0,0
dd 2,2,6,1,2,3,5,6,6,3,6,0,6,1,2,7,1,4228,0
dd 0,2,3,1,6,4,2,1,4,3,3,0,3,2,1,0,0,0,0
dd 5,0,6,3,4,3,6,3,4,4,5,6,5,4,0,0,0,0,0
dd 2,0,6,6,3,6,5,6,0,2,6,6,5,1,5,7,1,2184,0
dd 0,4,3,4,4,5,4,1,0,3,6,0,0,5,6,0,0,0,0
dd 5,6,1,2,3,4,1,3,1,4,2,2,0,5,0,0,0,0,0
dd 6,1,4,1,3,5,6,6,4,6,2,5,4,3,1,0,0,0,0
dd 2,1,3,0,5,2,3,1,2,4,2,3,2,2,6,2,1,1057,0
dd 5,4,1,3,4,1,6,2,0,5,3,5,2,3,5,0,0,0,0
dd 0,5,5,4,5,4,4,5,4,4,4,2,3,0,6,0,0,0,0
dd 1,0,1,1,3,1,4,6,3,3,4,3,3,1,0,3,1,4368,0
dd 3,5,5,6,1,5,1,5,6,5,2,0,0,4,1,0,0,0,0
dd 4,3,4,5,2,2,2,1,4,2,5,0,5,2,3,0,0,0,0
dd 1,1,6,4,6,1,1,3,2,2,3,0,4,2,0,0,0,0,0
dd 1,1,1,6,2,1,1,3,3,3,5,6,5,0,2,5,2,903,0
dd 5,0,0,4,6,6,3,5,6,3,1,6,6,2,1,7,1,4368,0
dd 0,4,0,3,4,2,4,5,0,1,0,4,6,2,6,5,1,2114,0
dd 5,6,2,6,6,2,6,3,5,2,2,5,3,2,3,0,0,0,0
dd 5,0,5,0,3,3,1,1,4,4,5,0,6,5,5,0,0,0,0
dd 4,4,1,3,0,4,6,2,4,3,3,1,1,4,3,0,0,0,0
dd 5,2,3,3,3,3,5,4,3,4,4,3,3,5,4,6,2,4380,0
dd 4,2,1,4,0,2,2,0,5,3,4,6,5,2,3,0,0,0,0
dd 1,2,1,5,4,3,2,1,5,3,1,0,4,3,2,0,0,0,0
dd 4,1,4,6,3,4,3,1,0,2,5,4,6,5,1,0,0,0,0
dd 4,0,3,6,2,3,6,0,3,5,0,3,2,0,2,3,1,8322,0
dd 6,1,2,3,4,2,4,4,1,1,0,2,2,2,4,2,1,14336,0
dd 6,6,3,0,6,5,2,5,4,5,1,6,5,4,6,0,0,0,0
dd 5,1,0,1,2,4,0,4,5,4,2,5,3,0,3,0,0,0,0
dd 5,3,2,2,1,1,3,6,0,6,0,2,3,2,4,0,0,0,0
dd 5,1,1,6,5,6,6,6,6,4,3,1,4,4,2,14,1,480,0
dd 0,1,5,6,3,0,6,1,4,4,2,5,1,5,2,0,0,0,0
dd 4,5,3,2,1,5,0,4,0,5,0,4,4,5,2,0,0,0,0
dd 4,6,1,1,4,5,4,4,5,6,6,0,6,3,5,0,0,0,0
dd 2,5,5,6,5,1,0,0,4,3,3,5,6,5,4,0,0,0,0
dd 5,0,2,2,4,4,1,6,0,0,1,0,5,5,2,0,0,0,0
dd 1,0,5,6,6,0,0,5,1,3,6,1,2,3,4,0,0,0,0
dd 5,6,3,2,1,2,1,0,0,2,6,2,2,6,5,0,0,0,0
dd 0,5,5,5,5,6,2,0,1,1,4,5,0,5,5,10,1,30,0
dd 3,5,6,1,4,4,1,4,3,0,5,5,1,1,6,0,0,0,0
dd 6,1,2,4,1,4,6,1,1,0,6,2,0,6,2,0,0,0,0
dd 1,2,5,2,3,6,1,1,3,0,3,0,3,3,1,3,1,4368,0
dd 0,6,5,6,3,6,0,5,2,0,0,2,2,5,4,0,0,0,0
dd 2,5,4,4,3,0,6,5,6,0,0,2,6,6,0,0,0,0,0
dd 6,4,1,0,1,1,1,3,2,6,2,1,2,2,6,0,0,0,0
dd 5,0,1,5,6,4,0,6,4,1,0,3,2,3,0,0,0,0,0
dd 2,6,4,6,1,0,0,3,5,5,5,5,4,5,5,0,0,0,0
dd 0,2,6,4,3,5,1,6,3,0,3,1,5,5,6,0,0,0,0
dd 5,0,1,2,1,3,3,4,6,6,5,2,6,6,2,0,0,0,0
dd 1,3,3,5,4,3,4,5,4,2,0,3,6,4,2,0,0,0,0
dd 5,6,6,6,5,2,0,5,6,5,5,2,0,1,3,7,1,14,0
dd 0,0,5,6,5,6,5,4,0,2,3,1,0,6,1,0,0,0,0
dd 6,3,3,2,3,0,5,2,0,1,2,4,6,3,1,0,0,0,0
dd 6,3,2,4,4,4,4,2,1,2,1,1,3,6,1,0,0,0,0
dd 4,4,1,5,3,1,4,6,4,2,2,2,4,2,1,5,1,4161,0
dd 6,2,0,5,6,2,5,6,4,2,5,0,0,4,2,0,0,0,0
dd 6,5,3,1,5,2,3,4,5,5,1,0,0,1,6,0,0,0,0
dd 4,3,6,3,5,3,4,5,2,4,0,5,1,0,1,0,0,0,0
dd 1,1,4,6,3,6,5,2,0,0,0,5,0,6,5,0,0,0,0
dd 0,6,0,4,6,6,5,5,0,4,1,6,3,4,2,0,0,0,0
dd 1,6,0,1,5,5,3,6,5,3,0,1,6,4,3,0,0,0,0
dd 4,1,5,6,5,4,4,5,6,2,3,1,6,3,6,0,0,0,0
dd 1,3,1,0,6,3,0,4,5,4,0,3,6,6,3,0,0,0,0
dd 0,6,3,5,3,2,6,2,1,2,5,2,3,4,4,0,0,0,0
.code

273
tools/tests/clover_test.inc Normal file
View file

@ -0,0 +1,273 @@
include tools\tests\clover_fixtures.inc
.data
clpTestStage3D dd 0
clpTestReady db "build\actions\clover-ready.bmp",0
clpTestSpin db "build\actions\clover-spin.bmp",0
clpTestStopped db "build\actions\clover-stopped.bmp",0
clpTestWin db "build\actions\clover-win.bmp",0
clpTestNext db "build\actions\clover-next.bmp",0
clpTestPay db "build\actions\clover-paytable.bmp",0
.code
RunCloverTest3D PROC uses ebx esi edi
local oldBank:DWORD,oldMode:DWORD,oldSeed:DWORD,iterations:DWORD
local fixture:DWORD,expected:DWORD,rngSnapshot:DWORD,previous:DWORD,ticks:DWORD
mov eax,bankroll
mov oldBank,eax
mov eax,engineMode
mov oldMode,eax
mov eax,rngSeed3D
mov oldSeed,eax
mov clpTestStage3D,1
mov engineMode,MODE_WALK
mov interactTargetId,INTERACT_SLOTS_CLOVER
call Trigger3DInteraction
cmp engineMode,MODE_CLOVER
jne clp_test_fail
call TableBusy3D
test eax,eax
jnz clp_test_fail
call RenderNativeClover3D
invoke WriteActionSnapshot3D,OFFSET clpTestReady
test eax,eax
jz clp_test_fail
mov clpTestStage3D,2
lea ebx,clpFixtures3D
mov iterations,clpFixtureCount3D
clp_test_fixture:
mov fixture,ebx
mov esi,ebx
lea edi,clpGrid3D
mov ecx,15
rep movsd
call CloverEvaluate3D
mov ebx,fixture
mov eax,[ebx+60]
cmp clpTotal3D,eax
jne clp_test_fail
mov eax,[ebx+64]
cmp clpCount3D,eax
jne clp_test_fail
mov eax,[ebx+68]
cmp clpWinMask3D,eax
jne clp_test_fail
mov eax,[ebx+72]
cmp clpJackpot3D,eax
jne clp_test_fail
xor esi,esi
xor edi,edi
clp_test_combo_sum:
cmp esi,clpCount3D
jae @F
add edi,clpMults3D[esi*4]
mov eax,clpMasks3D[esi*4]
test eax,eax
jz clp_test_fail
and eax,clpWinMask3D
cmp eax,clpMasks3D[esi*4]
jne clp_test_fail
inc esi
jmp clp_test_combo_sum
@@:
cmp edi,clpTotal3D
jne clp_test_fail
add ebx,76
dec iterations
jnz clp_test_fixture
mov clpGrid3D,7
call CloverEvaluate3D
cmp clpTotal3D,0
jne clp_test_fail
cmp clpCount3D,0
jne clp_test_fail
mov clpGrid3D,0
mov clpTestStage3D,3
mov clpBet3D,50
mov bankroll,49
mov clpState3D,SLOT_READY
mov eax,rngSeed3D
mov rngSnapshot,eax
call StartNativeClover3D
cmp bankroll,49
jne clp_test_fail
mov eax,rngSnapshot
cmp rngSeed3D,eax
jne clp_test_fail
mov iterations,500
clp_test_round:
call ClearKeys3D
mov engineMode,MODE_CLOVER
mov clpState3D,SLOT_READY
mov clpBet3D,100
mov bankroll,100000
call StartNativeClover3D
cmp bankroll,99900
jne clp_test_fail
cmp clpState3D,SLOT_SPIN
jne clp_test_fail
mov eax,rngSeed3D
mov rngSnapshot,eax
call CloverEvaluate3D
mov eax,clpTotal3D
imul eax,100
add eax,99900
mov expected,eax
mov clpCount3D,0
mov clpTotal3D,0
call StartNativeClover3D
cmp bankroll,99900
jne clp_test_fail
mov eax,iterations
and eax,3
mov eax,slotsTestRates[eax*4]
mov deltaTime3D,eax
mov ticks,0
; Queue exit for half the rounds, without cancelling or repaying.
test iterations,1
jz @F
call CardTableEscape3D
@@:
clp_test_round_tick:
mov pressedKeys3D[57h],1
mov pressedKeys3D[20h],1
call HandleNativeClover3D
call ClearKeys3D
cmp clpBet3D,100
jne clp_test_fail
mov eax,rngSnapshot
cmp rngSeed3D,eax
jne clp_test_fail
inc ticks
cmp ticks,220
ja clp_test_fail
cmp clpState3D,SLOT_SPIN
je clp_test_round_tick
mov eax,expected
cmp bankroll,eax
jne clp_test_fail
call SettleNativeClover3D
mov eax,expected
cmp bankroll,eax
jne clp_test_fail
test iterations,1
jz @F
cmp engineMode,MODE_WALK
jne clp_test_fail
@@:
xor esi,esi
clp_test_final_column:
mov ebx,esi
shl ebx,8
mov ecx,clpSteps3D[esi*4]
lea ebx,clpTape3D[ebx+ecx*4]
xor edi,edi
clp_test_final_row:
mov ecx,edi
imul ecx,5
add ecx,esi
mov eax,[ebx+edi*4]
cmp eax,clpGrid3D[ecx*4]
jne clp_test_fail
inc edi
cmp edi,3
jb clp_test_final_row
inc esi
cmp esi,5
jb clp_test_final_column
dec iterations
jnz clp_test_round
mov clpTestStage3D,4
mov clpState3D,SLOT_SPIN
xor esi,esi
clp_test_monotonic_column:
mov clpTimer3D,0
mov previous,0
clp_test_monotonic_tick:
invoke CloverReelPosition3D,esi
cmp eax,previous
jb clp_test_fail
mov previous,eax
inc clpTimer3D
cmp clpTimer3D,3400
jbe clp_test_monotonic_tick
mov eax,clpSteps3D[esi*4]
imul eax,CLP_CELL
cmp eax,previous
jne clp_test_fail
inc esi
cmp esi,5
jb clp_test_monotonic_column
mov clpTestStage3D,5
mov clpState3D,SLOT_READY
mov bankroll,100000
call StartNativeClover3D
mov clpTimer3D,900
mov eax,rngSeed3D
mov rngSnapshot,eax
call RenderNativeClover3D
mov eax,rngSnapshot
cmp rngSeed3D,eax
jne clp_test_fail
cmp bankroll,99900
jne clp_test_fail
invoke WriteActionSnapshot3D,OFFSET clpTestSpin
mov clpTimer3D,2200
call RenderNativeClover3D
invoke WriteActionSnapshot3D,OFFSET clpTestStopped
; Force an all-seven jackpot and inspect distinct paid masks.
lea edi,clpGrid3D
mov eax,6
mov ecx,15
rep stosd
mov clpLeave3D,0
mov engineMode,MODE_CLOVER
call SettleNativeClover3D
cmp clpJackpot3D,1
jne clp_test_fail
cmp clpCount3D,2
jb clp_test_fail
call RenderNativeClover3D
invoke WriteActionSnapshot3D,OFFSET clpTestWin
mov pressedKeys3D[27h],1
call HandleNativeClover3D
cmp clpSelected3D,1
jne clp_test_fail
call RenderNativeClover3D
invoke WriteActionSnapshot3D,OFFSET clpTestNext
mov clpPaytable3D,1
call RenderNativeClover3D
invoke WriteActionSnapshot3D,OFFSET clpTestPay
call CardTableEscape3D
cmp engineMode,MODE_CLOVER
jne clp_test_fail
cmp clpPaytable3D,0
jne clp_test_fail
call CardTableEscape3D
cmp engineMode,MODE_WALK
jne clp_test_fail
mov clpTestStage3D,6
mov clpState3D,SLOT_SPIN
mov clpStake3D,10000
mov bankroll,999999999
call SettleNativeClover3D
cmp bankroll,1000000000
jne clp_test_fail
cmp clpGross3D,1
jne clp_test_fail
call SettleNativeClover3D
cmp bankroll,1000000000
jne clp_test_fail
mov eax,oldBank
mov bankroll,eax
mov eax,oldMode
mov engineMode,eax
mov eax,oldSeed
mov rngSeed3D,eax
call ClearKeys3D
call FreeNativeClover3D
xor eax,eax
ret
clp_test_fail:
mov eax,clpTestStage3D
add eax,860
ret
RunCloverTest3D ENDP

View file

@ -90,6 +90,8 @@ include tools\tests\world_regression.inc
include tools\tests\hall_furniture_test.inc
include tools\tests\wheel_scene_test.inc
include tools\tests\npc_life_test.inc
RunHallTest3D PROC uses ebx esi edi
local oldBank:DWORD,oldSeed:DWORD,endX:DWORD,endV:DWORD
mov eax, bankroll
@ -122,19 +124,12 @@ RunHallTest3D PROC uses ebx esi edi
call TestHallSurface3D
cmp eax, 1
jne hall_test_fail
; Sample the entire walking route; every accepted position must stay navigable.
; Visible people use the same personality positions as the new simulation.
mov patronClock3D, 0
mov deltaTime3D, 50
mov ebx, 400
hall_test_patron_route:
call UpdatePatrons3D
cmp ebx, 201
jne @F
cmp hallGuests3D.posX,22*FP_1
jne hall_test_fail
cmp hallGuests3D.posY,20*FP_1
jne hall_test_fail
@@:
invoke CanWalk3D,hallGuests3D.posX,hallGuests3D.posY
cmp eax, 1
jne hall_test_fail
@ -145,10 +140,6 @@ hall_test_patron_route:
jnz hall_test_patron_route
cmp patronClock3D, 0
jne hall_test_fail
cmp hallGuests3D.posX,18*FP_1
jne hall_test_fail
cmp hallGuests3D.posY,18*FP_1
jne hall_test_fail
invoke CanWalk3D,4*FP_1,27*FP_1
test eax, eax
jnz hall_test_fail
@ -297,7 +288,7 @@ hall_test_two_balls:
jb hall_test_two_balls
mov poolX3D, 100*FP_1
mov poolY3D, 115*FP_1
mov poolX3D[4], 105*FP_1+FP_1*4/5
mov poolX3D[4], 102*FP_1+FP_1*4/5
mov poolY3D[4], 115*FP_1
mov poolVX3D, FP_1
invoke PoolCollide3D,0,1
@ -324,7 +315,7 @@ hall_test_fast_contact:
cmp poolVX3D[4], 0
jle hall_test_fail
mov poolActive3D[4], 0
mov poolX3D, 33*FP_1
mov poolX3D, 31*FP_1
mov poolVX3D, -30000
call PoolStep3D
cmp poolVX3D, 0
@ -334,7 +325,7 @@ hall_test_fast_contact:
mov hallTestStage3D, 75
mov poolActive3D[4], 0
mov poolX3D, 160*FP_1
mov poolY3D, 52*FP_1
mov poolY3D, 51*FP_1+FP_1/2
invoke PoolPocketBall3D,0
cmp poolActive3D, 0
jne hall_test_fail
@ -342,8 +333,8 @@ hall_test_fast_contact:
jne hall_test_fail
; Near a middle pocket, the jaw must rebound an off-center approach.
mov poolActive3D, 1
mov poolX3D, 145*FP_1
mov poolY3D, 64*FP_1
mov poolX3D, 157*FP_1
mov poolY3D, 52*FP_1
mov poolVX3D, 0
mov poolVY3D, -20000
invoke PoolJawContact3D,0
@ -397,16 +388,15 @@ hall_test_friction:
mov poolRail3D, 1
mov poolActive3D[8*4], 0
mov poolPotted3D, 100h
mov pyrScore3D,7
call PoolFinishShot3D
cmp poolState3D, POOL_RESULT
jne hall_test_fail
cmp poolWinner3D, 1
cmp poolWinner3D, 0
jne hall_test_fail
call InitPool3D
mov poolGroups3D, 1
mov poolAtStart3D, 0
mov poolBreak3D, 0
mov poolFirst3D, 8
mov poolFirst3D, -1
mov poolRail3D, 1
mov poolActive3D[8*4], 0
mov poolPotted3D, 100h
@ -414,7 +404,12 @@ hall_test_friction:
mov poolCalledPocket3D, 0
mov poolPocket3D[8*4], 0
call PoolFinishShot3D
cmp poolWinner3D, 0
cmp poolState3D,POOL_PENALTY
jne hall_test_fail
cmp poolActive3D[8*4],1
jne hall_test_fail
invoke PyramidPenalty3D,1
cmp pyrScore3D[4],1
jne hall_test_fail
call InitPool3D
call RenderPool3D
@ -431,6 +426,12 @@ hall_test_friction:
call RunWorldRegression3D
test eax, eax
jz hall_test_fail
call RunPyramidTest3D
test eax,eax
jnz hall_test_fail
call RunNpcLifeTest3D
test eax,eax
jnz hall_test_fail
mov eax, oldSeed
cmp eax, rngSeed3D
jne hall_test_fail

View file

@ -0,0 +1,20 @@
.386
.model flat,stdcall
option casemap:none
include F:\MASM32\masm32\include\windows.inc
include F:\MASM32\masm32\include\kernel32.inc
InitPaths3D PROTO
LoadTextures3D PROTO
InitializeCasinoSprites3D PROTO
LoadHallAssets3D PROTO
RunNpcLifeTest3D PROTO
.code
RunNpcLifeProbe PROC
call InitPaths3D
call LoadTextures3D
call InitializeCasinoSprites3D
call LoadHallAssets3D
call RunNpcLifeTest3D
invoke ExitProcess,eax
RunNpcLifeProbe ENDP
END

View file

@ -0,0 +1,227 @@
.data
npcTestBase1 db "build\arrival\fixtures\npc-one.dat",0
npcTestBase2 db "build\arrival\fixtures\npc-two.dat",0
npcTestJournal1 db "build\arrival\fixtures\npc-one.dat.npc",0
npcTestJournal2 db "build\arrival\fixtures\npc-two.dat.npc",0
npcTestOpenShot db "build\actions\npc-dialog-open.bmp",0
npcTestReadyShot db "build\actions\npc-dialog-ready.bmp",0
npcTestDoneShot db "build\actions\npc-dialog-done.bmp",0
.code
RunNpcLifeTest3D PROC uses ebx esi edi
local oldBank:DWORD,steps:DWORD,identity:DWORD,firstX:DWORD,firstY:DWORD,claimBank:DWORD
mov eax,bankroll
mov oldBank,eax
mov hallTestStage3D,7901
call InitHall3D
xor esi,esi
npc_test_destinations:
invoke NpcPlanPath3D,0,esi
cmp eax,1
jne npc_test_fail
inc esi
cmp esi,NPC_DEST_COUNT
jb npc_test_destinations
mov esi,20
npc_test_rate:
call InitNpcLife3D
mov playerX,30*FP_1+FP_HALF
mov playerY,30*FP_1+FP_HALF
mov edi,1
npc_test_rate_clear_crowd:
imul ebx,edi,SIZEOF NpcLife3D
mov npcLife3D[ebx].state,NPC_AWAY
inc edi
cmp edi,NPC_LIFE_COUNT
jb npc_test_rate_clear_crowd
invoke NpcPlanPath3D,0,5
cmp eax,1
jne npc_test_fail
mov eax,2000
xor edx,edx
div esi
mov steps,eax
npc_test_rate_step:
invoke NpcMove3D,0,esi
dec steps
jnz npc_test_rate_step
mov eax,casinoNpcs3D.posX
mov edx,casinoNpcs3D.posY
cmp esi,20
jne npc_test_rate_compare
mov firstX,eax
mov firstY,edx
mov esi,50
jmp npc_test_rate
npc_test_rate_compare:
cmp eax,firstX
jne npc_test_fail
cmp edx,firstY
jne npc_test_fail
cmp esi,100
je npc_test_routes
mov esi,100
jmp npc_test_rate
npc_test_routes:
mov hallTestStage3D,7902
call InitNpcLife3D
mov deltaTime3D,50
mov steps,2400
npc_test_simulate:
call UpdateNpcLife3D
xor esi,esi
npc_test_person_bounds:
imul ebx,esi,SIZEOF NpcMindEntity
invoke CanWalk3D,casinoNpcs3D[ebx].posX,casinoNpcs3D[ebx].posY
cmp eax,1
jne npc_test_fail
imul edi,esi,SIZEOF NpcLife3D
cmp npcLife3D[edi].state,NPC_AWAY
ja npc_test_fail
cmp npcLife3D[edi].identity,NPC_IDENTITY_COUNT
jae npc_test_fail
cmp npcLife3D[edi].pathCount,NPC_PATH_CAP
ja npc_test_fail
inc esi
cmp esi,NPC_LIFE_COUNT
jb npc_test_person_bounds
dec steps
jnz npc_test_simulate
cmp npcLifeClock3D,120000
jne npc_test_fail
mov hallTestStage3D,7903
invoke DeleteFileA,OFFSET npcTestJournal1
invoke DeleteFileA,OFFSET npcTestJournal2
mov npcJournalBasePtr3D,OFFSET npcTestBase1
mov npcDateOverride3D,20261012
mov npcJournalLoaded3D,0
call NpcEnsureJournal3D
cmp eax,1
jne npc_test_fail
xor esi,esi
npc_test_find_any:
invoke NpcContractType3D,esi
test eax,eax
jz npc_test_accept
inc esi
cmp esi,NPC_IDENTITY_COUNT
jb npc_test_find_any
jmp npc_test_fail
npc_test_accept:
mov identity,esi
invoke NpcRecordRound3D,0,1
cmp npcProgress3D[esi*4],0
jne npc_test_fail
invoke NpcAcceptContract3D,esi
cmp eax,1
jne npc_test_fail
invoke NpcClaimContract3D,esi
test eax,eax
jnz npc_test_fail
; A real legacy graphical settlement contributes once, including repeat protection.
mov skulls3d_state,3
mov skulls3d_choice,0
mov skulls3d_ballPos,0
mov skulls3d_bet,50
call FinalizeSkullsGuess
cmp npcProgress3D[esi*4],1
jne npc_test_fail
mov eax,bankroll
mov claimBank,eax
call FinalizeSkullsGuess
mov eax,claimBank
cmp bankroll,eax
jne npc_test_fail
cmp npcProgress3D[esi*4],1
jne npc_test_fail
invoke NpcRecordRound3D,1,1
invoke NpcRecordRound3D,2,0
invoke NpcRecordRound3D,3,1
cmp npcProgress3D[esi*4],3
jne npc_test_fail
invoke NpcClaimContract3D,esi
cmp eax,1
jne npc_test_fail
mov eax,claimBank
add eax,150
cmp bankroll,eax
jne npc_test_fail
mov npcJournalLoaded3D,0
call NpcEnsureJournal3D
invoke NpcClaimContract3D,identity
test eax,eax
jnz npc_test_fail
mov hallTestStage3D,7904
mov npcJournalBasePtr3D,OFFSET npcTestBase2
call NpcEnsureJournal3D
cmp npcAccepted3D,0
jne npc_test_fail
cmp npcClaimed3D,0
jne npc_test_fail
call InitNpcLife3D
mov playerX,16*FP_1+FP_HALF
mov playerY,18*FP_1+FP_HALF
mov playerAngle,384
call UpdateCamera3D
mov interactTargetId,0
call CheckNpcInteractions3D
cmp interactTargetId,INTERACT_NPC
jne npc_test_fail
cmp npcSelected3D,0
jne npc_test_fail
call TriggerHallInteraction3D
cmp engineMode,MODE_NPC_DIALOG
jne npc_test_fail
call RenderNpcDialog3D
invoke WriteActionSnapshot3D,OFFSET npcTestOpenShot
mov mouseClick3D,1
mov mouseX3D,60
mov mouseY3D,175
call HandleNpcDialog3D
bt npcAccepted3D,0
jnc npc_test_fail
mov ebx,3
npc_test_finish_dialog:
xor esi,esi
npc_test_finish_games:
invoke NpcRecordRound3D,esi,1
inc esi
cmp esi,6
jb npc_test_finish_games
dec ebx
jnz npc_test_finish_dialog
call RenderNpcDialog3D
invoke WriteActionSnapshot3D,OFFSET npcTestReadyShot
mov mouseClick3D,1
mov mouseX3D,145
mov mouseY3D,175
call HandleNpcDialog3D
bt npcClaimed3D,0
jnc npc_test_fail
call RenderNpcDialog3D
invoke WriteActionSnapshot3D,OFFSET npcTestDoneShot
call CardTableEscape3D
cmp eax,1
jne npc_test_fail
cmp engineMode,MODE_WALK
jne npc_test_fail
mov npcDateOverride3D,20261013
call NpcEnsureJournal3D
cmp npcAccepted3D,0
jne npc_test_fail
cmp npcClaimed3D,0
jne npc_test_fail
mov npcAccepted3D,0
mov npcClaimed3D,0
mov npcJournalLoaded3D,0
mov npcDateOverride3D,0
mov npcJournalBasePtr3D,OFFSET npcJournalTestBase3D
mov deltaTime3D,16
mov eax,oldBank
mov bankroll,eax
call ClearKeys3D
xor eax,eax
ret
npc_test_fail:
mov eax,hallTestStage3D
ret
RunNpcLifeTest3D ENDP

Some files were not shown because too many files have changed in this diff Show more