Implement native slots, coin toss, European roulette and hall furniture

Add native assembly scenes and assets for Slots (777), Coin flip, and European Roulette; add 3D hall furniture packs and prompts; improve Russian UI strings in CP1251; update launcher hash and rebuild shipping binaries.
This commit is contained in:
Artvasil09 2026-10-10 14:43:30 +03:00
parent 3ad5bf4c9d
commit 4f8ae0893b
104 changed files with 5933 additions and 327 deletions

2
.gitignore vendored
View file

@ -14,9 +14,11 @@ __pycache__/
/build/actions/
/build/arrival/
/build/buckshot/
/build/coin/
/build/hall/
/build/startup/
/build/tables/
/build/wheel/
/build/*.res
/build/payload_*.zip
/build/PokerGameSetup_base.exe

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,20 @@
{
"table": [
640,
480,
1
],
"faces": [
128,
128,
2
],
"hands": [
224,
224,
16
],
"hand_anchor": "held coin (88,24); empty hand shared grid cell",
"held_frames": 5,
"alpha": "opaque table, binary cutouts"
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,100 @@
{
"card-table": {
"size": [
320,
208
],
"bounds": [
914,
407,
1240,
618
],
"frames": 1
},
"roulette-table": {
"size": [
288,
268
],
"bounds": [
0,
633,
309,
919
],
"frames": 1
},
"backroom-table": {
"size": [
304,
236
],
"bounds": [
314,
686,
612,
918
],
"frames": 1
},
"skill-cabinet": {
"size": [
224,
368
],
"bounds": [
210,
0,
635,
585
],
"frames": 1
},
"lounge-chair": {
"size": [
240,
256
],
"bounds": [
907,
42,
1410,
579
],
"frames": 1
},
"lamp-table": {
"size": [
192,
224
],
"bounds": [
241,
584,
627,
1024
],
"frames": 1
},
"velvet-rope": {
"size": [
288,
192
],
"bounds": [
860,
584,
1439,
1024
],
"frames": 1
},
"slot-cabinets": {
"size": [
224,
256
],
"frames": 3
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,21 @@
{
"symbols": [
"seven",
"cherries",
"lemon",
"bell",
"BAR",
"grapes",
"diamond",
"crown"
],
"cabinet": [
640,
480
],
"symbol": [
64,
64
],
"pipeline": "offline normalization only; gameplay and reel animation are MASM"
}

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,42 @@
{
"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
],
"zero_pockets": 1
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

255
assets/data/ui-ru.json Normal file
View file

@ -0,0 +1,255 @@
{
"coinTitle3D": "МОНЕТКА",
"coinHeadsText3D": "ОРЁЛ",
"coinTailsText3D": "РЕШКА",
"coinFlipText3D": "БРОСОК",
"coinAgainText3D": "ЕЩЁ РАЗ",
"coinExitText3D": "ВЫХОД",
"coinBankText3D": "БАНК $",
"coinStakeText3D": "СТАВКА",
"coinReturnText3D": "ВЫПЛАТА",
"coinLeavingText3D": "ВЫХОД ПОСЛЕ БРОСКА",
"coinTossText3D": "В ПОЛЁТЕ",
"coinLandingText3D": "ПРИЗЕМЛЕНИЕ",
"coinPrepareText3D": "БРОСАЕМ",
"coinMissingText3D": "НЕТ СПРАЙТОВ",
"coinLowBankText3D": "МАЛО ФИШЕК",
"wheelBetView3D": "СТАВКИ",
"wheelExitText3D": "ВЫХОД",
"wheelSpinningText3D": "СТАВКИ ЗАКРЫТЫ",
"wheelLeavingText3D": "ВЫХОД ПОСЛЕ ВРАЩЕНИЯ",
"wheelResultText3D": "ЧИСЛО",
"wheelMissingArt3D": "НЕТ СПРАЙТОВ РУЛЕТКИ",
"wheelRed3D": "КРАСН",
"wheelBlack3D": "ЧЁРН",
"wheelEven3D": "ЧЁТ",
"wheelOdd3D": "НЕЧЁТ",
"wheelColumn13D": "РЯД 1",
"wheelColumn23D": "РЯД 2",
"wheelColumn33D": "РЯД 3",
"wheelTitle3D": "ЕВРОПЕЙСКАЯ РУЛЕТКА",
"wheelSpin3D": "КРУТИТЬ",
"wheelClear3D": "СБРОС",
"wheelAmountText3D": "ФИШКА $",
"wheelStakedText3D": "СТАВКА $",
"wheelPaidText3D": "ВЫПЛАТА",
"textPause3D": "КАЗИНО / НАСТРОЙКИ",
"textResume3D": "ESC - ПРОДОЛЖИТЬ",
"textQuit3D": "Q - СОХРАНИТЬ И ВЫЙТИ",
"textDisplayFill3D": "1 ЭКРАН: ВЕСЬ МОНИТОР",
"textDisplayFit3D": "1 ЭКРАН: КЛАССИКА 4:3",
"textLowSpec3D": "2 РЕЖИМ",
"textSoundLevel3D": "ГРОМКОСТЬ",
"textVolumeKeys3D": "+ / - ГРОМКОСТЬ",
"textFullscreenKey3D": "F11 ПОЛНЫЙ ЭКРАН / ОКНО",
"textLowSpecOff3D": "ПОЛНЫЙ",
"textLowSpecEco3D": "ЭКО",
"textLowSpecQuiet3D": "ТИХИЙ",
"textMissing3D": "Не удалось загрузить assets/textures/casino3d-hd.bin. Папка assets должна находиться рядом с игрой.",
"textWalkKeys3D": "W/S: ХОД A/D: ПОВОРОТ E: ДЕЙСТВИЕ",
"textWindowKeys3D": "F11 ЭКРАН / ESC",
"textBank3D": "БАНК $",
"textTip3D": "ЧАЕВЫЕ $100",
"textUnavailable3D": "ПОКА НЕ РЕАЛИЗОВАНО",
"textSlots3D": "777 / АВТОМАТ",
"textSlotsKeys3D": "ПРОБЕЛ: ИГРА ВВЕРХ/ВНИЗ: СТАВКА",
"textBack3D": "ESC: НАЗАД В КАЗИНО",
"textBet3D": "СТАВКА $",
"textDealer3D": "ДИЛЕР",
"textPlayer3D": "ВАШИ КАРТЫ",
"textBJKeys3D": "H: КАРТА S: ХВАТИТ",
"textDeal3D": "ПРОБЕЛ: СДАТЬ ВВЕРХ/ВНИЗ: СТАВКА",
"textNext3D": "ПРОБЕЛ: СЛЕДУЮЩАЯ РАЗДАЧА",
"textRed3D": "1 КРАСНОЕ",
"textBlack3D": "2 ЧЁРНОЕ",
"textZero3D": "3 НОЛЬ",
"textEven3D": "4 ЧЁТ",
"textOdd3D": "5 НЕЧЁТ",
"textSpin3D": "ПРОБЕЛ: ИГРА ESC: ВЫХОД",
"textResult3D": "ЧИСЛО",
"textShop3D": "МАГАЗИН КАЗИНО",
"textItem1_3D": "1 ЗОЛОТАЯ РУБАШКА - $1000",
"textItem2_3D": "2 ИЗУМРУДНАЯ РУБАШКА - $1500",
"textItem3_3D": "3 БАГРОВАЯ РУБАШКА - $1500",
"textShopHint3D": "КУПИТЬ / ВЫБРАТЬ РУБАШКУ",
"textBought3D": "РУБАШКА ВЫБРАНА",
"textATM3D": "КАССА",
"textATMNote3D": "БАЛАНС СОХРАНЯЕТСЯ ПРИ ВЫХОДЕ",
"textATMNote2_3D": "ЛОКАЛЬНЫЙ ПРОФИЛЬ ИГРОКА",
"textNoCash3D": "НЕДОСТАТОЧНО ФИШЕК",
"textWin3D": "ВЫИГРЫШ",
"textLose3D": "ПРОИГРЫШ",
"textTie3D": "НИЧЬЯ",
"textBJ3D": "21 / БЛЭКДЖЕК",
"textTips3D": "СПАСИБО ЗА ЧАЕВЫЕ",
"entryWelcome3D": "ВХОД В КАЗИНО",
"entryChoose3D": "ВАШ ПРОФИЛЬ",
"entryGuest3D": "ГОСТЬ",
"entryNew3D": "+ НОВЫЙ ИГРОК",
"entryNameText3D": "ИМЯ ИГРОКА",
"entryConfirm3D": "ВОЙТИ",
"entryCancel3D": "ESC: НАЗАД",
"entryContinue3D": "ДОБРО ПОЖАЛОВАТЬ",
"entryProfileError3D": "СБОЙ ЗАГРУЗКИ",
"entryNameError3D": "ИМЯ ЗАНЯТО / СБОЙ",
"entryStop3D": "ОДНУ МИНУТУ.",
"entryChecking3D": "ПРОВЕРЯЕМ СПИСОК ГОСТЕЙ",
"entryReady3D": "ПРОХОДИТЕ.",
"entrySkip3D": "ENTER: ПОДОЙТИ",
"hallBarPrompt3D": "E: КОКТЕЙЛЬНЫЙ БАР",
"hallBunkerPrompt3D": "E: ПОДВАЛ / МАГАЗИН",
"hallUpPrompt3D": "E: НАВЕРХ",
"hallPoolPrompt3D": "E: БИЛЬЯРД",
"barName3D": "БАР СТАРОГО ГОРОДА",
"barDrinks3D": "1 ХАЙБОЛ $100",
"barDrinks23D": "2 КОКТЕЙЛЬ $200",
"barDrinks33D": "3 ВОДА $0",
"barPoor3D": "МАЛО ФИШЕК",
"barMixText3D": "СМЕШИВАЕМ",
"barDrinkText3D": "ВАШЕ ЗДОРОВЬЕ",
"merchantGrey3D": "СЕРЕБРО",
"merchantBlue3D": "САПФИРОВАЯ ВОЛНА",
"merchantRed3D": "БАГРОВЫЙ КРЕСТ",
"merchantGold3D": "ЗОЛОТОЕ СОЛНЦЕ",
"merchantSkull3D": "ЧЕРЕП И РОЗЫ",
"merchantClover3D": "КЛЕВЕР",
"merchantArcane3D": "АРКАНА",
"merchantNoir3D": "НУАР",
"merchantDiamond3D": "АЛМАЗ",
"merchantRider3D": "НАЕЗДНИК",
"merchantBee3D": "ПЧЕЛА",
"merchantFan3D": "ВЕЕР",
"merchantWeb3D": "ПАУТИНА",
"merchantTitle3D": "СИДОРОВИЧ",
"merchantStock3D": "РУБАШКИ",
"merchantBuy3D": "КУПИТЬ",
"merchantOwned3D": "ЕСТЬ",
"merchantEquipped3D": "НАДЕТО",
"merchantPoor3D": "МАЛО ФИШЕК",
"merchantShopKeys3D": "ВВЕРХ/ВНИЗ ENTER: КУПИТЬ ESC: ХОЛЛ",
"merchantHello3D": "ПОСМОТРИ, ЧТО ЕСТЬ.",
"merchantThanks3D": "ХОРОШИЙ ВЫБОР.",
"pkTitle3D": "ТЕХАССКИЙ ХОЛДЕМ",
"pkPotText3D": "БАНК",
"pkStreet0Text3D": "ПРЕФЛОП",
"pkStreet1Text3D": "ФЛОП",
"pkStreet2Text3D": "ТЁРН",
"pkStreet3Text3D": "РИВЕР",
"pkCheckText3D": "ПАС",
"pkCallText3D": "УРАВНЯТЬ",
"pkRaiseText3D": "ПОВЫСИТЬ",
"pkFoldText3D": "СБРОС",
"pkAllInText3D": "ВА-БАНК",
"pkCostText3D": "КОЛЛ",
"pkMinimumText3D": "МИН. РЕЙЗ",
"pkDealText3D": "СДАТЬ",
"pkNextText3D": "ДАЛЕЕ",
"pkAiWait3D": "ДИЛЕР",
"pkAiCheck3D": "ПАС",
"pkAiCall3D": "КОЛЛ",
"pkAiRaise3D": "РЕЙЗ",
"pkAiFold3D": "СБРОС",
"pkWinText3D": "ВЫ ВЫИГРАЛИ",
"pkLoseText3D": "ДИЛЕР ВЫИГРАЛ",
"pkTieText3D": "БАНК ПОРОВНУ",
"pkRank0": "СТАРШАЯ КАРТА",
"pkRank1": "ПАРА",
"pkRank2": "ДВЕ ПАРЫ",
"pkRank3": "ТРОЙКА",
"pkRank4": "СТРИТ",
"pkRank5": "ФЛЕШ",
"pkRank6": "ФУЛЛ-ХАУС",
"pkRank7": "КАРЕ",
"pkRank8": "СТРИТ-ФЛЕШ",
"cbTitle3D": "БЛЭКДЖЕК / 21",
"cbHitText3D": "КАРТА",
"cbStandText3D": "ХВАТИТ",
"cbDoubleText3D": "УДВОИТЬ",
"cbNaturalText3D": "БЛЭКДЖЕК",
"tableBlindText3D": "БЛАЙНД $",
"tableRaiseText3D": "РЕЙЗ +$",
"tableScoreText3D": "ОЧКИ",
"bs3d_textTitle": "БАКШОТ",
"bs3d_textBank": "БАНК $",
"bs3d_textPlayer": "ИГРОК",
"bs3d_textDealer": "ДИЛЕР",
"bs3d_textBet": "СТАВКА $",
"bs3d_textReady": "ПРОБЕЛ: ИГРАТЬ",
"bs3d_textResult": "ПРОБЕЛ: ЕЩЁ РАЗ",
"bs3d_textExit": "ESC: ХОЛЛ",
"bs3d_textPause": "ESC: ПАУЗА",
"bs3d_textChoose": "1: В СЕБЯ",
"bs3d_textOpponent": "2: В ДИЛЕРА",
"bs3d_textWin": "ВЫ ВЫИГРАЛИ",
"bs3d_textLose": "ДИЛЕР ВЫИГРАЛ",
"bs3d_textLoad": "ПЕРЕЗАРЯДКА",
"bs3d_textLive": "БОЕВЫЕ",
"bs3d_textBlank": "ХОЛОСТЫЕ",
"bs3d_textShells": "ПАТРОНЫ",
"bs3d_textEmpty": "МАЛО ДЕНЕГ",
"bs3d_textShot": "БОЕВОЙ ПАТРОН",
"bs3d_textDry": "ХОЛОСТОЙ ПАТРОН",
"bs3d_textKnownLive": "СЛЕДУЮЩИЙ: БОЕВОЙ",
"bs3d_textKnownBlank": "СЛЕДУЮЩИЙ: ХОЛОСТОЙ",
"bs3d_textSaw": "УРОН СЛЕДУЮЩЕГО ВЫСТРЕЛА x2",
"bs3d_textBeer": "ПАТРОН ВЫБРОШЕН",
"bs3d_textCuffs": "ПРОТИВНИК ПРОПУСКАЕТ ХОД",
"bs3d_textItemEmpty": "ПРЕДМЕТ НЕДОСТУПЕН",
"bs3d_textWait": "ХОД ДИЛЕРА",
"bs3d_textItem": "ИСПОЛЬЗУЕМ ПРЕДМЕТ",
"bs3d_textAssets": "АНИМАЦИЯ НЕ НАЙДЕНА ИЛИ ПОВРЕЖДЕНА",
"bs3d_textMedical": "МЕДИЦИНСКАЯ МАШИНА: РЕАНИМАЦИЯ",
"bs3d_textDeath": "ЖИЗНЕННЫЙ СИГНАЛ ПОТЕРЯН",
"bs3d_textGlassKey": "3: ЛУПА",
"bs3d_textSawKey": "4: ПИЛА",
"bs3d_textBeerKey": "5: ПИВО",
"bs3d_textCuffKey": "6:ОКОВЫ",
"poolTitle3D": "ПУЛ / 8",
"poolPlayerText3D": "ИГРОК",
"poolDealerText3D": "ДИЛЕР",
"poolOpenText3D": "СТОЛ ОТКРЫТ",
"poolSolidsText3D": "СПЛОШНЫЕ",
"poolStripesText3D": "ПОЛОСАТЫЕ",
"poolFoulText3D": "ФОЛ / С РУКИ",
"poolWinText3D": "ПОБЕДА",
"poolShotText3D": "УДАР",
"poolRackText3D": "ЗАНОВО",
"poolPowerText3D": "СИЛА",
"poolPlaceText3D": "ПОСТАВЬТЕ БИТОК",
"poolMovingText3D": "КАТЯТСЯ",
"poolBallText3D": "ШАР",
"poolPocketText3D": "ЛУЗА",
"startupEnter3D": "ENTER",
"promptPoker": "[E] ПОКЕР (ТЕХАССКИЙ ХОЛДЕМ)",
"promptDice": "[E] ИГРА В КОСТИ",
"promptBuckshot": "[E] БАКШОТ РУЛЕТКА",
"textDiceTitle": "ИГРА В КОСТИ",
"textSkillTitle": "=== ПРОВЕРКА НАВЫКА ===",
"textSkillGreatMsg": "ОТЛИЧНО! +БОНУС!",
"strPush": "НИЧЬЯ",
"sz3DWindowTitle": "Ассемблер Казино - 2.5D",
"szTitleBlackjack": "КАЗИК 3D - БЛЭКДЖЕК (21)",
"szTitlePoker": "КАЗИК 3D - ПОКЕР (ТЕХАССКИЙ ХОЛДЕМ)",
"szTitleDurak": "КАЗИК 3D - ПЕРЕВОДНОЙ ДУРАК",
"szTitleCasinoRoulette": "КАЗИК 3D - ЕВРОПЕЙСКАЯ РУЛЕТКА",
"szTitleClassicSlot": "КАЗИК 3D - АВТОМАТ 777",
"szTitleCloverSlot": "КАЗИК 3D - КЛЕВЕРНАЯ ЯМА (5x3)",
"szTitleDice": "КАЗИК 3D - ИГРА В КОСТИ",
"szTitleSkulls": "КАЗИК 3D - НАПЁРСТКИ",
"szTitleCoin": "КАЗИК 3D - МОНЕТКА (ОРЁЛ/РЕШКА)",
"szTitleSkillCheck": "КАЗИК 3D - ПРОВЕРКА НАВЫКА",
"szTitleBuckshot": "КАЗИК 3D - БАКШОТ РУЛЕТКА",
"szTitleRussianRoulette": "КАЗИК 3D - РУССКАЯ РУЛЕТКА",
"szTitleOnlineRR": "КАЗИК 3D - СЕТЕВАЯ РУССКАЯ РУЛЕТКА",
"szTitleUmamusume": "КАЗИК 3D - ГОНКИ УМАМУСУМЕ",
"szTitleHellTower": "КАЗИК 3D - АДСКАЯ БАШНЯ",
"szTitleShop": "КАЗИК 3D - КАРТЫ И ТАЛИСМАНЫ",
"szTitleATM": "КАЗИК 3D - БАНКОМАТ",
"szTitleTerminal": "КАЗИК 3D - ЛОКАЛЬНЫЕ ПРОФИЛИ",
"szTitleObserver": "КАЗИК 3D - НАБЛЮДАТЕЛЬ (ИИ)",
"symBell": " ЗВОН",
"symCherry": "ВИШНЯ",
"symClover": "КЛЕВЕР",
"symGem": "АЛМАЗ",
"symCrown": "КОРОНА"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 847 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View file

@ -0,0 +1,6 @@
{
"tool": "image_gen",
"table": "Production background for a playable first-person coin toss casino mini-game, 4:3 landscape 640x480 composition. Detailed hand-crafted late-1990s pixel art, sharp pixels, rich textured surfaces, not a photograph and not low-poly. Match a burgundy velvet, green felt, brass and dark carved wood retro casino. POV seated player looking down at a small green felt gaming table with thick polished wood rim. The lower 70% is broad unobstructed felt, especially the center at 50% width 65% height for a dynamically tossed coin. Across the table in upper third is a dignified male dealer in white shirt and burgundy waistcoat, head fully visible with comfortable margin at least 12% image height from top, his hands BELOW the table out of sight. Background wall panels and warm lamps, distant casino chairs, coherent perspective, no crowd. Few chip stacks at left and right corners, not in center. No coin, no player's hands, no baked shadows in central play area, no lettering, no digits, no markings, no interface, no logos. This is game environment art, not an illustration in a frame.",
"faces": "A production sprite sheet on genuinely transparent background for a detailed late-1990s pixel art casino coin toss game. Exactly TWO separate gold coin faces, arranged left-to-right in one horizontal row with wide transparent gap and generous transparent margins. Orthographic straight-on circular views of the SAME antique solid gold casino token, embossed detailed relief, thick finely reeded edge, high contrast dark recesses and restrained polished highlights. LEFT coin has a crowned eagle relief (HEADS). RIGHT coin has a large embossed five-point star in a wreath (TAILS). No lettering, no digits, no table, no hands, no environment, no drop shadows outside circular boundary. Pixel-art material texture and clearly distinguishable symbols, NOT photorealism, NOT emoji or flat icon. Both coins exactly same diameter, palette, lighting from upper left, centered at same height, complete uncropped silhouettes; perfect circles in frontal view. Do not add extra coins or tiny examples.",
"hands": "Production sprite sheet, genuinely transparent background, exactly 8 poses in a regular 4-column by 2-row grid, wide transparent gutters between every cell. A first-person PLAYER'S RIGHT HAND emerging from bottom right, detailed late-1990s textured pixel art, bare light skin and black leather coat cuff, matching a retro 2.5D casino game. Every cell contains ONLY one complete wrist/cuff and hand, same hand anatomy, same scale and same lighting from upper left, no environment, no text, no grid lines, no second hand. Sequence read left-to-right then next row: pose 1 fingers curled and THUMB curled below an imaginary coin; pose 2 thumb cocked against index finger; pose 3 thumb beginning upward flick; pose 4 thumb extended after releasing imaginary coin; pose 5 fingers open in follow-through; pose 6 fingers relaxed as hand draws back; pose 7 relaxed hand half withdrawn downward; pose 8 relaxed hand withdrawn pose. No actual coin in any pose because it is rendered separately. No knife, no cigarette, no gun, no gloves, no sleeves other than dark cuff. Maintain a common wrist anchor at same place within each cell and natural palm facing upper-left toward center of screen. Hand should fit the bottom-right corner of a first-person screen, not point at the viewer. Complete cutouts, no clipping by cell boundaries. Smooth progression of real distinct poses."
}

View file

@ -0,0 +1,6 @@
{
"tool": "image_gen",
"prompt": "Use case: precise-object-edit / game sprite animation. Input image is a style and character-hand reference, NOT a layout to copy. Create a NEW production sprite sheet with exactly SIXTEEN equal square cells in a 4 columns by 4 rows grid, left-to-right top-to-bottom chronological order, true transparent background and no separators, labels, lettering or shadows on the empty background. Same first-person RIGHT human hand, natural anatomy, five fingers, black leather jacket cuff, warm light from upper left, detailed late-1990s pixel-art look matching the reference. Same scale, camera and wrist origin in EVERY cell; wrist cropped at lower-right corner, all fingers fully inside cell with ample clear alpha margin. Animate a physically convincing coin toss with the THUMB, not a pinch-and-drop. Frames 0-5: a small gold coin (diameter about one thumbnail width) is drawn AS PART OF THE SPRITE resting FLAT DIRECTLY ON THE THUMBNAIL, supported by the thumb while the other fingers curl; coin is horizontal and its edge visible, no gap between thumbnail and coin, no coin poking through fingers. Six VERY SMALL intermediate changes: thumb bends down/cocks, then springs upward; same wrist, same coin contact, no teleport. Frames 6-8: thumb finishes snapping upward, the coin has LEFT THE HAND and MUST NOT APPEAR anywhere in these or any later cells; fingers open only slightly, NOT a waving open palm. Frames 9-15: empty hand relaxes and smoothly withdraws down toward the lower-right edge in seven subtly different intermediate poses. NO airborne coins drawn anywhere. First six coins neutral embossed gold, no face/letter readability needed. Keep reference skin and leather texture but remove the red halo around the silhouette. Clean alpha boundary, no red outlines, no second hands, no extra limbs. This will be composited over a green casino table; the coin in flight is rendered separately by the assembly engine.",
"reference": "assets/sprites/coin/hands-v1.png",
"output": "assets/sprites/coin/hands-v2.png"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

View file

@ -0,0 +1,4 @@
{
"tool": "image_gen",
"prompt": "Create a production sprite sheet for a detailed retro 2.5D first-person casino game, all four objects fully isolated on true transparent background, a strict 2 columns by 2 rows grid with very generous transparent gutters. Matching late-1990s richly textured pixel-art sprites, NOT photorealistic or smooth vector. Burgundy leather, aged brass, dark wood, restrained emerald. Front three-quarter view, camera at standing human eye height, complete floor-to-top objects, crisp pixels. TOP LEFT: a tall industrial skill-check arcade cabinet, complete curved upper housing with a circular illuminated green timing dial, mechanical hook emblem, orange-red light column, physical button control pedestal and wide stable metal base; all upper and lower parts must be present, no blood, no person. TOP RIGHT: an elegant high-backed burgundy leather armchair with carved dark wooden legs, complete chair. BOTTOM LEFT: a small round dark wooden side table with one lit brass shaded table lamp and a whiskey tumbler, complete table legs. BOTTOM RIGHT: two brass stanchions joined by a burgundy velvet rope, complete circular floor bases. Every object separate and complete, never crosses its cell; NO other objects, NO human limbs, NO lettering, NO background floor or room, no cast shadow outside sprite."
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

View file

@ -0,0 +1,5 @@
{
"tool": "built-in image_gen",
"cabinet": "Use case: stylized-concept. Asset type: finished 4:3 first-person casino game environment background, native 640x480 retro software-rendered game. Detailed cohesive late-1990s pixel art, deliberately drawn clusters, crisp edges and textured shaded metal, NOT photorealistic, NOT blurry, no UI mockup. Straight-on close view of one vintage mechanical slot machine, centered, complete cabinet with dark red enamel, aged brass trim, chrome, amber bulbs, black ribbed sides, a chunky lever on the right and coin tray below. In a richly furnished casino, other machines and red upholstered chairs only at far margins. Top marquee has exactly \"777\". Three tall EMPTY ivory reel windows side by side dominate center, reserved for moving symbols rendered by the game, absolutely NO reel symbols inside. Dark inset blank plate below reels for a digital counter, red mechanical round spin button underneath. Layout on a 640x480 target: machine fills x=78..562,y=38..452; three empty windows jointly x=148..492,y=146..298; there is space above machine y<38 for HUD and below y>442 for game buttons, coin tray visible lower down. No hands, no people, no additional labels or text. Single coherent depth, frontal reels without perspective skew, premium legible game art matching wooden/red/green pixel-art casino interiors.",
"symbols": "Use case: stylized-concept. Asset type: production transparent game sprite atlas, exactly eight icons in a strict equal 4-column by 2-row grid, square image 1024x1024, each cell256x512? No: canvas should be landscape 2048x1024 with eight square512x512 cells, four across and two down. Transparent background, no borders, no labels, no shadows outside icon silhouettes, no text except BAR. Each icon centered in its cell with generous identical safe padding, consistent large scale about300 pixels. Row1 left to right: a single ornate RED SEVEN number7 outlined in gold; two glossy red cherries on green stems; one bright yellow lemon with a green leaf; a polished golden bell. Row2 left to right: a traditional black rectangular casino BAR plaque with exactly \"BAR\" in warm white; one bunch of purple grapes with leaf; a faceted cyan diamond jewel; a gold royal crown with ruby insets. Late-1990s richly detailed retro pixel art, deliberate crisp pixel clusters, shaded jewel/enamel surfaces matching vintage slot-machine reel printing, not vector art, not smooth modern illustrations, not photorealistic. Eight isolated symbols only, equal framing, nothing overlaps cell boundaries, no perspective background. These are reel sprites for a dark-red/brass mechanical slot cabinet."
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

View file

@ -0,0 +1,5 @@
{
"tool": "image_gen",
"wide": "Generate a production 640x480 aspect-ratio opaque background for a first-person European roulette scene in a richly detailed late-1990s pixel-art casino, matching burgundy leather, green felt, aged brass, polished dark wood interior. POV a player seated at roulette table. Composition precise: left half/top part contains an EMPTY oval roulette wooden bowl, center at 31% image width and 27% image height, outer size 53% image width and 34% image height; no rotating rotor and NO numbers in the empty bowl, those will be rendered dynamically by game code. A formally dressed male croupier stands across the table in the upper RIGHT quarter, his head and upper torso visible above the tabletop, one open hand inviting bets, no playing cards. Green felt table fills lower half and will be covered by native betting UI. Several stacks of red/white/blue chips near outer corners, NOT inside the bowl. Background soft-focus burgundy wall panels, warm brass wall lights and rail. Crisp hand-crafted textured pixel art, coherent perspective, NOT photograph, NOT low-poly, no lettering, no UI, no overlay, no logos, no extra hands, no static ball, NO painted betting numbers or fake roulette wheel markings.",
"closeup": "Edit this roulette background into a second camera angle of the SAME table and SAME casino, used during a spin. Preserve the exact detailed retro pixel-art style, colors, male croupier identity and wooden bowl materials. Bring the camera smoothly closer and angle down at the wheel. Empty inner wheel bowl is centered at 50% image width and 57% image height, its INNER rim ellipse covers from 12% to 88% image width horizontally, 30% to 86% image height vertically. Entire oval wheel housing fits within the image, not cropped, large across the center. Croupier is in the upper right BACKGROUND, much smaller and behind the bowl, not in front of it. Leave the bowl completely empty so exact numbered rotating rotor and ball can be drawn by code. No roulette numerals, no letters, no ball, no interface, no playing cards, no translucent overlay. Green felt surrounds the wooden housing; warm lamps and burgundy casino interior in the background."
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

View file

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

Binary file not shown.

View file

@ -151,6 +151,8 @@ include src_graphics\billiards_data.inc
include src_graphics\buckshot_data.inc
include src_graphics\russian_view_data.inc
include src_graphics\unavailable_data.inc
include src_graphics\native_slots_data.inc
include src_graphics\native_coin_data.inc
.data
ALIGN 4
@ -164,7 +166,7 @@ msg3D C3D_MSG <>
wc3D C3D_WNDCLASSEX <>
sz3DClassName db "Casino3DRetroClass",0
sz3DWindowTitle db "Assembler Casino - 2.5D",0
sz3DWindowTitle db 192,241,241,229,236,225,235,229,240,32,202,224,231,232,237,238,32,45,32,50,46,53,68,0
szTexturesRelPath db "assets\textures\casino3d-hd.bin",0
szTexturesFullPath db 260 dup(0)
@ -186,16 +188,16 @@ flashMessage db 128 dup(0)
promptDancer db "[E] ÊÀÁÀÐÅ - ×ÀÅÂÛÅ $100",0
promptBlackjack db "[E] ÁËÝÊÄÆÅÊ (21)",0
promptPoker db "[E] ÏÎÊÅÐ (TEXAS HOLD'EM)",0
promptPoker db 91,69,93,32,207,206,202,197,208,32,40,210,197,213,192,209,209,202,200,201,32,213,206,203,196,197,204,41,0
promptDurak db "[E] ÊÀÐÒÛ: ÏÎÄÊÈÄÍÎÉ ÄÓÐÀÊ",0
promptCasinoRoulette db "[E] ÅÂÐÎÏÅÉÑÊÀß ÐÓËÅÒÊÀ",0
promptClassicSlot db "[E] ÊËÀÑÑÈ×ÅÑÊÈÅ ÑËÎÒÛ 777",0
promptCloverSlot db "[E] ÑËÎÒÛ CLOVERPIT (5x3)",0
promptDice db "[E] ÈÃÐÀ Â ÊÎÑÒÈ (CRAPS)",0
promptDice db 91,69,93,32,200,195,208,192,32,194,32,202,206,209,210,200,0
promptSkulls db "[E] ÍÀϨÐÑÒÊÈ (×ÅÐÅÏÀ)",0
promptCoin db "[E] ÌÎÍÅÒÊÀ (ÎШË/ÐÅØÊÀ)",0
promptSkillCheck db "[E] ÒÐÅÍÈÐÎÂÊÀ ÐÅÀÊÖÈÈ",0
promptBuckshot db "[E] BUCKSHOT ROULETTE",0
promptBuckshot db 91,69,93,32,193,192,202,216,206,210,32,208,211,203,197,210,202,192,0
promptRussianRoulette db "[E] ÐÓÑÑÊÀß ÐÓËÅÒÊÀ (VIP)",0
promptOnlineRR db "[E] ÑÅÒÅÂÀß ÐÓËÅÒÊÀ (NET)",0
promptUmamusume db "[E] ÑÊÀ×ÊÈ ÓÌÀÌÓÑÓÌÝ",0
@ -205,25 +207,27 @@ promptATM db "[E]
promptPlayerTerminal db "[E] ÒÅÐÌÈÍÀË: ÑÒÀÒÛ È À×ÈÂÊÈ",0
promptObserver db "[E] ÐÅÆÈÌ ÍÀÁËÞÄÀÒÅËß (ÈÈ)",0
szTitleBlackjack db "[KAZIK 3D CASINO] - ÁËÝÊÄÆÅÊ (21)",0
szTitlePoker db "[KAZIK 3D CASINO] - ÏÎÊÅÐ (TEXAS HOLD'EM)",0
szTitleDurak db "[KAZIK 3D CASINO] - ÏÎÄÊÈÄÍÎÉ ÄÓÐÀÊ",0
szTitleCasinoRoulette db "[KAZIK 3D CASINO] - ÅÂÐÎÏÅÉÑÊÀß ÐÓËÅÒÊÀ",0
szTitleClassicSlot db "[KAZIK 3D CASINO] - ÊËÀÑÑÈ×ÅÑÊÈÅ ÑËÎÒÛ 777",0
szTitleCloverSlot db "[KAZIK 3D CASINO] - ÑËÎÒ-ÌÀØÈÍÀ CLOVERPIT (5x3)",0
szTitleDice db "[KAZIK 3D CASINO] - ÈÃÐÀ Â ÊÎÑÒÈ (CRAPS)",0
szTitleSkulls db "[KAZIK 3D CASINO] - ÍÀϨÐÑÒÊÈ",0
szTitleCoin db "[KAZIK 3D CASINO] - ÌÎÍÅÒÊÀ (ÎШË/ÐÅØÊÀ)",0
szTitleSkillCheck db "[KAZIK 3D CASINO] - ÏÐÎÂÅÐÊÀ ÐÅÀÊÖÈÈ",0
szTitleBuckshot db "[KAZIK 3D CASINO] - BUCKSHOT ROULETTE",0
szTitleRussianRoulette db "[KAZIK 3D CASINO] - ÐÓÑÑÊÀß ÐÓËÅÒÊÀ",0
szTitleOnlineRR db "[KAZIK 3D CASINO] - ÑÅÒÅÂÀß ÐÓÑÑÊÀß ÐÓËÅÒÊÀ",0
szTitleUmamusume db "[KAZIK 3D CASINO] - ÑÊÀ×ÊÈ ÓÌÀÌÓÑÓÌÝ",0
szTitleHellTower db "[KAZIK 3D CASINO] - ÁÀØÍß ÀÄÀ (ÒÓÐÍÈÐ)",0
szTitleShop db "[KAZIK 3D CASINO] - ÁÓÒÈÊ È ÒÀËÈÑÌÀÍÛ",0
szTitleATM db "[KAZIK 3D CASINO] - ÁÀÍÊÎÌÀÒ",0
szTitleTerminal db "[KAZIK 3D CASINO] - ÒÅÐÌÈÍÀË ÈÃÐÎÊÀ",0
szTitleObserver db "[KAZIK 3D CASINO] - ÐÅÆÈÌ ÍÀÁËÞÄÀÒÅËß (ÈÈ)",0
szTitleBlackjack db 202,192,199,200,202,32,51,68,32,45,32,193,203,221,202,196,198,197,202,32,40,50,49,41,0
szTitlePoker db 202,192,199,200,202,32,51,68,32,45,32,207,206,202,197,208,32,40,210,197,213,192,209,209,202,200,201,32,213,206,203,196
db 197,204,41,0
szTitleDurak db 202,192,199,200,202,32,51,68,32,45,32,207,197,208,197,194,206,196,205,206,201,32,196,211,208,192,202,0
szTitleCasinoRoulette db 202,192,199,200,202,32,51,68,32,45,32,197,194,208,206,207,197,201,209,202,192,223,32,208,211,203,197,210,202,192,0
szTitleClassicSlot db 202,192,199,200,202,32,51,68,32,45,32,192,194,210,206,204,192,210,32,55,55,55,0
szTitleCloverSlot db 202,192,199,200,202,32,51,68,32,45,32,202,203,197,194,197,208,205,192,223,32,223,204,192,32,40,53,120,51,41,0
szTitleDice db 202,192,199,200,202,32,51,68,32,45,32,200,195,208,192,32,194,32,202,206,209,210,200,0
szTitleSkulls db 202,192,199,200,202,32,51,68,32,45,32,205,192,207,168,208,209,210,202,200,0
szTitleCoin db 202,192,199,200,202,32,51,68,32,45,32,204,206,205,197,210,202,192,32,40,206,208,168,203,47,208,197,216,202,192,41,0
szTitleSkillCheck db 202,192,199,200,202,32,51,68,32,45,32,207,208,206,194,197,208,202,192,32,205,192,194,219,202,192,0
szTitleBuckshot db 202,192,199,200,202,32,51,68,32,45,32,193,192,202,216,206,210,32,208,211,203,197,210,202,192,0
szTitleRussianRoulette db 202,192,199,200,202,32,51,68,32,45,32,208,211,209,209,202,192,223,32,208,211,203,197,210,202,192,0
szTitleOnlineRR db 202,192,199,200,202,32,51,68,32,45,32,209,197,210,197,194,192,223,32,208,211,209,209,202,192,223,32,208,211,203,197,210
db 202,192,0
szTitleUmamusume db 202,192,199,200,202,32,51,68,32,45,32,195,206,205,202,200,32,211,204,192,204,211,209,211,204,197,0
szTitleHellTower db 202,192,199,200,202,32,51,68,32,45,32,192,196,209,202,192,223,32,193,192,216,205,223,0
szTitleShop db 202,192,199,200,202,32,51,68,32,45,32,202,192,208,210,219,32,200,32,210,192,203,200,209,204,192,205,219,0
szTitleATM db 202,192,199,200,202,32,51,68,32,45,32,193,192,205,202,206,204,192,210,0
szTitleTerminal db 202,192,199,200,202,32,51,68,32,45,32,203,206,202,192,203,220,205,219,197,32,207,208,206,212,200,203,200,0
szTitleObserver db 202,192,199,200,202,32,51,68,32,45,32,205,192,193,203,222,196,192,210,197,203,220,32,40,200,200,41,0
textCabinetFooter db "[ ÂÛÕÎÄ: ÂÂÅÄÈÒÅ 0 / ÂÛÁÅÐÈÒÅ 'N' ÄËß ÂÎÇÂÐÀÒÀ Â ÇÀË ]",0
msgDancerTipped db "Âû áðîñèëè $100 òàíöîâùèöå! ÓÄÀ×À ÀÊÒÈÂÈÐÎÂÀÍÀ!",0
@ -328,7 +332,7 @@ dice3d_val2 dd 6
dice3d_sum dd 10
dice3d_win dd 0
textDiceTitle db "GRAND CRAPS",0
textDiceTitle db 200,195,208,192,32,194,32,202,206,209,210,200,0
textDiceEven db "[1] רÒ",0
textDiceOdd db "[2] ÍÅ×ÅÒ",0
textDiceEvenNums db "2 4 6 ...",0
@ -390,13 +394,13 @@ skill3d_misses dd 0
skill3d_last_result dd 0
skill3d_win_amount dd 0
textSkillTitle db "=== S K I L L C H E C K ===",0
textSkillTitle db 61,61,61,32,207,208,206,194,197,208,202,192,32,205,192,194,219,202,192,32,61,61,61,0
textSkillHitsHdr db "ÕÈÒ: ",0
textSkillMissHdr db "ÏÐÎÌÀÕ: ",0
textSkillReadyMsg db "ÍÀÆÌÈÒÅ [ÏÐÎÁÅË] ÄËß ÑÒÀÐÒÀ",0
textSkillPrompt db "ÍÀÆÌÈÒÅ [ÏÐÎÁÅË] Â ÇÎÍÅ!",0
textSkillHitMsg db "ÕÈÒ! ÒÎ×ÍÎÅ ÏÎÏÀÄÀÍÈÅ!",0
textSkillGreatMsg db "ÎÒËÈ×ÍÎ! GREAT SKILL CHECK! +ÁÎÍÓÑ!",0
textSkillGreatMsg db 206,210,203,200,215,205,206,33,32,43,193,206,205,211,209,33,0
textSkillMissMsg db "ÏÐÎÌÀÕ! ÒÐÅÂÎÃÀ!",0
textSkillEscape db "ÏÎÁÅÃ ÓÑÏÅØÅÍ! ÂÛÈÃÐÛØ: +$",0
textSkillLost db "ÂÛ ÏÎÉÌÀÍÛ! ÏÐÎÈÃÐÛØ ÑÒÀÂÊÈ!",0
@ -415,17 +419,17 @@ atmMsg db 128 dup(0)
strWin db "ÏÎÁÅÄÀ! +$",0
strLose db "ÏÐÎÈÃÐÛØ",0
strJackpot db "ÄÆÅÊÏÎÒ! +$",0
strPush db "ÍÈ×Üß (PUSH)",0
strPush db 205,200,215,220,223,0
strDealerBust db "ÄÈËÅÐ ÏÅÐÅÁÐÀË! ÏÎÁÅÄÀ! +$",0
strPlayerBust db "ÏÅÐÅÁÎÐ (>21)! ÏÐÎÈÃÐÛØ",0
sym7 db " 777 ",0
symBar db " BAR ",0
symBell db " BELL",0
symCherry db "CHERRY",0
symClover db "CLOVER",0
symGem db " GEM ",0
symCrown db "CROWN",0
symBell db 32,199,194,206,205,0
symCherry db 194,200,216,205,223,0
symClover db 202,203,197,194,197,208,0
symGem db 192,203,204,192,199,0
symCrown db 202,206,208,206,205,192,0
cardRankStr db "A ",0,"2 ",0,"3 ",0,"4 ",0,"5 ",0,"6 ",0,"7 ",0,"8 ",0,"9 ",0,"10",0,"J ",0,"Q ",0,"K ",0
@ -2647,14 +2651,15 @@ DrawDetailedCoin ENDP
; IN-ENGINE 2.5D MINI-GAME: COIN FLIP
; ==============================================================================
InitCoinGame PROC
mov coin3d_state, 0
mov coin3d_timer, 0
mov coin3d_streak, 0
mov coin3d_win, 0
call InitNativeCoin3D
ret
InitCoinGame ENDP
HandleCoinInput PROC
call HandleNativeCoin3D
ret
; Retained legacy frame-count prototype, excluded from the executable.
IF 0
cmp coin3d_state, 0
je coin_handle_idle
cmp coin3d_state, 1
@ -2775,6 +2780,7 @@ coin_handle_result:
invoke PlayCasinoSound3D, OFFSET soundClick, 0, 1
coin_done:
ret
ENDIF
HandleCoinInput ENDP
RenderCoinGame PROC uses ebx esi edi
@ -2782,6 +2788,9 @@ RenderCoinGame PROC uses ebx esi edi
local rxVal:DWORD
local kx:DWORD
call RenderNativeCoin3D
ret
IF 0
; Background & Cabinet border
invoke DrawBox, 0, 0, 320, 240, 00140906h
invoke DrawBoxBorder, 6, 6, 308, 228, 0022150Fh, 00482E1Bh, 3
@ -2894,6 +2903,7 @@ coin_render_keys:
invoke DrawKeycap3D, kx, 192, OFFSET textKeyEsc, 0
ret
ENDIF
RenderCoinGame ENDP
; ==============================================================================
@ -4693,7 +4703,10 @@ check_target_durak:
check_target_slots777:
cmp eax, INTERACT_SLOTS_777
jne check_target_slots_clover
call BeginUnavailable3D
call InitNativeSlots3D
test eax,eax
jz target_unported
mov engineMode,MODE_SLOTS
ret
check_target_slots_clover:
@ -5100,6 +5113,8 @@ include src_graphics\card_tables_render.inc
include src_graphics\durak_render.inc
include src_graphics\card_table_input.inc
include src_graphics\scene_animation.inc
include src_graphics\native_slots.inc
include src_graphics\native_coin.inc
include src_graphics\graphic_profiles.inc
include src_graphics\city_arrival_scene.inc
include src_graphics\arrival_scene.inc
@ -5246,8 +5261,10 @@ check_slots_mode:
@@:
cmp engineMode, MODE_SLOTS
jne check_bj_mode
call HandleSlotsInput
call RenderSlotsGame
call HandleNativeSlots3D
cmp engineMode,MODE_WALK
je check_walk_mode
call RenderNativeSlots3D
jmp present_frame
check_bj_mode:
cmp engineMode, MODE_BLACKJACK
@ -5355,7 +5372,7 @@ pause_draw_quality_value:
invoke DrawString8x8, 44, 112, OFFSET textVolumeKeys3D, 00D8BC6Ah
invoke DrawString8x8, 44, 130, OFFSET textSoundLevel3D, 00BBBBBBh
invoke FormatNumber, settingsVolume, OFFSET hudNumBuffer
invoke DrawString8x8, 108, 130, OFFSET hudNumBuffer, 00FFFFFFh
invoke DrawString8x8, 124, 130, OFFSET hudNumBuffer, 00FFFFFFh
invoke DrawString8x8, 44, 150, OFFSET textFullscreenKey3D, 00A0A0A0h
invoke DrawString8x8, 44, 170, OFFSET textResume3D, 00D8BC6Ah
invoke DrawString8x8, 44, 188, OFFSET textQuit3D, 00BBBBBBh
@ -5441,6 +5458,7 @@ engine_shutdown:
call FreeStartupAssets3D
call FreeCardTableAssets3D
call FreeBuckshotAssets3D
call FreeNativeSlots3D
call FreeArrivalAssets3D
cmp h3DDC, 0
je @F

Binary file not shown.

View file

@ -101,6 +101,28 @@ not decorative extra opponents. Native talisman effects, achievements, daily
tasks and online tables still require integration with the selected profile.
The old installer has not been repackaged with this work.
The classic 777 cabinet is now playable natively: generated cabinet/symbol art,
three easing reel strips, sequential stops, selectable stakes, a paytable and
queued safe exit. See `native-slots.md`. CloverPit remains a separate unported
game rather than a renamed copy of this machine.
Hall furniture now uses isolated, tightly bounded table and cabinet packs rather
than contaminated atlas cells. A full-height skill cabinet and twelve collidable
lounge/rope props are described in `hall-furniture.md`; game entry IDs and saves
remain unchanged.
European roulette now has matching first-person table/close-up scenes and a
separately animated exact 37-pocket rotor, plus persistent result and queued exit.
See `native-european-roulette.md` for supported bets and current visual limits.
The six exterior walkers now exclusively use outdoor art and validated routes;
street people/guards and hall tables use smaller world dimensions.
Coin toss now has its own first-person table, two embossed face sprites and
sixteen hand poses with an integrated coin on the thumb before release.
Its time-based toss, rotation, shadow and landing
bounces end on the paid result, which stays visible until the next-round command.
Escape queues a safe departure during the toss. See `native-coin-scene.md`.
The target is more than twenty actual games, not twenty menu placeholders. Native
expansion candidates, to be specified and tested individually: Texas Hold'em,
blackjack, Durak, European roulette, craps, coin toss, cups, Russian roulette,
@ -119,6 +141,15 @@ Durak regressions include 500 complete games, all 5184 trump/attack/defense
combinations, exact 36-card conservation, transfer/refill/take restrictions,
all-card paging and exit from every active phase. A shared 52-card fill check
guards against face-up cards incorrectly reading the back-color palette.
777 checks cover all 8000 stop combinations, 500 complete spins at four update
rates, monotonic reel paths, one-time debits/payouts and safe exit. Eight real
native framebuffer snapshots check reel clipping and sequential stops.
Hall furniture checks cover floor anchors, valid map cells, collision and the
main passage, with three additional close-up native snapshots.
Coin regressions cover 500 paid flips at four update rates, every exit phase,
1701 continuous coin positions, wager controls, missing-art rejection and ten
native framebuffer captures. `check_coin_scene.py --parity` also compares the
tested coin procedures with the shipping object.
The camera route is sampled against the exterior map, the full timed opening
must reach profile selection, restarting must return to the black transition,
and renderer calls must restore the hall camera, eye height, map, horizon, floor,

View file

@ -0,0 +1,29 @@
# Coin Result Window
The native MASM coin scene opens an owned, non-activating graphical window to
the right of the main game. It renders into a separate 320 x 400 BGRA buffer,
using the same coin faces, toss clock and actual outcome as the table scene.
It does not choose another outcome or change wagers, payouts or RNG state.
The outcome remains hidden until the coin lands. The window then shows the
actual face, a large Russian side label, win/loss and the net stake change.
The result stays until the player requests another toss. The window closes
when leaving the game, including an exit queued during an unfinished toss.
Closing its X only closes the preview; the main scene's view button reopens it.
In fullscreen, a temporary strip is reserved on the right. Closing the preview
restores the previous main-window rectangle. F11 restores the temporary layout
before changing fullscreen mode and then reopens the preview. A subsequent
explicit window move is not overwritten by preview cleanup. Placement uses the
owning monitor's work area, including negative multi-monitor coordinates.
Russian UI labels are stored in `assets/data/ui-ru.json` as an offline authoring
map. `tools/generators/compile_ui_ru.py` compiles only the listed declarations
to decimal CP1251 bytes in assembly. No JSON or non-assembly runtime is added.
Brand logos, standard card ranks and literal keyboard key names remain intact.
Verification includes hidden native owner/preview creation, repeat opening,
monitor bounds, restoration, F11 and absence of a quit message from preview
closure. Native captures verify both outcome faces and actual Cyrillic glyphs.
The detail renderer is checked not to overwrite the main scene or alter bank
and RNG state. The existing 500-flip coin tests cover settlement and safe exits.

37
docs/hall-furniture.md Normal file
View file

@ -0,0 +1,37 @@
# Hall Furniture V5
The runtime remains MASM32 assembly. Python only packs art and verifies native
framebuffer captures offline; no Python dependency is shipped with the game.
## Corrections
- Card, roulette and backroom tables use their own tightly bounded ANM1 packs,
rather than equal atlas-grid cells containing pieces of adjacent dancers.
- Slot cabinets also use isolated complete frames, including the upper housing.
- Skill checks use a dedicated tall cabinet with a timing dial, hook, button
pedestal and floor base. Its world height is independent of table height.
- Table dimensions now cover the full table and its legs, not transparent sheet
margins. Dedicated assets retain a zero-height floor anchor.
- Twelve decor objects form three seating groups and three rope boundaries.
They have collision footprints and stay clear of the existing patron route,
bar serving side, stairs and game approaches.
- Missing dedicated art is not replaced with the old contaminated atlas frame.
Existing station interaction IDs, game rules and saved data are unchanged.
## Assets
New art: `assets/sprites/hall/furniture-v5.png`.
Built-in image_gen prompt: `assets/sprites/hall/prompts-v5.json`.
Existing source art: `assets/sprites/casino-atlas-v1.png`.
Packed runtime assets: `assets/animations/hall/*-v5.bin`.
Packing: `tools/generators/pack_hall_furniture.py`.
## Verification
`tools/tests/hall_furniture_test.inc` validates station binding, cabinet
dimensions, floor anchors, collision, map occupancy and the main passage.
It captures the actual MASM renderer at table, skill cabinet and lounge views.
The full native action suite also checks visitor routes and old scene/game rules.
`tools/tests/check_hall_furniture.py --parity` validates alpha/bounds, captures
and COFF function parity between the tested and published assembly builds.

45
docs/native-coin-scene.md Normal file
View file

@ -0,0 +1,45 @@
# Native coin table
Coin toss now stays inside the graphical MASM executable. Its first-person
table, two readable coin faces and sixteen hand poses are separate packed assets.
The dealer is a static background character, not an animated rig.
## Controls and settlement
- Choose heads/tails with the buttons or 1/2.
- Change the stake with +/- buttons or W/S and Up/Down (50 through 5000).
- Start with TOSS, Space or Enter. The stake is debited once at this point.
- A winning flip returns twice the stake, including the original stake.
- The result stays visible until AGAIN, Space or Enter returns to selection.
- EXIT or Escape during a paid toss queues departure until settlement.
- Missing art blocks paid play, and insufficient funds never reduce the stake automatically.
Progress is saved through the existing native profile path on debit and settlement.
This is the current local save model, not a crash-recovery journal for unfinished bets.
## Animation
The 1700 ms timeline has a 300 ms thumb preparation, a 720 ms ballistic flight,
two small landing bounces and a final resting pose. In the first five poses the
coin and supporting fingers are one sprite, aligned to the launch point (414,266).
There is no separate, floating coin layer before release. Airborne rotation has
constant angular speed, rather than slowing into a hover. Rotation ends on the
actual chosen result. Position, face, squash, withdrawal and shadow are derived from
elapsed milliseconds rather than frame counts. Rendering does not consume RNG
or alter the wager/outcome. This is an authored toss animation, not rigid-body physics.
## Assets and checks
Source images and complete built-in image-generation prompts:
`assets/sprites/coin/{table,faces}-v1.png`, `hands-v2.png`,
`prompts-v1.json` and `prompts-v2.json`.
The offline Python packer is `tools/generators/pack_coin_assets.py`;
the game loads only ANM1 packs from `assets/animations/coin/`.
No Python or C/C++ runtime was added.
Native assertions live in `tools/tests/coin_test.inc` and run inside the real
assembly action-test executable. The checker `tools/tests/check_coin_scene.py`
validates assets and captured framebuffers, with optional tested/shipping COFF parity.
The launch-path asset probe also loads these packs from the root and from an
unrelated working directory. Run those checks after building; this document does
not itself certify a particular build.

View file

@ -0,0 +1,57 @@
# Native European roulette
The graphical roulette table stays inside the MASM game window. It uses a
generated first-person felt/table view and a matching close-up of the same bowl.
The 37-pocket rotor is a separate precision asset, inverse-sampled and
projected onto the bowl ellipse by assembly code. Upright screen-space numerals
are drawn separately in MASM so the perspective never squashes or rotates their
glyphs. Close-up labels use a crisp 3x5 font at 2x scale; the distant betting
view uses 1x scale. A settled pocket has a gold outline and its result is also
displayed in the wheel centre. Frame timing, counter-rotating
ball motion, camera transition, betting, input and payouts are also assembly.
Python only packs art and validates offline fixtures; it is not the runtime.
## Betting and rounds
Click a number, including the single zero, or an outside bet. The felt's top,
middle and bottom rows are 3/2/1. Supported bets are straight numbers, red/black,
even/odd, 1-18/19-36, three dozens and three columns. Split, street, corner and
six-line bets are not implemented. Gross multipliers are 36, 2 and 3 respectively;
zero loses every outside bet. Chip size is $50 to $1,000,000, adjustable with the
plus/minus controls or W/S. Clear/Backspace resets a draft without charging it.
Spin/Space charges the complete stake once and starts a 4.2-second spin.
The winning number is not printed during the spin. The rotor and ball slow down,
the ball moves into the selected pocket, and result/payout stay on screen until
BETS, Space or Enter returns to a fresh betting layout. Results cannot be
charged or settled again by repeated updates. Escape/EXIT leaves a draft/result
immediately; during a spin it queues departure until settlement. Missing art
blocks a paid spin and never opens a console fallback.
## Assets and checks
- Generated sources: `assets/sprites/wheel/{table,closeup}-v1.png`.
- Built-in image generation/edit prompts: `assets/sprites/wheel/prompts-v1.json`.
- ANM1 packs: `assets/animations/wheel/{table,closeup}-v1.bin` and `rotor-v2.bin`.
- Offline packer: `tools/generators/pack_european_wheel.py`.
- Native assertions: `tools/tests/wheel_scene_test.inc`, called by the action suite.
- Asset/frame/shipping checks: `tools/tests/check_european_wheel.py --parity`.
Native assertions exercise all 37 results with all 49 supported bets, one-time
settlement, every numbered cell's input mapping, monotonic millisecond easing,
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.
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.
## Exterior scale fixes
Two former stationary indoor-guest sprites near the entrance now use outdoor
walking frames and independent curb routes. All six walkers use reduced world
dimensions; entrance guards are smaller too. Routes are sampled against the
actual exterior map and must remain in empty cells. Indoor guest art is forbidden
in the city prop registry. Existing hall tables are about eleven percent smaller;
cabinet dimensions, station IDs and saved progress are unchanged.

View file

@ -1,6 +1,6 @@
# Native scene fixes
The MASM game stays in one graphical window. Unported 777, CloverPit,
The MASM game stays in one graphical window. Unported CloverPit,
online roulette, Umamusume, tower, terminal and observer interactions open a
native "not implemented yet" panel. Escape, E, Enter, Space or its close button
return to the same position. Opening/closing never charges a stake, runs a
@ -10,6 +10,8 @@ graphical interactions.
Durak now has a native animated table instead of that panel, including
podkidnoy/perevodnoy rules and exit confirmation. See `native-durak.md`.
777 now has an animated native cabinet, paytable and safely queued exit.
See `native-slots.md`.
Russian roulette now renders the same first-person room/table style as
Buckshot, with its own revolver/loading hands and independent asset buffers.

64
docs/native-slots.md Normal file
View file

@ -0,0 +1,64 @@
# Native 777
The 777 cabinet in the hall now opens a playable first-person graphical slot
machine, rather than the unavailable panel or a console routine. Runtime rules,
input, reel movement, sprite clipping and bank settlement are MASM only.
## Rules
There is one paying line: the middle symbol on each of three independent drums.
Each drum has 20 stops, with these symbol counts: cherries 5, lemon 4, grapes 3,
bell 3, BAR 2, seven 1, diamond 1 and crown 1. Stops are sampled once when the
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
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
remains 1000000000. Stakes are saved immediately and outcomes are settled and
saved once. An insufficient bankroll never starts a paid spin.
## Interaction
Space/Enter and the spin button start one spin; W/S, arrow keys or the minus/plus
buttons change the stake between spins. P or the payouts button opens a separate
paytable. Escape first closes that overlay without leaving the machine.
During a spin, Escape or the header exit button queues a return to the same hall
position after all three drums stop and the payout is saved. It does not pause,
refund, forfeit a valid result, or allow another stake to be queued. Closing the
application before settlement retains the already deducted stake; resumable
pending rounds are not implemented.
## Art And Timing
The cabinet and eight symbol sprites were generated with the built-in image_gen
tool in the existing detailed retro pixel-art direction. Source assets and exact
prompts are in `assets/sprites/slots/`; the project does not depend on the tool's
external image cache. `pack_slot_assets.py` normalizes them offline to one
opaque 640x480 cabinet and eight alpha-tested 64x64 symbols in ANM1 packs.
Reels move through their complete strips with a monotonic quadratic ease-out,
stopping at 1800, 2400 and 3000 milliseconds. Icons are clipped to each reel's
window; neighboring cabinet artwork cannot be overwritten. The opaque cabinet
uses a direct framebuffer copy rather than alpha sampling every background pixel.
No pre-rendered spin video, C game implementation or frame-dependent RNG is used.
## Verification
`test_casino_actions.ps1 -SkipPreview` includes 8000 stop combinations, 500
complete spins at four update rates, millisecond-by-millisecond monotonicity,
idempotent payout, maximum-bank handling, missing/malformed assets, insufficient
stakes, paytable and queued exit checks. Test saves remain disabled in the
native test build. `check_slot_frames.py` checks eight real MASM framebuffer
snapshots, sequential stops and symbol clipping. `check_slot_build.py` compares
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.

BIN
poker.exe

Binary file not shown.

BIN
poker.obj

Binary file not shown.

View file

@ -18,4 +18,4 @@ DebOut=
[FILES]
1=poker.asm
[SHOWFILE]
1=0000000000000000140600007403000000000000030000000000000094
1=0000000000000000140600007403000000000000030000004836000012

Binary file not shown.

View file

@ -42,20 +42,20 @@ entryDisplayName3D db 24 dup(0)
entryDat3D db ".dat",0
entryWildcard3D db "\*.dat",0
entryLocalRoot3D db ".",0
entryWelcome3D db "CASINO / ENTRY",0
entryChoose3D db "YOUR PROFILE",0
entryGuest3D db "PREVIEW PROFILE",0
entryNew3D db "+ NEW PLAYER",0
entryNameText3D db "PLAYER NAME",0
entryWelcome3D db 194,213,206,196,32,194,32,202,192,199,200,205,206,0
entryChoose3D db 194,192,216,32,207,208,206,212,200,203,220,0
entryGuest3D db 195,206,209,210,220,0
entryNew3D db 43,32,205,206,194,219,201,32,200,195,208,206,202,0
entryNameText3D db 200,204,223,32,200,195,208,206,202,192,0
entryNameHint3D db "A-Z 0-9 _ -",0
entryConfirm3D db "ENTER",0
entryCancel3D db "ESC: BACK",0
entryContinue3D db "WELCOME",0
entryConfirm3D db 194,206,201,210,200,0
entryCancel3D db 69,83,67,58,32,205,192,199,192,196,0
entryContinue3D db 196,206,193,208,206,32,207,206,198,192,203,206,194,192,210,220,0
entryMoney3D db "$",0
entryProfileError3D db "CANNOT LOAD SAVE",0
entryNameError3D db "NAME TAKEN / I/O",0
entryStop3D db "ONE MOMENT.",0
entryChecking3D db "CHECKING THE GUEST LIST",0
entryReady3D db "PLEASE, COME IN.",0
entrySkip3D db "ENTER: APPROACH",0
entryProfileError3D db 209,193,206,201,32,199,192,195,208,211,199,202,200,0
entryNameError3D db 200,204,223,32,199,192,205,223,210,206,32,47,32,209,193,206,201,0
entryStop3D db 206,196,205,211,32,204,200,205,211,210,211,46,0
entryChecking3D db 207,208,206,194,197,208,223,197,204,32,209,207,200,209,206,202,32,195,206,209,210,197,201,0
entryReady3D db 207,208,206,213,206,196,200,210,197,46,0
entrySkip3D db 69,78,84,69,82,58,32,207,206,196,206,201,210,200,0
.code

View file

@ -47,20 +47,20 @@ poolCharge3D dd 0
poolAccumulator3D dd 0
poolTime3D dd 0
poolStill3D dd 0
poolTitle3D db "8 BALL",0
poolPlayerText3D db "PLAYER",0
poolDealerText3D db "DEALER",0
poolOpenText3D db "OPEN TABLE",0
poolSolidsText3D db "SOLIDS",0
poolStripesText3D db "STRIPES",0
poolFoulText3D db "FOUL / BALL IN HAND",0
poolWinText3D db "RACK WON",0
poolShotText3D db "SHOT",0
poolRackText3D db "RERACK",0
poolPowerText3D db "POWER",0
poolPlaceText3D db "PLACE CUE BALL",0
poolMovingText3D db "ROLLING",0
poolBallText3D db "BALL",0
poolPocketText3D db "POCKET",0
poolTitle3D db 207,211,203,32,47,32,56,0
poolPlayerText3D db 200,195,208,206,202,0
poolDealerText3D db 196,200,203,197,208,0
poolOpenText3D db 209,210,206,203,32,206,210,202,208,219,210,0
poolSolidsText3D db 209,207,203,206,216,205,219,197,0
poolStripesText3D db 207,206,203,206,209,192,210,219,197,0
poolFoulText3D db 212,206,203,32,47,32,209,32,208,211,202,200,0
poolWinText3D db 207,206,193,197,196,192,0
poolShotText3D db 211,196,192,208,0
poolRackText3D db 199,192,205,206,194,206,0
poolPowerText3D db 209,200,203,192,0
poolPlaceText3D db 207,206,209,210,192,194,220,210,197,32,193,200,210,206,202,0
poolMovingText3D db 202,192,210,223,210,209,223,0
poolBallText3D db 216,192,208,0
poolPocketText3D db 203,211,199,192,0
poolGroupTexts3D dd OFFSET poolOpenText3D,OFFSET poolSolidsText3D,OFFSET poolStripesText3D
.code

View file

@ -1283,9 +1283,9 @@ bs_draw_health:
sub eax, bs3d_shellIndex
invoke DrawBuckshotNumber3D, 78, 396, eax, 00FFFFFFh
invoke DrawBuckshotText3D, 14, 412, OFFSET bs3d_textLive, 00DF6656h
invoke DrawBuckshotNumber3D, 52, 412, bs3d_liveCount, 00DF6656h
invoke DrawBuckshotText3D, 76, 412, OFFSET bs3d_textBlank, 007DBBCCh
invoke DrawBuckshotNumber3D, 120, 412, bs3d_blankCount, 007DBBCCh
invoke DrawBuckshotNumber3D, 70, 412, bs3d_liveCount, 00DF6656h
invoke DrawBuckshotText3D, 14, 430, OFFSET bs3d_textBlank, 007DBBCCh
invoke DrawBuckshotNumber3D, 86, 430, bs3d_blankCount, 007DBBCCh
xor esi, esi
bs_draw_inventory:
imul ebx, esi, 112
@ -1345,9 +1345,9 @@ bs_draw_ready:
invoke DrawBuckshotPanel3D, 112, 416, 28, 22, 00404A46h
invoke DrawBuckshotText3D, 122, 423, OFFSET bs3d_plus, 00FFFFFFh
invoke DrawBuckshotText3D, 22, 451, OFFSET bs3d_textBet, 00C7B88Fh
invoke DrawBuckshotNumber3D, 64, 451, bs3d_bet, 00FFFFFFh
invoke DrawBuckshotNumber3D, 92, 451, bs3d_bet, 00FFFFFFh
invoke DrawBuckshotPanel3D, 246, 446, 136, 24, 00363C30h
invoke DrawBuckshotText3D, 270, 454, OFFSET bs3d_textReady, 00E1C67Bh
invoke DrawBuckshotText3D, 258, 454, OFFSET bs3d_textReady, 00E1C67Bh
invoke DrawBuckshotText3D, 548, 454, OFFSET bs3d_textExit, 00AFAFAFh
cmp bs3d_error, 0
je bs_draw_done
@ -1363,13 +1363,13 @@ bs_draw_player:
jmp bs_draw_notice
bs_draw_win:
invoke DrawBuckshotText3D, 20, 455, OFFSET bs3d_textWin, 007FE395h
invoke DrawBuckshotNumber3D, 96, 455, bs3d_payout, 007FE395h
invoke DrawBuckshotNumber3D, 120, 455, bs3d_payout, 007FE395h
jmp bs_draw_result
bs_draw_lose:
invoke DrawBuckshotText3D, 20, 455, OFFSET bs3d_textLose, 00E1766Ah
bs_draw_result:
invoke DrawBuckshotPanel3D, 246, 446, 136, 24, 00363C30h
invoke DrawBuckshotText3D, 266, 455, OFFSET bs3d_textResult, 00E1C67Bh
invoke DrawBuckshotText3D, 254, 455, OFFSET bs3d_textResult, 00E1C67Bh
invoke DrawBuckshotText3D, 548, 455, OFFSET bs3d_textExit, 00AFAFAFh
jmp bs_draw_done
bs_draw_notice:

View file

@ -99,42 +99,43 @@ bs3d_frameCounts dd 1,12,16,16,12,8,8,12,32,8,16,8
bs3d_frameWidths dd 640,320,320,256,128,256,320,256,384,320,256,128
bs3d_frameHeights dd 480,280,280,224,128,256,280,224,256,240,224,128
bs3d_pumpSound db "assets\audio\generated\buckshot-pump.wav",0
bs3d_textTitle db "BUCKSHOT",0
bs3d_textBank db "BANK $",0
bs3d_textPlayer db "PLAYER",0
bs3d_textDealer db "DEALER",0
bs3d_textBet db "BET $",0
bs3d_textReady db "SPACE: PLAY",0
bs3d_textResult db "SPACE: AGAIN",0
bs3d_textExit db "ESC: HALL",0
bs3d_textPause db "ESC: PAUSE",0
bs3d_textChoose db "1: SELF",0
bs3d_textOpponent db "2: DEALER",0
bs3d_textWin db "YOU WIN",0
bs3d_textLose db "DEALER WINS",0
bs3d_textLoad db "RELOADING",0
bs3d_textLive db "LIVE",0
bs3d_textBlank db "BLANK",0
bs3d_textShells db "SHELLS",0
bs3d_textEmpty db "NOT ENOUGH CASH",0
bs3d_textShot db "LIVE ROUND",0
bs3d_textDry db "BLANK ROUND",0
bs3d_textKnownLive db "NEXT: LIVE",0
bs3d_textKnownBlank db "NEXT: BLANK",0
bs3d_textSaw db "NEXT DAMAGE x2",0
bs3d_textBeer db "SHELL EJECTED",0
bs3d_textCuffs db "OPPONENT SKIPS A TURN",0
bs3d_textItemEmpty db "ITEM UNAVAILABLE",0
bs3d_textWait db "DEALER'S TURN",0
bs3d_textItem db "USING AN ITEM",0
bs3d_textAssets db "ANIMATION FILE MISSING OR INVALID",0
bs3d_textMedical db "MEDICAL UNIT: RESTORING VITALS",0
bs3d_textDeath db "VITAL SIGNAL LOST",0
bs3d_textTitle db 193,192,202,216,206,210,0
bs3d_textBank db 193,192,205,202,32,36,0
bs3d_textPlayer db 200,195,208,206,202,0
bs3d_textDealer db 196,200,203,197,208,0
bs3d_textBet db 209,210,192,194,202,192,32,36,0
bs3d_textReady db 207,208,206,193,197,203,58,32,200,195,208,192,210,220,0
bs3d_textResult db 207,208,206,193,197,203,58,32,197,217,168,32,208,192,199,0
bs3d_textExit db 69,83,67,58,32,213,206,203,203,0
bs3d_textPause db 69,83,67,58,32,207,192,211,199,192,0
bs3d_textChoose db 49,58,32,194,32,209,197,193,223,0
bs3d_textOpponent db 50,58,32,194,32,196,200,203,197,208,192,0
bs3d_textWin db 194,219,32,194,219,200,195,208,192,203,200,0
bs3d_textLose db 196,200,203,197,208,32,194,219,200,195,208,192,203,0
bs3d_textLoad db 207,197,208,197,199,192,208,223,196,202,192,0
bs3d_textLive db 193,206,197,194,219,197,0
bs3d_textBlank db 213,206,203,206,209,210,219,197,0
bs3d_textShells db 207,192,210,208,206,205,219,0
bs3d_textEmpty db 204,192,203,206,32,196,197,205,197,195,0
bs3d_textShot db 193,206,197,194,206,201,32,207,192,210,208,206,205,0
bs3d_textDry db 213,206,203,206,209,210,206,201,32,207,192,210,208,206,205,0
bs3d_textKnownLive db 209,203,197,196,211,222,217,200,201,58,32,193,206,197,194,206,201,0
bs3d_textKnownBlank db 209,203,197,196,211,222,217,200,201,58,32,213,206,203,206,209,210,206,201,0
bs3d_textSaw db 211,208,206,205,32,209,203,197,196,211,222,217,197,195,206,32,194,219,209,210,208,197,203,192,32,120,50,0
bs3d_textBeer db 207,192,210,208,206,205,32,194,219,193,208,206,216,197,205,0
bs3d_textCuffs db 207,208,206,210,200,194,205,200,202,32,207,208,206,207,211,209,202,192,197,210,32,213,206,196,0
bs3d_textItemEmpty db 207,208,197,196,204,197,210,32,205,197,196,206,209,210,211,207,197,205,0
bs3d_textWait db 213,206,196,32,196,200,203,197,208,192,0
bs3d_textItem db 200,209,207,206,203,220,199,211,197,204,32,207,208,197,196,204,197,210,0
bs3d_textAssets db 192,205,200,204,192,214,200,223,32,205,197,32,205,192,201,196,197,205,192,32,200,203,200,32,207,206,194,208,197,198,196,197
db 205,192,0
bs3d_textMedical db 204,197,196,200,214,200,205,209,202,192,223,32,204,192,216,200,205,192,58,32,208,197,192,205,200,204,192,214,200,223,0
bs3d_textDeath db 198,200,199,205,197,205,205,219,201,32,209,200,195,205,192,203,32,207,206,210,197,208,223,205,0
bs3d_textItems dd OFFSET bs3d_textGlassKey,OFFSET bs3d_textSawKey
dd OFFSET bs3d_textBeerKey,OFFSET bs3d_textCuffKey
bs3d_textGlassKey db "3: GLASS",0
bs3d_textSawKey db "4: SAW",0
bs3d_textBeerKey db "5: BEER",0
bs3d_textCuffKey db "6: CUFFS",0
bs3d_textGlassKey db 51,58,32,203,211,207,192,0
bs3d_textSawKey db 52,58,32,207,200,203,192,0
bs3d_textBeerKey db 53,58,32,207,200,194,206,0
bs3d_textCuffKey db 54,58,206,202,206,194,219,0
bs3d_minus db "-",0
bs3d_plus db "+",0

View file

@ -146,6 +146,24 @@ RequestCardExit3D ENDP
; Called by the engine's Escape dispatch, before the generic pause branch.
CardTableEscape3D PROC
cmp engineMode, MODE_COIN
jne @F
call CoinEscape3D
mov eax,1
ret
@@:
cmp engineMode, MODE_ROULETTE
jne @F
call WheelEscape3D
mov eax,1
ret
@@:
cmp engineMode, MODE_SLOTS
jne @F
call SlotEscape3D
mov eax,1
ret
@@:
cmp engineMode, MODE_POKER
je card_escape_handle
cmp engineMode, MODE_BLACKJACK

View file

@ -108,41 +108,41 @@ pkError3D dd 0
pkChipActor3D dd 0
pkChipAmount3D dd 0
pkChipTimer3D dd 320
pkTitle3D db "TEXAS HOLD'EM",0
pkPotText3D db "POT $",0
pkStreet0Text3D db "PREFLOP",0
pkStreet1Text3D db "FLOP",0
pkStreet2Text3D db "TURN",0
pkStreet3Text3D db "RIVER",0
pkTitle3D db 210,197,213,192,209,209,202,200,201,32,213,206,203,196,197,204,0
pkPotText3D db 193,192,205,202,0
pkStreet0Text3D db 207,208,197,212,203,206,207,0
pkStreet1Text3D db 212,203,206,207,0
pkStreet2Text3D db 210,168,208,205,0
pkStreet3Text3D db 208,200,194,197,208,0
pkStreetNames3D dd OFFSET pkStreet0Text3D,OFFSET pkStreet1Text3D,OFFSET pkStreet2Text3D,OFFSET pkStreet3Text3D
pkCheckText3D db "CHECK",0
pkCallText3D db "CALL",0
pkRaiseText3D db "RAISE",0
pkFoldText3D db "FOLD",0
pkAllInText3D db "ALL-IN",0
pkCostText3D db "CALL $",0
pkMinimumText3D db "MIN RAISE",0
pkDealText3D db "DEAL",0
pkNextText3D db "NEXT",0
pkAiWait3D db "DEALER",0
pkAiCheck3D db "CHECK",0
pkAiCall3D db "CALL",0
pkAiRaise3D db "RAISE",0
pkAiFold3D db "FOLD",0
pkCheckText3D db 207,192,209,0
pkCallText3D db 211,208,192,194,205,223,210,220,0
pkRaiseText3D db 207,206,194,219,209,200,210,220,0
pkFoldText3D db 209,193,208,206,209,0
pkAllInText3D db 194,192,45,193,192,205,202,0
pkCostText3D db 202,206,203,203,0
pkMinimumText3D db 204,200,205,46,32,208,197,201,199,0
pkDealText3D db 209,196,192,210,220,0
pkNextText3D db 196,192,203,197,197,0
pkAiWait3D db 196,200,203,197,208,0
pkAiCheck3D db 207,192,209,0
pkAiCall3D db 202,206,203,203,0
pkAiRaise3D db 208,197,201,199,0
pkAiFold3D db 209,193,208,206,209,0
pkAiNames3D dd OFFSET pkAiWait3D,OFFSET pkAiCheck3D,OFFSET pkAiCall3D,OFFSET pkAiRaise3D,OFFSET pkAiFold3D
pkWinText3D db "YOU WIN",0
pkLoseText3D db "DEALER WINS",0
pkTieText3D db "SPLIT POT",0
pkWinText3D db 194,219,32,194,219,200,195,208,192,203,200,0
pkLoseText3D db 196,200,203,197,208,32,194,219,200,195,208,192,203,0
pkTieText3D db 193,192,205,202,32,207,206,208,206,194,205,211,0
pkResults3D dd OFFSET pkWinText3D,OFFSET pkLoseText3D,OFFSET pkTieText3D
pkRank0 db "HIGH CARD",0
pkRank1 db "PAIR",0
pkRank2 db "TWO PAIR",0
pkRank3 db "THREE OF A KIND",0
pkRank4 db "STRAIGHT",0
pkRank5 db "FLUSH",0
pkRank6 db "FULL HOUSE",0
pkRank7 db "FOUR OF A KIND",0
pkRank8 db "STRAIGHT FLUSH",0
pkRank0 db 209,210,192,208,216,192,223,32,202,192,208,210,192,0
pkRank1 db 207,192,208,192,0
pkRank2 db 196,194,197,32,207,192,208,219,0
pkRank3 db 210,208,206,201,202,192,0
pkRank4 db 209,210,208,200,210,0
pkRank5 db 212,203,197,216,0
pkRank6 db 212,211,203,203,45,213,192,211,209,0
pkRank7 db 202,192,208,197,0
pkRank8 db 209,210,208,200,210,45,212,203,197,216,0
pkRankNames3D dd OFFSET pkRank0,OFFSET pkRank1,OFFSET pkRank2,OFFSET pkRank3,OFFSET pkRank4
dd OFFSET pkRank5,OFFSET pkRank6,OFFSET pkRank7,OFFSET pkRank8
pkRankLimits3D dd 7462,6185,3325,2467,1609,1599,322,166,10
@ -163,15 +163,15 @@ cbDouble3D dd 0
cbOutcome3D dd 0
cbCard3D dd 0
cbError3D dd 0
cbTitle3D db "BLACKJACK / 21",0
cbHitText3D db "HIT",0
cbStandText3D db "STAND",0
cbDoubleText3D db "DOUBLE",0
cbNaturalText3D db "BLACKJACK",0
cbTitle3D db 193,203,221,202,196,198,197,202,32,47,32,50,49,0
cbHitText3D db 202,192,208,210,192,0
cbStandText3D db 213,194,192,210,200,210,0
cbDoubleText3D db 211,196,194,206,200,210,220,0
cbNaturalText3D db 193,203,221,202,196,198,197,202,0
cbResults3D dd OFFSET pkWinText3D,OFFSET pkLoseText3D,OFFSET textTie3D
tableBlindText3D db "BLIND $",0
tableRaiseText3D db "RAISE +$",0
tableScoreText3D db "TOTAL",0
tableBlindText3D db 193,203,192,201,205,196,32,36,0
tableRaiseText3D db 208,197,201,199,32,43,36,0
tableScoreText3D db 206,215,202,200,0
tablePlusText3D db "+",0
tableMinusText3D db "-",0
tableDealOrder3D dd 0,2,1,3

View file

@ -312,7 +312,7 @@ bj_render_stake_enabled:
invoke DrawStakeControls3D, eax
invoke DrawString8x8, 8, 36, OFFSET textBet3D, 00DDCFB1h
invoke FormatNumber, cbBet3D, OFFSET hudNumBuffer
invoke DrawString8x8, 48, 36, OFFSET hudNumBuffer, 00F6F4DDh
invoke DrawString8x8, 80, 36, OFFSET hudNumBuffer, 00F6F4DDh
cmp cbState3D, CB_IDLE
je bj_render_buttons
xor edi, edi

View file

@ -73,7 +73,7 @@ CITY_PROP_COUNT equ 47
CITY_FIRST_CAR equ 9
CITY_TRAFFIC_EAST equ 14
CITY_TRAFFIC_WEST equ 15
CITY_PEDESTRIAN_COUNT equ 4
CITY_PEDESTRIAN_COUNT equ 6
CityWalkRoute3D STRUCT
objectIndex dd ?
minX dd ?
@ -109,9 +109,9 @@ cityObjects3D CityProp3D <(8*FP_1+FP_1/3),(26*FP_1+FP_1/3),FP_1*3/2,FP_1*5/4,0,O
CityProp3D <4*FP_1,14*FP_1,FP_1*3,FP_1*9/5,8,OFFSET cityCarsPack3D>
CityProp3D <28*FP_1,15*FP_1+FP_1/5,FP_1*3,FP_1*9/5,0,OFFSET cityCarsPack3D>
cityNearMan3D LABEL BYTE
CityProp3D <12*FP_1,FP_1*253/20,FP_1,FP_1*9/5,0,OFFSET hallStreetLife3D>
CityProp3D <12*FP_1,FP_1*253/20,FP_1*7/10,FP_1*27/20,0,OFFSET hallStreetLife3D>
cityFarWoman3D LABEL BYTE
CityProp3D <26*FP_1,FP_1*169/10,FP_1*6/5,2*FP_1,16,OFFSET hallStreetLife3D>
CityProp3D <26*FP_1,FP_1*169/10,FP_1*4/5,FP_1*7/5,16,OFFSET hallStreetLife3D>
CityProp3D <26*FP_1,FP_1*259/20,FP_1*4/5,FP_1,34,OFFSET hallStreetLife3D>
CityProp3D <10*FP_1+FP_HALF,FP_1*333/20,FP_1*4/5,FP_1*6/5,35,OFFSET hallStreetLife3D>
CityProp3D <21*FP_1,FP_1*259/20,FP_1/4,FP_1*23/50,36,OFFSET hallStreetLife3D>
@ -119,9 +119,9 @@ cityFarWoman3D LABEL BYTE
CityProp3D <34*FP_1,FP_1*259/20,FP_1,3*FP_1,3,OFFSET cityPropsPack3D>
; Another walking pair occupies the near and far curbs, not the traffic lanes.
cityNearWoman3D LABEL BYTE
CityProp3D <14*FP_1,FP_1*63/5,FP_1*6/5,2*FP_1,16,OFFSET hallStreetLife3D>
CityProp3D <14*FP_1,FP_1*63/5,FP_1*4/5,FP_1*7/5,16,OFFSET hallStreetLife3D>
cityFarMan3D LABEL BYTE
CityProp3D <22*FP_1,FP_1*337/20,FP_1,FP_1*9/5,0,OFFSET hallStreetLife3D>
CityProp3D <22*FP_1,FP_1*337/20,FP_1*7/10,FP_1*27/20,0,OFFSET hallStreetLife3D>
; Near-side street furniture is staggered along the route and clear of its center.
CityProp3D <12*FP_1,FP_1*259/20,FP_1*3/5,FP_1/2,33,OFFSET hallStreetLife3D>
CityProp3D <14*FP_1+FP_HALF,FP_1*259/20,FP_1*4/5,FP_1*6/5,35,OFFSET hallStreetLife3D>
@ -129,9 +129,11 @@ cityFarMan3D LABEL BYTE
CityProp3D <16*FP_1,FP_1*259/20,FP_1/4,FP_1*23/50,36,OFFSET hallStreetLife3D>
CityProp3D <18*FP_1,FP_1*259/20,FP_1/4,FP_1*23/50,36,OFFSET hallStreetLife3D>
CityProp3D <24*FP_1,FP_1*259/20,FP_1/4,FP_1*23/50,36,OFFSET hallStreetLife3D>
; Waiting guests flank the entrance instead of blocking it.
CityProp3D <20*FP_1+FP_HALF,9*FP_1,FP_1*17/20,FP_1*17/10,1,OFFSET hallPatrons3D>
CityProp3D <24*FP_1+FP_HALF,9*FP_1,FP_1*17/20,FP_1*17/10,5,OFFSET hallPatrons3D>
; Independent outdoor walkers replace the stationary indoor visitor billboards.
cityEntryMan3D LABEL BYTE
CityProp3D <20*FP_1+FP_HALF,9*FP_1,FP_1*7/10,FP_1*27/20,0,OFFSET hallStreetLife3D>
cityEntryWoman3D LABEL BYTE
CityProp3D <24*FP_1+FP_HALF,9*FP_1,FP_1*4/5,FP_1*7/5,16,OFFSET hallStreetLife3D>
CityProp3D <13*FP_1,FP_1*333/20,FP_1*4/5,FP_1,34,OFFSET hallStreetLife3D>
CityProp3D <21*FP_1,FP_1*333/20,FP_1*4/5,FP_1*6/5,35,OFFSET hallStreetLife3D>
CityProp3D <24*FP_1,FP_1*333/20,FP_1,3*FP_1,3,OFFSET cityPropsPack3D>
@ -153,6 +155,8 @@ cityWalkRoutes3D CityWalkRoute3D <(cityNearMan3D-cityObjects3D)/SIZEOF CityProp3
CityWalkRoute3D <(cityFarWoman3D-cityObjects3D)/SIZEOF CityProp3D,10*FP_1,28*FP_1,FP_1*169/10,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>
IFDEF CASINO_ACTION_TEST
cityVisiblePixels3D dd CITY_PROP_COUNT dup(0)
ENDIF

View file

@ -0,0 +1,438 @@
; An owned, non-activating window shares the game's clock and settled outcome.
OpenCoinDetail3D PROC
cmp h3DWnd,0
je detail_open_done
cmp coinDetailWnd3D,0
jne detail_open_done
mov coinDetailWC3D.cbSize,SIZEOF C3D_WNDCLASSEX
mov coinDetailWC3D.style,CS_HREDRAW or CS_VREDRAW
mov coinDetailWC3D.lpfnWndProc,OFFSET CoinDetailEvents3D
mov eax,h3DInst
mov coinDetailWC3D.hInstance,eax
mov coinDetailWC3D.lpszClassName,OFFSET coinDetailClass3D
invoke LoadCursorA,0,32512
mov coinDetailWC3D.hCursor,eax
invoke RegisterClassExA,OFFSET coinDetailWC3D
mov coinDetailBmi3D.bmiHeader.biSize,SIZEOF C3D_BITMAPINFOHEADER
mov coinDetailBmi3D.bmiHeader.biWidth,COIN_DETAIL_W
mov coinDetailBmi3D.bmiHeader.biHeight,-COIN_DETAIL_H
mov coinDetailBmi3D.bmiHeader.biPlanes,1
mov coinDetailBmi3D.bmiHeader.biBitCount,32
mov coinDetailBmi3D.bmiHeader.biSizeImage,COIN_DETAIL_W*COIN_DETAIL_H*4
invoke CreateWindowExA,08000080h,OFFSET coinDetailClass3D,OFFSET coinDetailTitle3D,80800000h,0,0,COIN_DETAIL_W,COIN_DETAIL_H,h3DWnd,0,h3DInst,0
mov coinDetailWnd3D,eax
test eax,eax
jz detail_open_done
call LayoutCoinDetail3D
invoke IsWindowVisible,h3DWnd
test eax,eax
jz detail_open_done
invoke ShowWindow,coinDetailWnd3D,4
detail_open_done:
ret
OpenCoinDetail3D ENDP
; Restore only our own resize; never overwrite a later F11 or user window move.
CloseCoinDetail3D PROC uses esi edi
local current:CoinDetailRect3D
cmp coinDetailWnd3D,0
je detail_restore
invoke DestroyWindow,coinDetailWnd3D
mov coinDetailWnd3D,0
detail_restore:
cmp coinDetailReserved3D,0
je detail_close_done
mov coinDetailReserved3D,0
invoke IsWindow,h3DWnd
test eax,eax
jz detail_close_done
mov eax,fullscreen3D
cmp eax,coinDetailFullscreen3D
jne detail_close_done
invoke GetWindowRect,h3DWnd,ADDR current
lea esi,current
lea edi,coinDetailApplied3D
mov ecx,4
repe cmpsd
jne detail_close_done
mov eax,coinDetailOriginal3D[8]
sub eax,coinDetailOriginal3D
mov edx,coinDetailOriginal3D[12]
sub edx,coinDetailOriginal3D[4]
invoke SetWindowPos,h3DWnd,0,coinDetailOriginal3D,coinDetailOriginal3D[4],eax,edx,14h
detail_close_done:
ret
CloseCoinDetail3D ENDP
LayoutCoinDetail3D PROC uses ebx esi edi
local current:CoinDetailRect3D,panelX:DWORD,panelY:DWORD,panelW:DWORD,panelH:DWORD
cmp coinDetailWnd3D,0
je detail_layout_done
mov eax,coinDetailWidth3D
mov panelW,eax
mov eax,coinDetailHeight3D
mov panelH,eax
invoke GetWindowRect,h3DWnd,ADDR current
cmp coinDetailReserved3D,0
je detail_layout_new
mov eax,fullscreen3D
cmp eax,coinDetailFullscreen3D
jne detail_layout_new
lea esi,current
lea edi,coinDetailApplied3D
mov ecx,4
repe cmpsd
je detail_layout_position
detail_layout_new:
mov coinDetailReserved3D,0
invoke MonitorFromWindow,h3DWnd,2
invoke GetMonitorInfoA,eax,OFFSET coinDetailMonitor3D
test eax,eax
jz detail_layout_done
; Work area bounds include taskbar and monitors with a negative origin.
mov ebx,coinDetailMonitor3D[28]
sub ebx,coinDetailMonitor3D[20]
mov eax,COIN_DETAIL_W
cmp ebx,800
jae @F
mov eax,ebx
shr eax,2
@@:
mov panelW,eax
mov eax,coinDetailMonitor3D[32]
sub eax,coinDetailMonitor3D[24]
cmp eax,COIN_DETAIL_H
jbe @F
mov eax,COIN_DETAIL_H
@@:
mov panelH,eax
mov coinDetailHeight3D,eax
mov eax,panelW
mov coinDetailWidth3D,eax
mov eax,current.right
add eax,8
add eax,panelW
cmp eax,coinDetailMonitor3D[28]
jle detail_layout_position
lea esi,current
lea edi,coinDetailOriginal3D
mov ecx,4
rep movsd
mov eax,fullscreen3D
mov coinDetailFullscreen3D,eax
mov eax,coinDetailMonitor3D[28]
sub eax,panelW
sub eax,8
sub eax,coinDetailMonitor3D[20]
mov ebx,coinDetailMonitor3D[32]
sub ebx,coinDetailMonitor3D[24]
invoke SetWindowPos,h3DWnd,0,coinDetailMonitor3D[20],coinDetailMonitor3D[24],eax,ebx,14h
invoke GetWindowRect,h3DWnd,ADDR current
lea esi,current
lea edi,coinDetailApplied3D
mov ecx,4
rep movsd
mov coinDetailReserved3D,1
detail_layout_position:
mov eax,current.right
add eax,8
mov panelX,eax
mov eax,current.top
mov ebx,current.bottom
sub ebx,eax
sub ebx,panelH
sar ebx,1
add eax,ebx
cmp eax,coinDetailMonitor3D[24]
jge @F
mov eax,coinDetailMonitor3D[24]
@@:
mov ebx,coinDetailMonitor3D[32]
sub ebx,panelH
cmp eax,ebx
jle @F
mov eax,ebx
@@:
mov panelY,eax
invoke SetWindowPos,coinDetailWnd3D,0,panelX,panelY,panelW,panelH,14h
detail_layout_done:
ret
LayoutCoinDetail3D ENDP
UpdateCoinDetail3D PROC
cmp coinDetailWnd3D,0
je detail_update_done
call LayoutCoinDetail3D
invoke InvalidateRect,coinDetailWnd3D,0,0
invoke UpdateWindow,coinDetailWnd3D
detail_update_done:
ret
UpdateCoinDetail3D ENDP
CoinDetailEvents3D PROC hwnd:DWORD,message:DWORD,wp:DWORD,lp:DWORD
local paint:CoinDetailPaint3D,rect:CoinDetailRect3D,dc:DWORD
cmp message,WM_CLOSE
je detail_event_close
cmp message,WM_DESTROY
je detail_event_destroy
cmp message,201h
jne @F
invoke GetClientRect,hwnd,ADDR rect
movzx eax,WORD PTR lp
imul eax,COIN_DETAIL_W
xor edx,edx
div rect.right
cmp eax,280
jb detail_event_done
movzx eax,WORD PTR lp[2]
imul eax,COIN_DETAIL_H
xor edx,edx
div rect.bottom
cmp eax,36
jb detail_event_close
jmp detail_event_done
@@:
cmp message,14h
jne @F
mov eax,1
ret
@@:
cmp message,21h
jne @F
mov eax,3
ret
@@:
cmp message,0Fh
jne detail_event_default
invoke BeginPaint,hwnd,ADDR paint
mov dc,eax
call RenderCoinDetail3D
invoke GetClientRect,hwnd,ADDR rect
invoke StretchDIBits,dc,0,0,rect.right,rect.bottom,0,0,COIN_DETAIL_W,COIN_DETAIL_H,OFFSET coinDetailBuffer3D,OFFSET coinDetailBmi3D,0,SRCCOPY
invoke EndPaint,hwnd,ADDR paint
jmp detail_event_done
detail_event_close:
call CloseCoinDetail3D
jmp detail_event_done
detail_event_destroy:
mov coinDetailWnd3D,0
detail_event_done:
xor eax,eax
ret
detail_event_default:
invoke DefWindowProcA,hwnd,message,wp,lp
ret
CoinDetailEvents3D ENDP
CoinDetailBox3D PROC uses edi x:DWORD,y:DWORD,w:DWORD,h:DWORD,color:DWORD
local row:DWORD
mov eax,y
mov row,eax
detail_box_row:
mov eax,row
cmp eax,COIN_DETAIL_H
jae detail_box_done
imul eax,COIN_DETAIL_W
add eax,x
lea edi,coinDetailBuffer3D[eax*4]
mov ecx,w
mov eax,color
rep stosd
inc row
dec h
jnz detail_box_row
detail_box_done:
ret
CoinDetailBox3D ENDP
CoinDetailText3D PROC uses ebx esi edi x:DWORD,y:DWORD,pText:DWORD,color:DWORD,scale:DWORD
local row:DWORD,column:DWORD,bits:DWORD
mov esi,pText
detail_text_char:
movzx eax,BYTE PTR [esi]
test eax,eax
jz detail_text_done
lea ebx,font8x8[eax*8]
mov row,0
detail_text_row:
mov ecx,row
movzx eax,BYTE PTR [ebx+ecx]
mov bits,eax
mov column,0
detail_text_pixel:
test bits,80h
jz detail_text_next
mov eax,column
imul eax,scale
add eax,x
mov edx,scale
add edx,eax
cmp edx,COIN_DETAIL_W
ja detail_text_next
mov edx,row
imul edx,scale
add edx,y
mov ecx,scale
add ecx,edx
cmp ecx,COIN_DETAIL_H
ja detail_text_next
invoke CoinDetailBox3D,eax,edx,scale,scale,color
detail_text_next:
shl bits,1
inc column
cmp column,8
jb detail_text_pixel
inc row
cmp row,8
jb detail_text_row
mov eax,scale
shl eax,3
add x,eax
inc esi
jmp detail_text_char
detail_text_done:
ret
CoinDetailText3D ENDP
CoinDetailNumber3D PROC uses ebx edi x:DWORD,y:DWORD,value:DWORD,color:DWORD
local digits[16]:BYTE
lea edi,digits[15]
mov BYTE PTR [edi],0
mov eax,value
mov ebx,10
detail_number_digit:
xor edx,edx
div ebx
add dl,'0'
dec edi
mov [edi],dl
test eax,eax
jnz detail_number_digit
invoke CoinDetailText3D,x,y,edi,color,2
ret
CoinDetailNumber3D ENDP
; Binary-alpha faces are sampled into a private buffer, never the main scene.
CoinDetailFace3D PROC uses ebx esi edi face:DWORD,height:DWORD
local row:DWORD,column:DWORD,sourceRow:DWORD,top:DWORD
cmp coinFacesArt3D.pixels,0
je detail_face_done
mov eax,face
and eax,1
shl eax,16
add eax,coinFacesArt3D.pixels
mov esi,eax
mov eax,height
shr eax,1
neg eax
add eax,166
mov top,eax
mov row,0
detail_face_row:
mov eax,row
shl eax,7
xor edx,edx
div height
shl eax,9
mov sourceRow,eax
mov eax,row
add eax,top
imul eax,COIN_DETAIL_W
add eax,60
lea edi,coinDetailBuffer3D[eax*4]
mov column,0
detail_face_pixel:
mov eax,column
shl eax,7
xor edx,edx
mov ecx,200
div ecx
shl eax,2
add eax,sourceRow
mov ebx,[esi+eax]
test ebx,0FF000000h
jz @F
mov [edi],ebx
@@:
add edi,4
inc column
cmp column,200
jb detail_face_pixel
inc row
mov eax,row
cmp eax,height
jb detail_face_row
detail_face_done:
ret
CoinDetailFace3D ENDP
RenderCoinDetail3D PROC uses ebx esi edi
invoke CoinDetailBox3D,0,0,320,400,0014191Dh
invoke CoinDetailBox3D,0,0,320,36,00252D2Ah
invoke CoinDetailText3D,16,10,OFFSET coinDetailTitle3D,00E5D29Eh,2
invoke CoinDetailText3D,292,10,OFFSET coinDetailClose3D,00ADB9BCh,2
cmp coinArtReady3D,1
jne detail_render_missing
invoke CoinPose3D,coin3d_timer
mov ebx,200
cmp coin3d_state,COIN_IDLE
je detail_render_face
cmp coin3d_timer,COIN_LAND_MS
jae detail_render_face
mov eax,coinAngle3D
mov ebx,tableCos[eax*4]
test ebx,ebx
jns @F
neg ebx
@@:
imul ebx,200
shr ebx,16
cmp ebx,6
jae detail_render_face
mov ebx,6
detail_render_face:
invoke CoinDetailFace3D,coinFace3D,ebx
cmp coin3d_state,COIN_IDLE
je detail_render_pick
cmp coin3d_timer,COIN_LAND_MS
jb detail_render_air
mov eax,OFFSET coinHeadsText3D
mov ebx,96
cmp coin3d_result,1
je @F
mov eax,OFFSET coinTailsText3D
mov ebx,80
@@:
invoke CoinDetailText3D,ebx,284,eax,00F4E7C8h,4
mov eax,coin3d_result
cmp eax,coin3d_choice
jne detail_render_loss
invoke CoinDetailText3D,104,336,OFFSET coinDetailWin3D,0087D4A5h,2
invoke CoinDetailText3D,100,366,OFFSET coinDetailPlus3D,0087D4A5h,2
invoke CoinDetailNumber3D,124,366,coin3d_bet,0087D4A5h
invoke CoinDetailText3D,212,366,OFFSET coinDetailCash3D,0087D4A5h,2
ret
detail_render_loss:
invoke CoinDetailText3D,96,336,OFFSET coinDetailLose3D,00E99383h,2
invoke CoinDetailText3D,100,366,OFFSET coinDetailMinus3D,00E99383h,2
invoke CoinDetailNumber3D,124,366,coin3d_bet,00E99383h
invoke CoinDetailText3D,212,366,OFFSET coinDetailCash3D,00E99383h,2
ret
detail_render_air:
invoke CoinDetailText3D,40,310,OFFSET coinDetailAir3D,00E5D29Eh,2
ret
detail_render_pick:
invoke CoinDetailText3D,104,292,OFFSET coinDetailPick3D,00ADB9BCh,2
mov eax,OFFSET coinHeadsText3D
mov ebx,96
cmp coin3d_choice,1
je @F
mov eax,OFFSET coinTailsText3D
mov ebx,80
@@:
invoke CoinDetailText3D,ebx,330,eax,00F4E7C8h,4
ret
detail_render_missing:
invoke CoinDetailText3D,16,190,OFFSET coinMissingText3D,00E99383h,2
ret
RenderCoinDetail3D ENDP

View file

@ -0,0 +1,54 @@
COIN_DETAIL_W equ 320
COIN_DETAIL_H equ 400
IsWindow PROTO :DWORD
IsWindowVisible PROTO :DWORD
InvalidateRect PROTO :DWORD,:DWORD,:DWORD
GetWindow PROTO :DWORD,:DWORD
CoinDetailRect3D STRUCT
left dd ?
top dd ?
right dd ?
bottom dd ?
CoinDetailRect3D ENDS
CoinDetailPaint3D STRUCT
dc dd ?
erase dd ?
rect CoinDetailRect3D <>
restore dd ?
update dd ?
reserved db 32 dup(?)
CoinDetailPaint3D ENDS
OpenCoinDetail3D PROTO
CloseCoinDetail3D PROTO
LayoutCoinDetail3D PROTO
UpdateCoinDetail3D PROTO
RenderCoinDetail3D PROTO
CoinDetailEvents3D PROTO :DWORD,:DWORD,:DWORD,:DWORD
CoinDetailBox3D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
CoinDetailText3D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
CoinDetailFace3D PROTO :DWORD,:DWORD
CoinDetailNumber3D PROTO :DWORD,:DWORD,:DWORD,:DWORD
.data
coinDetailClass3D db "CasinoCoinDetail",0
coinDetailWnd3D dd 0
coinDetailWC3D C3D_WNDCLASSEX <>
coinDetailBmi3D C3D_BITMAPINFO <>
coinDetailReserved3D dd 0
coinDetailFullscreen3D dd 0
coinDetailWidth3D dd 320
coinDetailHeight3D dd 400
coinDetailOriginal3D dd 4 dup(0)
coinDetailApplied3D dd 4 dup(0)
coinDetailMonitor3D dd 40,9 dup(0)
coinDetailBuffer3D dd (COIN_DETAIL_W*COIN_DETAIL_H) dup(0)
coinDetailView3D db 194,200,196,0
coinDetailTitle3D db 204,206,205,197,210,192,0
coinDetailClose3D db "X",0
coinDetailAir3D db 204,206,205,197,210,192,32,194,32,207,206,203,168,210,197,0
coinDetailPick3D db 194,219,193,208,192,205,206,0
coinDetailWin3D db 194,219,200,195,208,219,216,0
coinDetailLose3D db 207,208,206,200,195,208,219,216,0
coinDetailPlus3D db "+",0
coinDetailMinus3D db "-",0
coinDetailCash3D db "$",0
.code

View file

@ -79,8 +79,44 @@ hallPropsPath3D db "assets\animations\hall\props.bin",0
hallDrinkPath3D db "assets\animations\hall\drink-v4.bin",0
hallLegacyPack3D AnimPack3D <314D4E41h,128,128,1,128*128*4,0>
hallSpriteTemp3D CityProp3D <>
hallSpriteWidths3D dd 3 dup(FP_1*3/5),4 dup(FP_1*3/2),2 dup(FP_1),5 dup(FP_1*3/2),FP_1,FP_1,FP_1,5 dup(FP_1*3/2)
hallSpriteHeights3D dd 3 dup(FP_1*6/5),4 dup(FP_1*4/5),2 dup(FP_1*3/2),5 dup(FP_1*4/5),FP_1,FP_1*3/2,FP_1*3/2,5 dup(FP_1*4/5)
; Tight sprite bounds: table height includes its legs; cabinets are not table-sized.
hallSpriteWidths3D dd 3 dup(FP_1*3/5),4 dup(FP_1*8/5),2 dup(FP_1),3 dup(FP_1*8/5),FP_1,FP_1*8/5,FP_1,FP_1,FP_1,5 dup(FP_1*8/5)
hallSpriteHeights3D dd 3 dup(FP_1*6/5),4 dup(FP_1*17/20),2 dup(FP_1*3/2),3 dup(FP_1*17/20),FP_1*9/5,FP_1*17/20,FP_1,FP_1*3/2,FP_1*3/2,5 dup(FP_1*17/20)
hallCardFurniture3D AnimPack3D <>
hallWheelFurniture3D AnimPack3D <>
hallBackFurniture3D AnimPack3D <>
hallSkillFurniture3D AnimPack3D <>
hallChairFurniture3D AnimPack3D <>
hallLampFurniture3D AnimPack3D <>
hallRopeFurniture3D AnimPack3D <>
hallSlotFurniture3D AnimPack3D <>
hallCardFurniturePath3D db "assets\animations\hall\card-table-v5.bin",0
hallWheelFurniturePath3D db "assets\animations\hall\roulette-table-v5.bin",0
hallBackFurniturePath3D db "assets\animations\hall\backroom-table-v5.bin",0
hallSkillFurniturePath3D db "assets\animations\hall\skill-cabinet-v5.bin",0
hallChairFurniturePath3D db "assets\animations\hall\lounge-chair-v5.bin",0
hallLampFurniturePath3D db "assets\animations\hall\lamp-table-v5.bin",0
hallRopeFurniturePath3D db "assets\animations\hall\velvet-rope-v5.bin",0
hallSlotFurniturePath3D db "assets\animations\hall\slot-cabinets-v5.bin",0
; Station order matches InitializeCasinoSprites3D; zero retains animated legacy art.
hallStationPacks3D dd 3 dup(0),OFFSET hallCardFurniture3D,OFFSET hallWheelFurniture3D
dd 2 dup(OFFSET hallCardFurniture3D),2 dup(OFFSET hallSlotFurniture3D)
dd 3 dup(OFFSET hallCardFurniture3D),OFFSET hallSkillFurniture3D,OFFSET hallCardFurniture3D
dd 3 dup(0),OFFSET hallBackFurniture3D,OFFSET hallCardFurniture3D
dd 2 dup(OFFSET hallBackFurniture3D),0
HALL_DECOR_COUNT equ 12
hallLoungeDecor3D CityProp3D <13*FP_1,19*FP_1,FP_1*3/4,FP_1,0,OFFSET hallChairFurniture3D>
CityProp3D <14*FP_1,19*FP_1,FP_1*3/5,FP_1*7/10,0,OFFSET hallLampFurniture3D>
CityProp3D <15*FP_1,19*FP_1,FP_1*3/4,FP_1,0,OFFSET hallChairFurniture3D>
CityProp3D <27*FP_1,18*FP_1,FP_1*3/4,FP_1,0,OFFSET hallChairFurniture3D>
CityProp3D <28*FP_1,18*FP_1,FP_1*3/5,FP_1*7/10,0,OFFSET hallLampFurniture3D>
CityProp3D <29*FP_1,18*FP_1,FP_1*3/4,FP_1,0,OFFSET hallChairFurniture3D>
CityProp3D <3*FP_1,18*FP_1,FP_1*3/4,FP_1,0,OFFSET hallChairFurniture3D>
CityProp3D <4*FP_1,18*FP_1,FP_1*3/5,FP_1*7/10,0,OFFSET hallLampFurniture3D>
CityProp3D <5*FP_1,18*FP_1,FP_1*3/4,FP_1,0,OFFSET hallChairFurniture3D>
CityProp3D <6*FP_1,6*FP_1,FP_1*9/5,FP_1*7/10,0,OFFSET hallRopeFurniture3D>
CityProp3D <26*FP_1,6*FP_1,FP_1*9/5,FP_1*7/10,0,OFFSET hallRopeFurniture3D>
CityProp3D <28*FP_1,22*FP_1,FP_1*9/5,FP_1*7/10,0,OFFSET hallRopeFurniture3D>
HALL_PROP_COUNT equ 19
hallObjects3D CityProp3D <12*FP_1+FP_HALF,7*FP_1,FP_1*3/5,FP_1*6/5,0,OFFSET hallProps3D>
CityProp3D <16*FP_1+FP_HALF,9*FP_1,FP_1*3/5,FP_1*6/5,2,OFFSET hallProps3D>
@ -103,19 +139,19 @@ hallObjects3D CityProp3D <12*FP_1+FP_HALF,7*FP_1,FP_1*3/5,FP_1*6/5,0,OFFSET hall
CityProp3D <14*FP_1,14*FP_1,FP_1*3/5,FP_1*6/5,2,OFFSET hallProps3D>
bunkerMerchant3D CityProp3D <16*FP_1,13*FP_1,FP_1*3/2,FP_1*6/5,0,OFFSET merchantPack3D,FP_1/8>
bunkerDoor3D CityProp3D <16*FP_1,27*FP_1,FP_1*3/2,FP_1*7/5,5,OFFSET hallProps3D,FP_1>
cityGuardLeft3D CityProp3D <21*FP_1+FP_1*4/5,8*FP_1+FP_HALF,FP_1,FP_1*8/5,0,OFFSET guardPack3D>
cityGuardRight3D CityProp3D <23*FP_1+FP_1/5,8*FP_1+FP_HALF,FP_1,FP_1*8/5,0,OFFSET guardPack3D>
hallBarPrompt3D db "E: COCKTAIL BAR",0
hallBunkerPrompt3D db "E: BASEMENT / SHOP",0
hallUpPrompt3D db "E: UPSTAIRS",0
hallPoolPrompt3D db "E: BILLIARDS",0
barName3D db "OLD TOWN BAR",0
barDrinks3D db "1 HIGHBALL $100",0
barDrinks23D db "2 COCKTAIL $200",0
barDrinks33D db "3 WATER $0",0
barPoor3D db "NOT ENOUGH CHIPS",0
barMixText3D db "MIXING",0
barDrinkText3D db "CHEERS",0
cityGuardLeft3D CityProp3D <21*FP_1+FP_1*4/5,8*FP_1+FP_HALF,FP_1*17/20,FP_1*7/5,0,OFFSET guardPack3D>
cityGuardRight3D CityProp3D <23*FP_1+FP_1/5,8*FP_1+FP_HALF,FP_1*17/20,FP_1*7/5,0,OFFSET guardPack3D>
hallBarPrompt3D db 69,58,32,202,206,202,210,197,201,203,220,205,219,201,32,193,192,208,0
hallBunkerPrompt3D db 69,58,32,207,206,196,194,192,203,32,47,32,204,192,195,192,199,200,205,0
hallUpPrompt3D db 69,58,32,205,192,194,197,208,213,0
hallPoolPrompt3D db 69,58,32,193,200,203,220,223,208,196,0
barName3D db 193,192,208,32,209,210,192,208,206,195,206,32,195,206,208,206,196,192,0
barDrinks3D db 49,32,213,192,201,193,206,203,32,36,49,48,48,0
barDrinks23D db 50,32,202,206,202,210,197,201,203,220,32,36,50,48,48,0
barDrinks33D db 51,32,194,206,196,192,32,36,48,0
barPoor3D db 204,192,203,206,32,212,200,216,197,202,0
barMixText3D db 209,204,197,216,200,194,192,197,204,0
barDrinkText3D db 194,192,216,197,32,199,196,206,208,206,194,220,197,0
barTime3D dd 0
barPhase3D dd 0
barSelection3D dd 0

View file

@ -238,6 +238,25 @@ hall_sprite_loop:
mov hallLegacyPack3D.pixels, edx
mov hallSpriteTemp3D.frame, 0
mov hallSpriteTemp3D.asset, OFFSET hallLegacyPack3D
mov eax, hallStationPacks3D[esi*4]
test eax, eax
jz hall_sprite_draw
; Missing dedicated art hides the station, never shows a contaminated atlas cell.
cmp [eax].AnimPack3D.pixels, 0
je hall_sprite_next
mov hallSpriteTemp3D.asset, eax
cmp eax, OFFSET hallSlotFurniture3D
jne hall_sprite_draw
mov eax, animationClock3D
xor edx, edx
mov ecx, 320
div ecx
add eax, esi
xor edx, edx
mov ecx, 3
div ecx
mov hallSpriteTemp3D.frame, edx
hall_sprite_draw:
invoke DrawCityObject3D, OFFSET hallSpriteTemp3D, 0
hall_sprite_next:
inc esi
@ -263,6 +282,14 @@ InitHall3D PROC uses esi edi
InitHall3D ENDP
LoadHallAssets3D PROC
invoke LoadAnimPack3D, OFFSET hallCardFurniture3D, OFFSET hallCardFurniturePath3D
invoke LoadAnimPack3D, OFFSET hallWheelFurniture3D, OFFSET hallWheelFurniturePath3D
invoke LoadAnimPack3D, OFFSET hallBackFurniture3D, OFFSET hallBackFurniturePath3D
invoke LoadAnimPack3D, OFFSET hallSkillFurniture3D, OFFSET hallSkillFurniturePath3D
invoke LoadAnimPack3D, OFFSET hallChairFurniture3D, OFFSET hallChairFurniturePath3D
invoke LoadAnimPack3D, OFFSET hallLampFurniture3D, OFFSET hallLampFurniturePath3D
invoke LoadAnimPack3D, OFFSET hallRopeFurniture3D, OFFSET hallRopeFurniturePath3D
invoke LoadAnimPack3D, OFFSET hallSlotFurniture3D, OFFSET hallSlotFurniturePath3D
invoke LoadAnimPack3D, OFFSET hallBartender3D, OFFSET hallBartenderPath3D
invoke LoadAnimPack3D, OFFSET hallProps3D, OFFSET hallPropsPath3D
invoke LoadAnimPack3D, OFFSET hallDrink3D, OFFSET hallDrinkPath3D
@ -324,6 +351,16 @@ hall_load_sign:
LoadHallAssets3D ENDP
FreeHallAssets3D PROC
call FreeNativeWheelArt3D
call FreeNativeCoinArt3D
invoke FreeAnimPack3D, OFFSET hallCardFurniture3D
invoke FreeAnimPack3D, OFFSET hallWheelFurniture3D
invoke FreeAnimPack3D, OFFSET hallBackFurniture3D
invoke FreeAnimPack3D, OFFSET hallSkillFurniture3D
invoke FreeAnimPack3D, OFFSET hallChairFurniture3D
invoke FreeAnimPack3D, OFFSET hallLampFurniture3D
invoke FreeAnimPack3D, OFFSET hallRopeFurniture3D
invoke FreeAnimPack3D, OFFSET hallSlotFurniture3D
invoke FreeAnimPack3D, OFFSET hallBartender3D
invoke FreeAnimPack3D, OFFSET hallProps3D
invoke FreeAnimPack3D, OFFSET hallDrink3D
@ -485,6 +522,14 @@ hall_render_lights:
inc esi
cmp esi, HALL_LIGHT_COUNT
jb hall_render_lights
xor esi, esi
hall_render_lounge:
imul eax, esi, SIZEOF CityProp3D
add eax, OFFSET hallLoungeDecor3D
invoke DrawCityObject3D,eax,0
inc esi
cmp esi, HALL_DECOR_COUNT
jb hall_render_lounge
call RenderPatrons3D
ret
hall_render_bunker:
@ -947,9 +992,35 @@ city_world_guard_right:
ret
RenderCityGuards3D ENDP
CanWalkHall3D PROC px:DWORD,py:DWORD
CanWalkHall3D PROC uses esi ebx px:DWORD,py:DWORD
cmp hallLevel3D, 0
jne hall_collision_bunker
xor esi, esi
hall_collision_decor:
imul ebx, esi, SIZEOF CityProp3D
mov eax, px
sub eax, hallLoungeDecor3D[ebx].posX
test eax, eax
jns @F
neg eax
@@:
mov ecx, hallLoungeDecor3D[ebx].worldW
shr ecx, 1
add ecx, 9830
cmp eax, ecx
ja hall_collision_next_decor
mov eax, py
sub eax, hallLoungeDecor3D[ebx].posY
test eax, eax
jns @F
neg eax
@@:
cmp eax, FP_1*2/5
jbe hall_collision_blocked
hall_collision_next_decor:
inc esi
cmp esi, HALL_DECOR_COUNT
jb hall_collision_decor
; Stage and wings are solid: the audience stays in the cabaret's front aisle.
cmp px, 10*FP_1-9830
jl hall_collision_stage_clear

View file

@ -10,32 +10,33 @@ merchantReactionKind3D dd 1
merchantPreviousFocus3D dd 0
merchantMessage3D dd 0
merchantPrices3D dd 0,3000,3000,10000,12000,15000,17000,18000,20000,22000,22000,23000,24000
merchantGrey3D db "SILVER / GREY",0
merchantBlue3D db "SAPPHIRE WAVE",0
merchantRed3D db "CRIMSON CROSS",0
merchantGold3D db "GOLDEN SUN",0
merchantSkull3D db "SKULL / ROSES",0
merchantClover3D db "CLOVERPIT",0
merchantArcane3D db "ARCANE",0
merchantNoir3D db "NOIR",0
merchantDiamond3D db "DIAMOND",0
merchantRider3D db "RIDER",0
merchantBee3D db "BEE",0
merchantFan3D db "FAN",0
merchantWeb3D db "WEB",0
merchantGrey3D db 209,197,208,197,193,208,206,0
merchantBlue3D db 209,192,207,212,200,208,206,194,192,223,32,194,206,203,205,192,0
merchantRed3D db 193,192,195,208,206,194,219,201,32,202,208,197,209,210,0
merchantGold3D db 199,206,203,206,210,206,197,32,209,206,203,205,214,197,0
merchantSkull3D db 215,197,208,197,207,32,200,32,208,206,199,219,0
merchantClover3D db 202,203,197,194,197,208,0
merchantArcane3D db 192,208,202,192,205,192,0
merchantNoir3D db 205,211,192,208,0
merchantDiamond3D db 192,203,204,192,199,0
merchantRider3D db 205,192,197,199,196,205,200,202,0
merchantBee3D db 207,215,197,203,192,0
merchantFan3D db 194,197,197,208,0
merchantWeb3D db 207,192,211,210,200,205,192,0
merchantNames3D dd OFFSET merchantGrey3D,OFFSET merchantBlue3D,OFFSET merchantRed3D
dd OFFSET merchantGold3D,OFFSET merchantSkull3D,OFFSET merchantClover3D
dd OFFSET merchantArcane3D,OFFSET merchantNoir3D,OFFSET merchantDiamond3D
dd OFFSET merchantRider3D,OFFSET merchantBee3D,OFFSET merchantFan3D,OFFSET merchantWeb3D
merchantTitle3D db "SIDOROVICH",0
merchantStock3D db "BACKS",0
merchantBuy3D db "BUY / EQUIP",0
merchantOwned3D db "OWNED",0
merchantEquipped3D db "EQUIPPED",0
merchantPoor3D db "NOT ENOUGH CHIPS",0
merchantShopKeys3D db "UP/DOWN ENTER: BUY ESC: HALL",0
merchantHello3D db "TAKE A LOOK.",0
merchantThanks3D db "A GOOD CHOICE.",0
merchantTitle3D db 209,200,196,206,208,206,194,200,215,0
merchantStock3D db 208,211,193,192,216,202,200,0
merchantBuy3D db 202,211,207,200,210,220,0
merchantOwned3D db 197,209,210,220,0
merchantEquipped3D db 205,192,196,197,210,206,0
merchantPoor3D db 204,192,203,206,32,212,200,216,197,202,0
merchantShopKeys3D db 194,194,197,208,213,47,194,205,200,199,32,32,69,78,84,69,82,58,32,202,211,207,200,210,220,32,32,69,83,67,58,32
db 213,206,203,203,0
merchantHello3D db 207,206,209,204,206,210,208,200,44,32,215,210,206,32,197,209,210,220,46,0
merchantThanks3D db 213,206,208,206,216,200,201,32,194,219,193,206,208,46,0
merchantArrowUp3D db "^",0
merchantArrowDown3D db "v",0
.code

View file

@ -0,0 +1,626 @@
InitNativeCoin3D PROC
mov coin3d_state,COIN_IDLE
mov coin3d_timer,0
mov coin3d_streak,0
mov coin3d_win,0
mov coin3d_choice,1
mov coin3d_result,1
mov coinExitQueued3D,0
mov coinError3D,0
cmp coin3d_bet,50
jae @F
mov coin3d_bet,50
@@:
cmp coin3d_bet,5000
jbe @F
mov coin3d_bet,5000
@@:
call LoadNativeCoinArt3D
call OpenCoinDetail3D
ret
InitNativeCoin3D ENDP
LoadNativeCoinArt3D PROC
cmp coinArtReady3D,1
je coin_art_loaded
invoke LoadAnimPack3D,OFFSET coinTableArt3D,OFFSET coinTablePath3D
invoke LoadAnimPack3D,OFFSET coinFacesArt3D,OFFSET coinFacesPath3D
invoke LoadAnimPack3D,OFFSET coinHandsArt3D,OFFSET coinHandsPath3D
invoke ValidateCityPack3D,OFFSET coinTableArt3D,640,480,1
invoke ValidateCityPack3D,OFFSET coinFacesArt3D,128,128,2
invoke ValidateCityPack3D,OFFSET coinHandsArt3D,224,224,16
cmp coinTableArt3D.pixels,0
je coin_art_failed
cmp coinFacesArt3D.pixels,0
je coin_art_failed
cmp coinHandsArt3D.pixels,0
je coin_art_failed
mov coinArtReady3D,1
coin_art_loaded:
mov eax,1
ret
coin_art_failed:
call FreeNativeCoinArt3D
xor eax,eax
ret
LoadNativeCoinArt3D ENDP
FreeNativeCoinArt3D PROC
call CloseCoinDetail3D
invoke FreeAnimPack3D,OFFSET coinTableArt3D
invoke FreeAnimPack3D,OFFSET coinFacesArt3D
invoke FreeAnimPack3D,OFFSET coinHandsArt3D
mov coinArtReady3D,0
ret
FreeNativeCoinArt3D ENDP
CoinEscape3D PROC
mov paused3D,0
cmp coin3d_state,COIN_TOSS
jne coin_leave_hall
mov coinExitQueued3D,1
call ClearKeys3D
ret
coin_leave_hall:
call CloseCoinDetail3D
mov coinExitQueued3D,0
mov engineMode,MODE_WALK
call ClearKeys3D
invoke PlayCasinoSound3D,0,0,0
ret
CoinEscape3D ENDP
; A paid flip settles once, even if Escape was pressed while it was in the air.
CoinSettle3D PROC
cmp coin3d_state,COIN_TOSS
jne coin_settle_done
mov coin3d_timer,COIN_TOTAL_MS
mov coin3d_win,0
mov eax,coin3d_choice
cmp eax,coin3d_result
jne coin_settle_loss
inc coin3d_streak
mov eax,coin3d_bet
shl eax,1
mov coin3d_win,eax
add bankroll,eax
jnc @F
mov bankroll,1000000000
@@:
cmp bankroll,1000000000
jbe @F
mov bankroll,1000000000
@@:
invoke PlayCasinoSound3D,OFFSET soundCoinDing,0,1
jmp coin_settle_save
coin_settle_loss:
mov coin3d_streak,0
invoke PlayCasinoSound3D,OFFSET soundLose,0,1
coin_settle_save:
mov coin3d_state,COIN_RESULT
call SaveProgress3D
coin_settle_done:
ret
CoinSettle3D ENDP
HandleNativeCoin3D PROC uses ebx
cmp mouseClick3D,0
je coin_native_state
cmp mouseY3D,3
jb coin_native_state
cmp mouseY3D,17
jae coin_native_state
cmp mouseX3D,252
jb coin_native_view
cmp mouseX3D,312
jae coin_native_state
mov mouseClick3D,0
call CoinEscape3D
cmp engineMode,MODE_WALK
je coin_native_done
jmp coin_native_state
coin_native_view:
cmp mouseX3D,198
jb coin_native_state
cmp mouseX3D,246
jae coin_native_state
mov mouseClick3D,0
call OpenCoinDetail3D
coin_native_state:
cmp coin3d_state,COIN_TOSS
jne coin_native_result
mov eax,coin3d_timer
add eax,deltaTime3D
jc coin_native_settle
cmp eax,COIN_TOTAL_MS
jae coin_native_settle
mov coin3d_timer,eax
ret
coin_native_settle:
call CoinSettle3D
cmp coinExitQueued3D,0
je coin_native_done
call CoinEscape3D
ret
coin_native_result:
cmp coin3d_state,COIN_RESULT
jne coin_native_mouse
invoke Pressed3D,20h
test eax,eax
jnz coin_native_again
invoke Pressed3D,0Dh
test eax,eax
jnz coin_native_again
cmp mouseClick3D,0
je coin_native_done
mov mouseClick3D,0
cmp mouseY3D,219
jb coin_native_done
cmp mouseY3D,237
jae coin_native_done
cmp mouseX3D,220
jb coin_native_done
cmp mouseX3D,312
jae coin_native_done
coin_native_again:
mov coin3d_state,COIN_IDLE
mov coin3d_timer,0
mov coinError3D,0
mov mouseClick3D,0
call ClearKeys3D
ret
coin_native_mouse:
cmp mouseClick3D,0
je coin_native_keys
mov mouseClick3D,0
cmp mouseY3D,199
jb coin_native_keys
cmp mouseY3D,215
jae coin_native_sides
cmp mouseX3D,118
jb coin_native_keys
cmp mouseX3D,134
jb coin_native_less
cmp mouseX3D,140
jb coin_native_keys
cmp mouseX3D,156
jb coin_native_more
jmp coin_native_keys
coin_native_sides:
cmp mouseY3D,219
jb coin_native_keys
cmp mouseY3D,237
jae coin_native_keys
cmp mouseX3D,8
jb coin_native_keys
cmp mouseX3D,98
jb coin_native_heads
cmp mouseX3D,106
jb coin_native_keys
cmp mouseX3D,196
jb coin_native_tails
cmp mouseX3D,220
jb coin_native_keys
cmp mouseX3D,312
jb coin_native_start
coin_native_keys:
invoke Pressed3D,26h
test eax,eax
jnz coin_native_more
invoke Pressed3D,57h
test eax,eax
jnz coin_native_more
invoke Pressed3D,28h
test eax,eax
jnz coin_native_less
invoke Pressed3D,53h
test eax,eax
jnz coin_native_less
invoke Pressed3D,31h
test eax,eax
jnz coin_native_heads
invoke Pressed3D,32h
test eax,eax
jnz coin_native_tails
invoke Pressed3D,20h
test eax,eax
jnz coin_native_start
invoke Pressed3D,0Dh
test eax,eax
jnz coin_native_start
ret
coin_native_more:
cmp coin3d_bet,5000
jae coin_native_done
add coin3d_bet,50
mov coinError3D,0
ret
coin_native_less:
cmp coin3d_bet,50
jbe coin_native_done
sub coin3d_bet,50
mov coinError3D,0
ret
coin_native_heads:
mov coin3d_choice,1
ret
coin_native_tails:
mov coin3d_choice,2
ret
coin_native_start:
cmp coinArtReady3D,1
jne coin_native_done
mov eax,coin3d_bet
cmp eax,50
jb coin_native_done
cmp eax,5000
ja coin_native_done
cmp eax,bankroll
ja coin_native_insufficient
sub bankroll,eax
mov coin3d_state,COIN_TOSS
mov coin3d_timer,0
mov coin3d_win,0
mov coinExitQueued3D,0
mov coinError3D,0
mov eax,coin3d_choice
dec eax
shl eax,8
add eax,96
mov coinStartPhase3D,eax
invoke RandomRange3D,2
inc eax
mov coin3d_result,eax
call SaveProgress3D
invoke PlayCasinoSound3D,OFFSET soundCoin,0,1
call ClearKeys3D
ret
coin_native_insufficient:
mov coinError3D,1
coin_native_done:
ret
HandleNativeCoin3D ENDP
; Derive pose from absolute elapsed time; never alter bank, outcome or RNG here.
CoinPose3D PROC uses ebx esi timeMs:DWORD
local phase:DWORD,flightTime:DWORD,bounceTime:DWORD
mov coinX3D,414
mov coinY3D,266
mov coinGroundY3D,344
mov coinWidth3D,32
mov coinHeight3D,12
mov coinShadowRadius3D,30
mov coinShadowAlpha3D,90
mov coinHandY3D,240
mov coinHandPose3D,0
mov eax,coin3d_choice
dec eax
mov coinFace3D,eax
shl eax,8
mov coinAngle3D,eax
cmp coin3d_state,COIN_IDLE
je coin_pose_done
mov eax,timeMs
cmp eax,COIN_RELEASE_MS
jae coin_pose_air
xor edx,edx
mov ecx,60
div ecx
mov coinHandPose3D,eax
ret
coin_pose_air:
sub eax,COIN_RELEASE_MS
mov flightTime,eax
mov ebx,eax
xor edx,edx
mov ecx,65
div ecx
add eax,5
cmp eax,15
jbe @F
mov eax,15
@@:
mov coinHandPose3D,eax
cmp ebx,200
jb @F
sub ebx,200
shr ebx,1
cmp ebx,300
jbe coin_pose_withdraw
mov ebx,300
coin_pose_withdraw:
add coinHandY3D,ebx
@@:
cmp flightTime,COIN_FLIGHT_MS
jae coin_pose_landing
mov eax,flightTime
shl eax,16
xor edx,edx
mov ecx,COIN_FLIGHT_MS
div ecx
mov phase,eax
imul eax,94
shr eax,16
sub coinX3D,eax
; Ballistic height from the thumb to the felt, with no mid-air easing/hover.
mov eax,FP_1
sub eax,phase
mul phase
shrd eax,edx,16
imul eax,480
shr eax,16
mov ebx,FP_1
sub ebx,phase
imul ebx,78
shr ebx,16
add eax,ebx
mov ebx,coinGroundY3D
sub ebx,eax
mov coinY3D,ebx
shr eax,2
sub coinShadowAlpha3D,eax
mov eax,coin3d_result
dec eax
shl eax,8
add eax,2*512
sub eax,coinStartPhase3D
mul phase
shrd eax,edx,16
add eax,coinStartPhase3D
and eax,511
mov coinAngle3D,eax
mov ebx,tableCos[eax*4]
mov coinFace3D,0
test ebx,ebx
jns @F
neg ebx
mov coinFace3D,1
@@:
imul ebx,32
shr ebx,16
cmp ebx,3
jae @F
mov ebx,3
@@:
mov coinHeight3D,ebx
ret
coin_pose_landing:
mov coinX3D,320
mov coinY3D,344
mov coinGroundY3D,344
mov coinHeight3D,32
mov coinShadowAlpha3D,110
mov eax,coin3d_result
dec eax
mov coinFace3D,eax
shl eax,8
mov coinAngle3D,eax
mov eax,flightTime
sub eax,COIN_FLIGHT_MS
mov bounceTime,eax
cmp eax,60
jbe @F
mov eax,60
@@:
mov ebx,eax
imul eax,18
xor edx,edx
mov ecx,60
div ecx
sub coinHeight3D,eax
mov eax,ebx
imul eax,24
xor edx,edx
div ecx
add coinWidth3D,eax
mov eax,bounceTime
mov ebx,180
mov esi,72
cmp eax,180
jb coin_pose_bounce
sub eax,180
mov ebx,120
mov esi,24
cmp eax,120
jae coin_pose_done
coin_pose_bounce:
shl eax,16
xor edx,edx
div ebx
mov ecx,eax
neg eax
add eax,FP_1
mul ecx
shrd eax,edx,16
imul eax,esi
shr eax,16
sub coinY3D,eax
coin_pose_done:
ret
CoinPose3D ENDP
; Fractional shadow on actual framebuffer pixels, rather than a black rectangular sprite.
DrawCoinShadow3D PROC uses ebx esi edi centerX:DWORD,centerY:DWORD,radiusX:DWORD,radiusY:DWORD,opacity:DWORD
local row:DWORD,column:DWORD,dy2:DWORD,limit:DWORD,inverse:DWORD
mov eax,255
sub eax,opacity
mov inverse,eax
mov eax,radiusX
imul eax,eax
mov ebx,radiusY
imul ebx,ebx
imul eax,ebx
mov limit,eax
mov eax,centerY
sub eax,radiusY
mov row,eax
coin_shadow_row:
mov eax,row
sub eax,centerY
imul eax,eax
imul eax,radiusX
imul eax,radiusX
mov dy2,eax
mov eax,centerX
sub eax,radiusX
mov column,eax
coin_shadow_pixel:
cmp row,SCREEN_H
jae coin_shadow_next
cmp column,SCREEN_W
jae coin_shadow_next
mov eax,column
sub eax,centerX
imul eax,eax
imul eax,radiusY
imul eax,radiusY
add eax,dy2
cmp eax,limit
ja coin_shadow_next
mov edi,row
imul edi,SCREEN_W
add edi,column
lea edi,screenBuffer[edi*4]
mov esi,[edi]
xor ebx,ebx
xor ecx,ecx
coin_shadow_channel:
mov eax,esi
and eax,255
imul eax,inverse
add eax,128
mov edx,eax
shr edx,8
add eax,edx
shr eax,8
shl eax,cl
or ebx,eax
shr esi,8
add ecx,8
cmp ecx,24
jb coin_shadow_channel
mov [edi],ebx
coin_shadow_next:
inc column
mov eax,centerX
add eax,radiusX
cmp column,eax
jle coin_shadow_pixel
inc row
mov eax,centerY
add eax,radiusY
cmp row,eax
jle coin_shadow_row
ret
DrawCoinShadow3D ENDP
RenderNativeCoin3D PROC uses ebx esi edi
local x:DWORD,y:DWORD,tint:DWORD
cmp coinArtReady3D,1
jne coin_render_missing
mov esi,coinTableArt3D.pixels
lea edi,screenBuffer
mov ecx,SCREEN_SIZE
rep movsd
invoke CoinPose3D,coin3d_timer
cmp coin3d_timer,COIN_RELEASE_MS
jae coin_render_released
invoke DrawAnimSprite3D,OFFSET coinHandsArt3D,coinHandPose3D,320,coinHandY3D,240,240
jmp coin_render_hud
coin_render_released:
mov eax,coinGroundY3D
add eax,14
invoke DrawCoinShadow3D,coinX3D,eax,coinShadowRadius3D,5,coinShadowAlpha3D
invoke DrawAnimSprite3D,OFFSET coinHandsArt3D,coinHandPose3D,320,coinHandY3D,240,240
mov eax,coinWidth3D
shr eax,1
neg eax
add eax,coinX3D
mov x,eax
mov eax,coinHeight3D
shr eax,1
neg eax
add eax,coinY3D
mov y,eax
add eax,2
invoke DrawAnimSprite3D,OFFSET coinFacesArt3D,coinFace3D,x,eax,coinWidth3D,coinHeight3D
invoke DrawAnimSprite3D,OFFSET coinFacesArt3D,coinFace3D,x,y,coinWidth3D,coinHeight3D
coin_render_hud:
invoke DrawBox,0,0,320,20,0014191Dh
invoke DrawString8x8,12,6,OFFSET coinTitle3D,00E5D29Eh
invoke DrawBoxBorder,198,3,48,14,00252D2Ah,0086754Eh,1
invoke DrawString8x8,210,6,OFFSET coinDetailView3D,00E5D29Eh
invoke DrawBoxBorder,252,3,60,14,00252D2Ah,0086754Eh,1
invoke DrawString8x8,266,6,OFFSET coinExitText3D,00E5D29Eh
invoke DrawBox,4,23,138,15,0014191Dh
invoke DrawString8x8,8,26,OFFSET coinBankText3D,00E5D29Eh
invoke DrawNumber,60,26,bankroll,00FFFFFFh
invoke DrawBox,0,194,320,46,0014191Dh
cmp coin3d_state,COIN_TOSS
je coin_render_toss_hud
cmp coin3d_state,COIN_RESULT
je coin_render_result_hud
invoke DrawString8x8,8,202,OFFSET coinStakeText3D,00E5D29Eh
invoke DrawNumber,72,202,coin3d_bet,00FFFFFFh
invoke DrawBoxBorder,118,199,16,16,003B3024h,00C6AA66h,1
invoke DrawString8x8,122,203,OFFSET coinMinusText3D,00FFFFFFh
invoke DrawBoxBorder,140,199,16,16,003B3024h,00C6AA66h,1
invoke DrawString8x8,144,203,OFFSET coinPlusText3D,00FFFFFFh
cmp coinError3D,0
je @F
invoke DrawString8x8,178,202,OFFSET coinLowBankText3D,00E99383h
@@:
mov tint,0086754Eh
cmp coin3d_choice,1
jne @F
mov tint,00EAC96Dh
@@:
invoke DrawBoxBorder,8,219,90,18,00252D2Ah,tint,1
invoke DrawString8x8,34,224,OFFSET coinHeadsText3D,00F4E7C8h
mov tint,0086754Eh
cmp coin3d_choice,2
jne @F
mov tint,00EAC96Dh
@@:
invoke DrawBoxBorder,106,219,90,18,00252D2Ah,tint,1
invoke DrawString8x8,132,224,OFFSET coinTailsText3D,00F4E7C8h
invoke DrawBoxBorder,220,219,92,18,00423929h,00C6AA66h,1
invoke DrawString8x8,242,224,OFFSET coinFlipText3D,00FFFFFFh
jmp coin_render_done
coin_render_toss_hud:
mov eax,OFFSET coinTossText3D
cmp coin3d_timer,COIN_RELEASE_MS
jae @F
mov eax,OFFSET coinPrepareText3D
@@:
cmp coin3d_timer,COIN_LAND_MS
jb @F
mov eax,OFFSET coinLandingText3D
@@:
cmp coinExitQueued3D,0
je @F
mov eax,OFFSET coinLeavingText3D
@@:
invoke DrawString8x8,82,214,eax,00E5D29Eh
jmp coin_render_done
coin_render_result_hud:
mov eax,OFFSET coinHeadsText3D
cmp coin3d_result,1
je @F
mov eax,OFFSET coinTailsText3D
@@:
invoke DrawString8x8,8,202,eax,00E5D29Eh
invoke DrawString8x8,140,202,OFFSET coinReturnText3D,00E5D29Eh
invoke DrawNumber,212,202,coin3d_win,00FFFFFFh
invoke DrawBoxBorder,220,219,92,18,003B3024h,00C6AA66h,1
invoke DrawString8x8,238,224,OFFSET coinAgainText3D,00FFFFFFh
jmp coin_render_done
coin_render_missing:
invoke DrawBox,0,0,320,240,0014191Dh
invoke DrawString8x8,76,108,OFFSET coinMissingText3D,00E5D29Eh
coin_render_done:
call UpdateCoinDetail3D
ret
RenderNativeCoin3D ENDP
include src_graphics\coin_detail.inc

View file

@ -0,0 +1,58 @@
COIN_IDLE equ 0
COIN_TOSS equ 1
COIN_RESULT equ 2
COIN_RELEASE_MS equ 300
COIN_FLIGHT_MS equ 720
COIN_LAND_MS equ (COIN_RELEASE_MS+COIN_FLIGHT_MS)
COIN_TOTAL_MS equ 1700
InitNativeCoin3D PROTO
HandleNativeCoin3D PROTO
RenderNativeCoin3D PROTO
LoadNativeCoinArt3D PROTO
FreeNativeCoinArt3D PROTO
CoinEscape3D PROTO
CoinSettle3D PROTO
CoinPose3D PROTO :DWORD
DrawCoinShadow3D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
ValidateCityPack3D PROTO :DWORD,:DWORD,:DWORD,:DWORD
.data
coinTableArt3D AnimPack3D <>
coinFacesArt3D AnimPack3D <>
coinHandsArt3D AnimPack3D <>
coinTablePath3D db "assets\animations\coin\table-v1.bin",0
coinFacesPath3D db "assets\animations\coin\faces-v1.bin",0
coinHandsPath3D db "assets\animations\coin\hands-v2.bin",0
coinArtReady3D dd 0
coinExitQueued3D dd 0
coinStartPhase3D dd 0
coinAngle3D dd 0
coinX3D dd 433
coinY3D dd 313
coinGroundY3D dd 344
coinWidth3D dd 56
coinHeight3D dd 56
coinFace3D dd 0
coinHandPose3D dd 0
coinHandY3D dd 250
coinShadowRadius3D dd 30
coinShadowAlpha3D dd 70
coinTitle3D db 204,206,205,197,210,202,192,0
coinHeadsText3D db 206,208,168,203,0
coinTailsText3D db 208,197,216,202,192,0
coinFlipText3D db 193,208,206,209,206,202,0
coinAgainText3D db 197,217,168,32,208,192,199,0
coinExitText3D db 194,219,213,206,196,0
coinBankText3D db 193,192,205,202,32,36,0
coinStakeText3D db 209,210,192,194,202,192,0
coinReturnText3D db 194,219,207,203,192,210,192,0
coinLeavingText3D db 194,219,213,206,196,32,207,206,209,203,197,32,193,208,206,209,202,192,0
coinTossText3D db 194,32,207,206,203,168,210,197,0
coinLandingText3D db 207,208,200,199,197,204,203,197,205,200,197,0
coinPrepareText3D db 193,208,206,209,192,197,204,0
coinMissingText3D db 205,197,210,32,209,207,208,192,201,210,206,194,0
coinLowBankText3D db 204,192,203,206,32,212,200,216,197,202,0
coinPlusText3D db "+",0
coinMinusText3D db "-",0
coinError3D dd 0
include src_graphics\coin_detail_data.inc
.code

View file

@ -0,0 +1,570 @@
FreeNativeSlots3D PROC
invoke FreeAnimPack3D, OFFSET slotCabinetPack3D
invoke FreeAnimPack3D, OFFSET slotSymbolsPack3D
ret
FreeNativeSlots3D ENDP
InitNativeSlots3D PROC
invoke LoadAnimPack3D, OFFSET slotCabinetPack3D, OFFSET slotCabinetPath3D
test eax,eax
jz slot_init_fail
cmp slotCabinetPack3D.imageWidth,640
jne slot_init_fail
cmp slotCabinetPack3D.height,480
jne slot_init_fail
cmp slotCabinetPack3D.frames,1
jne slot_init_fail
invoke LoadAnimPack3D, OFFSET slotSymbolsPack3D, OFFSET slotSymbolsPath3D
test eax,eax
jz slot_init_fail
cmp slotSymbolsPack3D.imageWidth,64
jne slot_init_fail
cmp slotSymbolsPack3D.height,64
jne slot_init_fail
cmp slotSymbolsPack3D.frames,8
jne slot_init_fail
mov slotSpinState,SLOT_READY
mov slotSpinTimer,0
mov slotWinAmount,0
mov slotStake3D,0
mov slotLeaveAfter3D,0
mov slotPaytable3D,0
mov slotError3D,0
mov slotEffects3D,0
mov slotBetIndex3D,1
mov slotBet,50
mov cardExitActive3D,0
mov paused3D,0
call ClearKeys3D
mov eax,1
ret
slot_init_fail:
call FreeNativeSlots3D
xor eax,eax
ret
InitNativeSlots3D ENDP
; Results are sampled once at stake debit. Rendering never consumes RNG or pays.
StartNativeSlots3D PROC uses ebx esi
cmp slotSpinState,SLOT_SPIN
je slot_start_done
cmp slotPaytable3D,0
jne slot_start_done
cmp slotCabinetPack3D.pixels,0
je slot_start_done
cmp slotSymbolsPack3D.pixels,0
je slot_start_done
mov eax,slotBet
test eax,eax
jz slot_start_done
cmp eax,10000
ja slot_start_done
cmp eax,bankroll
ja slot_start_money
sub bankroll,eax
mov slotStake3D,eax
mov slotSpinState,SLOT_SPIN
mov slotSpinTimer,0
mov slotWinAmount,0
mov slotLeaveAfter3D,0
mov slotError3D,0
mov slotEffects3D,0
xor esi,esi
slot_start_reel:
mov eax,slotStops3D[esi*4]
mov slotStartStops3D[esi*4],eax
invoke RandomRange3D,20
mov slotTargets3D[esi*4],eax
mov edx,slotStrip3D[eax*4]
mov slotReel1[esi*4],edx
sub eax,slotStartStops3D[esi*4]
jns @F
add eax,20
@@:
add eax,slotCycles3D[esi*4]
shl eax,6
mov slotTravel3D[esi*4],eax
inc esi
cmp esi,3
jb slot_start_reel
call SaveProgress3D
invoke PlayCasinoSound3D, OFFSET soundSlotSpin,0,1
ret
slot_start_money:
mov slotError3D,1
slot_start_done:
ret
StartNativeSlots3D ENDP
; Gross multipliers. A cherry pair pays 2x, any other pair returns the stake.
SlotMultiplier3D PROC first:DWORD,second:DWORD,third:DWORD
mov eax,first
cmp eax,7
ja slot_multiplier_zero
cmp second,7
ja slot_multiplier_zero
cmp third,7
ja slot_multiplier_zero
cmp eax,second
jne slot_multiplier_other_pair
cmp eax,third
jne slot_multiplier_pair
mov eax,slotTriple3D[eax*4]
ret
slot_multiplier_other_pair:
cmp eax,third
je slot_multiplier_pair
mov eax,second
cmp eax,third
jne slot_multiplier_zero
slot_multiplier_pair:
cmp eax,1
jne @F
mov eax,2
ret
@@:
mov eax,1
ret
slot_multiplier_zero:
xor eax,eax
ret
SlotMultiplier3D ENDP
SettleNativeSlots3D PROC uses esi
cmp slotSpinState,SLOT_SPIN
jne slot_settle_done
invoke SlotMultiplier3D,slotReel1,slotReel2,slotReel3
mul slotStake3D
mov slotWinAmount,eax
add bankroll,eax
jc slot_settle_cap
cmp bankroll,1000000000
jbe @F
slot_settle_cap:
mov bankroll,1000000000
@@:
xor esi,esi
slot_settle_stops:
mov eax,slotTargets3D[esi*4]
mov slotStops3D[esi*4],eax
inc esi
cmp esi,3
jb slot_settle_stops
mov slotSpinTimer,3000
mov slotSpinState,SLOT_RESULT
mov slotEffects3D,0
call SaveProgress3D
cmp slotWinAmount,0
je slot_settle_loss
invoke PlayCasinoSound3D,OFFSET soundRoundWin,0,1
jmp slot_settle_exit
slot_settle_loss:
invoke PlayCasinoSound3D,OFFSET soundRoundLose,0,1
slot_settle_exit:
cmp slotLeaveAfter3D,0
je slot_settle_done
call SlotEscape3D
slot_settle_done:
ret
SettleNativeSlots3D ENDP
; Escape queues a safe exit during a paid spin; it never opens the pause menu.
SlotEscape3D PROC
mov paused3D,0
cmp slotPaytable3D,0
je @F
mov slotPaytable3D,0
call ClearKeys3D
ret
@@:
cmp slotSpinState,SLOT_SPIN
jne slot_exit_hall
mov slotLeaveAfter3D,1
call ClearKeys3D
ret
slot_exit_hall:
mov slotLeaveAfter3D,0
mov paused3D,0
mov engineMode,MODE_WALK
call ClearKeys3D
invoke PlayCasinoSound3D,0,0,0
ret
SlotEscape3D ENDP
; A monotonic quadratic ease-out maps elapsed milliseconds to strip pixels.
SlotReelPosition3D PROC uses ebx index:DWORD
mov ebx,index
cmp ebx,2
ja slot_position_zero
mov eax,slotStops3D[ebx*4]
shl eax,6
cmp slotSpinState,SLOT_SPIN
jne slot_position_done
mov eax,slotSpinTimer
cmp eax,slotDurations3D[ebx*4]
jae slot_position_end
shl eax,10
xor edx,edx
div slotDurations3D[ebx*4]
mov ecx,2048
sub ecx,eax
imul eax,ecx
mul slotTravel3D[ebx*4]
shrd eax,edx,20
mov edx,slotStartStops3D[ebx*4]
shl edx,6
add eax,edx
ret
slot_position_end:
mov eax,slotStartStops3D[ebx*4]
shl eax,6
add eax,slotTravel3D[ebx*4]
slot_position_done:
ret
slot_position_zero:
xor eax,eax
ret
SlotReelPosition3D ENDP
SlotExitMouse3D PROC
cmp mouseClick3D,0
je slot_exit_mouse_done
mov mouseClick3D,0
cmp mouseY3D,3
jb slot_exit_mouse_done
cmp mouseY3D,17
jae slot_exit_mouse_done
cmp mouseX3D,252
jb slot_exit_mouse_done
cmp mouseX3D,312
jae slot_exit_mouse_done
call SlotEscape3D
slot_exit_mouse_done:
ret
SlotExitMouse3D ENDP
HandleNativeSlots3D PROC uses ebx
cmp slotSpinState,SLOT_SPIN
jne slot_handle_idle
; Process the header before settlement so the final spin tick cannot eat it.
call SlotExitMouse3D
mov eax,deltaTime3D
cmp eax,250
jbe @F
mov eax,250
@@:
add slotSpinTimer,eax
cmp slotSpinTimer,3000
jb slot_handle_exit_mouse
call SettleNativeSlots3D
ret
slot_handle_idle:
mov eax,deltaTime3D
add slotEffects3D,eax
cmp slotEffects3D,2000
jbe @F
mov slotEffects3D,2000
@@:
cmp slotPaytable3D,0
je slot_handle_controls
invoke Pressed3D,0Dh
test eax,eax
jnz slot_close_paytable
invoke Pressed3D,20h
test eax,eax
jnz slot_close_paytable
invoke Pressed3D,50h
test eax,eax
jnz slot_close_paytable
cmp mouseClick3D,0
je slot_handle_done
mov mouseClick3D,0
cmp mouseY3D,202
jb slot_handle_done
cmp mouseY3D,220
jae slot_handle_done
cmp mouseX3D,112
jb slot_handle_done
cmp mouseX3D,208
jae slot_handle_done
slot_close_paytable:
mov slotPaytable3D,0
ret
slot_handle_controls:
cmp mouseClick3D,0
je slot_handle_keys
cmp mouseY3D,222
jb slot_handle_keys
cmp mouseY3D,238
jae slot_handle_keys
mov mouseClick3D,0
mov eax,mouseX3D
cmp eax,11
jb slot_handle_keys
cmp eax,29
jb slot_bet_down
cmp eax,35
jb slot_handle_keys
cmp eax,53
jb slot_bet_up
cmp eax,120
jb slot_handle_keys
cmp eax,190
jb slot_handle_start
cmp eax,202
jb slot_handle_keys
cmp eax,272
jae slot_handle_keys
mov slotPaytable3D,1
ret
slot_handle_keys:
invoke Pressed3D,50h
test eax,eax
jz @F
mov slotPaytable3D,1
ret
@@:
invoke Pressed3D,57h
test eax,eax
jnz slot_bet_up
invoke Pressed3D,26h
test eax,eax
jnz slot_bet_up
invoke Pressed3D,53h
test eax,eax
jnz slot_bet_down
invoke Pressed3D,28h
test eax,eax
jnz slot_bet_down
invoke Pressed3D,20h
test eax,eax
jnz slot_handle_start
invoke Pressed3D,0Dh
test eax,eax
jz slot_handle_exit_mouse
slot_handle_start:
call StartNativeSlots3D
ret
slot_bet_up:
cmp slotBetIndex3D,6
jae slot_handle_done
inc slotBetIndex3D
jmp slot_bet_changed
slot_bet_down:
cmp slotBetIndex3D,0
je slot_handle_done
dec slotBetIndex3D
slot_bet_changed:
mov ecx,slotBetIndex3D
mov eax,slotBetValues3D[ecx*4]
mov slotBet,eax
mov slotError3D,0
ret
slot_handle_exit_mouse:
call SlotExitMouse3D
slot_handle_done:
ret
HandleNativeSlots3D ENDP
; Clip the 64x64 alpha-tested symbol to its own reel window, not the cabinet.
DrawSlotSymbol3D PROC uses ebx esi edi symbol:DWORD,x:DWORD,y:DWORD
local row:DWORD,lastRow:DWORD,source:DWORD
cmp symbol,7
ja slot_symbol_done
mov eax,slotSymbolsPack3D.pixels
test eax,eax
jz slot_symbol_done
mov ecx,symbol
shl ecx,14
add eax,ecx
mov source,eax
mov eax,y
cmp eax,SLOT_TOP
jge @F
mov eax,SLOT_TOP
@@:
mov row,eax
mov eax,y
add eax,64
cmp eax,SLOT_BOTTOM
jle @F
mov eax,SLOT_BOTTOM
@@:
mov lastRow,eax
slot_symbol_row:
mov eax,row
cmp eax,lastRow
jge slot_symbol_done
mov ecx,eax
sub ecx,y
shl ecx,8
mov esi,source
add esi,ecx
imul eax,640*4
mov ecx,x
lea edi,screenBuffer[eax+ecx*4]
mov ecx,64
slot_symbol_column:
lodsd
test eax,0FF000000h
jz @F
and eax,00FFFFFFh
mov [edi],eax
@@:
add edi,4
dec ecx
jnz slot_symbol_column
inc row
jmp slot_symbol_row
slot_symbol_done:
ret
DrawSlotSymbol3D ENDP
DrawSlotReel3D PROC uses ebx esi edi index:DWORD
local offsetPx:DWORD,baseStop:DWORD,symbolY:DWORD,symbolX:DWORD
invoke SlotReelPosition3D,index
xor edx,edx
mov ecx,64
div ecx
mov offsetPx,edx
xor edx,edx
mov ecx,20
div ecx
mov baseStop,edx
mov ebx,index
mov eax,slotWindows3D[ebx*4]
add eax,12
mov symbolX,eax
mov eax,160
sub eax,offsetPx
sub eax,64
mov symbolY,eax
mov esi,baseStop
add esi,19
xor edi,edi
slot_reel_symbol:
mov eax,esi
xor edx,edx
mov ecx,20
div ecx
mov eax,slotStrip3D[edx*4]
invoke DrawSlotSymbol3D,eax,symbolX,symbolY
add symbolY,64
inc esi
inc edi
cmp edi,4
jb slot_reel_symbol
ret
DrawSlotReel3D ENDP
RenderNativeSlots3D PROC uses ebx esi edi
; The validated cabinet is opaque and already framebuffer-sized.
mov esi,slotCabinetPack3D.pixels
test esi,esi
jz slot_render_done
lea edi,screenBuffer
mov ecx,SCREEN_SIZE
rep movsd
xor esi,esi
slot_render_reels:
invoke DrawSlotReel3D,esi
inc esi
cmp esi,3
jb slot_render_reels
; The payline is fixed at the center of the three physical drums.
invoke DrawBox,86,94,4,3,00E9CE71h
invoke DrawBox,232,94,4,3,00E9CE71h
cmp slotSpinState,SLOT_RESULT
jne slot_render_header
cmp slotWinAmount,0
je slot_render_header
mov eax,slotEffects3D
xor edx,edx
mov ecx,200
div ecx
test eax,1
jnz slot_render_header
invoke DrawBox,90,94,142,1,00E9CE71h
slot_render_header:
invoke DrawBox,0,0,320,19,00151D20h
invoke DrawString8x8,8,6,OFFSET slotTitle3D,00E8D7ADh
invoke DrawString8x8,54,6,OFFSET textBank3D,00A8DDB9h
invoke FormatNumber,bankroll,OFFSET hudNumBuffer
invoke DrawString8x8,103,6,OFFSET hudNumBuffer,00F4F3E8h
invoke DrawBoxBorder,252,3,60,14,002C3637h,00687672h,1
invoke DrawString8x8,258,6,OFFSET cardExitButton3D,00E9D8B0h
mov ebx,OFFSET slotReadyText3D
cmp slotSpinState,SLOT_SPIN
jne @F
mov ebx,OFFSET slotRollingText3D
@@:
cmp slotSpinState,SLOT_RESULT
jne @F
mov ebx,OFFSET slotLoseText3D
cmp slotWinAmount,0
je @F
mov ebx,OFFSET slotWinText3D
@@:
cmp slotError3D,0
je @F
mov ebx,OFFSET slotNoMoney3D
@@:
cmp slotLeaveAfter3D,0
je @F
mov ebx,OFFSET slotExitText3D
@@:
; Keep the message on the generated inset plate, with fixed-size numerals.
invoke DrawBox,98,147,126,12,00151D20h
invoke DrawString8x8,104,149,ebx,00E8D7ADh
cmp slotSpinState,SLOT_RESULT
jne slot_render_controls
cmp slotWinAmount,0
je 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 FormatNumber,slotBet,OFFSET hudNumBuffer
invoke DrawString8x8,83,207,OFFSET hudNumBuffer,00F4F3E8h
mov ebx,1
cmp slotSpinState,SLOT_SPIN
jne @F
xor ebx,ebx
@@:
mov edi,006D7470h
test ebx,ebx
jz @F
mov edi,00EEE5CDh
@@:
invoke DrawBoxBorder,11,222,18,16,0025372Ch,edi,1
invoke DrawBoxBorder,35,222,18,16,0025372Ch,edi,1
invoke DrawString8x8,16,226,OFFSET tableMinusText3D,edi
invoke DrawString8x8,40,226,OFFSET tablePlusText3D,edi
invoke DrawTableButton3D,120,OFFSET slotSpinText3D,ebx
invoke DrawTableButton3D,202,OFFSET slotPayText3D,ebx
cmp slotPaytable3D,0
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
xor esi,esi
slot_render_pay_row:
mov edi,esi
imul edi,14
add edi,66
mov eax,slotSymbolNames3D[esi*4]
invoke DrawString8x8,62,edi,eax,00F4F3E8h
invoke FormatNumber,slotTriple3D[esi*4],OFFSET hudNumBuffer
invoke DrawString8x8,212,edi,OFFSET strMultiplier3D,00E8D7ADh
invoke DrawString8x8,220,edi,OFFSET hudNumBuffer,00E8D7ADh
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 DrawBoxBorder,112,204,96,15,003B6C59h,00E9D8B0h,1
invoke DrawString8x8,128,207,OFFSET slotCloseText3D,00E9D8B0h
slot_render_done:
ret
RenderNativeSlots3D ENDP

View file

@ -0,0 +1,69 @@
; One fixed 20-stop strip per reel. Only the middle payline pays.
SLOT_READY equ 0
SLOT_SPIN equ 1
SLOT_RESULT equ 2
SLOT_CELL equ 64
SLOT_TOP equ 124
SLOT_BOTTOM equ 260
InitNativeSlots3D PROTO
FreeNativeSlots3D PROTO
StartNativeSlots3D PROTO
HandleNativeSlots3D PROTO
RenderNativeSlots3D PROTO
SlotMultiplier3D PROTO :DWORD,:DWORD,:DWORD
SettleNativeSlots3D PROTO
SlotEscape3D PROTO
SlotExitMouse3D PROTO
SlotReelPosition3D PROTO :DWORD
DrawSlotReel3D PROTO :DWORD
DrawSlotSymbol3D PROTO :DWORD,:DWORD,:DWORD
.data
slotCabinetPack3D AnimPack3D <>
slotSymbolsPack3D AnimPack3D <>
slotCabinetPath3D db "assets\animations\slots\cabinet-v1.bin",0
slotSymbolsPath3D db "assets\animations\slots\symbols-v1.bin",0
slotStrip3D dd 1,2,5,3,1,4,2,5,1,3,0,2,1,4,5,3,6,2,1,7
slotTriple3D dd 100,8,10,15,25,12,60,150
slotDurations3D dd 1800,2400,3000
slotCycles3D dd 80,100,120
slotWindows3D dd 180,278,376
slotStops3D dd 0,6,12
slotStartStops3D dd 0,6,12
slotTravel3D dd 3 dup(0)
slotTargets3D dd 3 dup(0)
slotBetValues3D dd 10,50,100,500,1000,5000,10000
slotBetIndex3D dd 1
slotStake3D dd 0
slotLeaveAfter3D dd 0
slotPaytable3D dd 0
slotError3D dd 0
slotEffects3D dd 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
slotPayTitle3D db 210,192,193,203,200,214,192,32,194,219,207,203,192,210,0
slotCloseText3D db 199,192,202,208,219,210,220,0
slotBetText3D db 209,210,192,194,202,192,32,36,0
slotWinText3D db 194,219,207,203,192,210,192,0
slotReadyText3D db 195,206,210,206,194,0
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
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
slotPayNote3D db 209,210,192,194,202,192,32,194,202,203,222,215,197,205,192,32,194,32,194,219,207,203,192,210,211,0
slotSymbol0Name3D db "7",0
slotSymbol1Name3D db 194,200,216,205,223,0
slotSymbol2Name3D db 203,200,204,206,205,0
slotSymbol3Name3D db 202,206,203,206,202,206,203,0
slotSymbol4Name3D db "BAR",0
slotSymbol5Name3D db 194,200,205,206,195,208,192,196,0
slotSymbol6Name3D db 192,203,204,192,199,0
slotSymbol7Name3D db 202,206,208,206,205,192,0
slotSymbolNames3D dd OFFSET slotSymbol0Name3D,OFFSET slotSymbol1Name3D
dd OFFSET slotSymbol2Name3D,OFFSET slotSymbol3Name3D,OFFSET slotSymbol4Name3D
dd OFFSET slotSymbol5Name3D,OFFSET slotSymbol6Name3D,OFFSET slotSymbol7Name3D
.code

View file

@ -7,12 +7,14 @@ InitNativeWheel3D PROC uses edi
mov wheelStake3D, 0
mov wheelReturn3D, 0
mov wheelTime3D, 0
mov wheelExitQueued3D, 0
call LoadNativeWheelArt3D
ret
InitNativeWheel3D ENDP
WheelAddBet3D PROC index:DWORD
cmp roulState, 1
je wheel_add_done
cmp roulState, 0
jne wheel_add_done
mov ecx, index
cmp ecx, 48
ja wheel_add_done
@ -136,6 +138,21 @@ wheel_settle_done:
WheelSettle3D ENDP
HandleNativeWheel3D PROC uses ebx esi edi
cmp mouseClick3D,0
je wheel_header_done
cmp mouseY3D,3
jb wheel_header_done
cmp mouseY3D,17
jae wheel_header_done
cmp mouseX3D,252
jb wheel_header_done
cmp mouseX3D,312
jae wheel_header_done
mov mouseClick3D,0
call WheelEscape3D
cmp engineMode,MODE_WALK
je wheel_handle_done
wheel_header_done:
cmp roulState, 1
jne wheel_draft_input
mov eax, deltaTime3D
@ -143,17 +160,34 @@ HandleNativeWheel3D PROC uses ebx esi edi
cmp wheelTime3D, 4200
jb wheel_handle_done
call WheelSettle3D
cmp wheelExitQueued3D,0
je wheel_handle_done
call WheelEscape3D
ret
wheel_draft_input:
cmp roulState, 2
jne @F
; Keep last winnings visible, but a new spin begins with an empty layout.
lea edi, wheelBets3D
xor eax, eax
mov ecx, 49
rep stosd
mov wheelStake3D, 0
mov roulState, 0
; Keep the landed ball and payout visible until an explicit next-round command.
invoke Pressed3D,20h
test eax,eax
jnz wheel_next_layout
invoke Pressed3D,0Dh
test eax,eax
jnz wheel_next_layout
cmp mouseClick3D,0
je wheel_handle_done
cmp mouseY3D,219
jb wheel_handle_done
cmp mouseY3D,237
jae wheel_handle_done
cmp mouseX3D,120
jb wheel_handle_done
cmp mouseX3D,200
jae wheel_handle_done
wheel_next_layout:
mov mouseClick3D,0
call InitNativeWheel3D
ret
@@:
invoke Pressed3D, 57h
test eax, eax
@ -199,6 +233,8 @@ wheel_mouse_number:
sub eax, 123
xor edx, edx
div ecx
neg eax
add eax,2
add eax, ebx
inc eax
invoke WheelAddBet3D, eax
@ -231,13 +267,43 @@ wheel_button:
jb wheel_spin_input
cmp mouseY3D, 121
jae wheel_spin_input
cmp mouseX3D,145
jb wheel_spin_input
cmp mouseX3D,159
jb wheel_mouse_more
cmp mouseX3D,162
jb wheel_spin_input
cmp mouseX3D,176
jb wheel_mouse_less
cmp mouseX3D,180
jb wheel_spin_input
cmp mouseX3D,226
jb wheel_mouse_clear
cmp mouseX3D, 230
jb wheel_spin_input
cmp mouseX3D,300
jae wheel_spin_input
mov pressedKeys3D[20h], 1
jmp wheel_spin_input
wheel_mouse_more:
cmp wheelChip3D,1000000
jae wheel_handle_done
add wheelChip3D,50
ret
wheel_mouse_less:
cmp wheelChip3D,50
jbe wheel_handle_done
sub wheelChip3D,50
ret
wheel_mouse_clear:
call InitNativeWheel3D
ret
wheel_spin_input:
invoke Pressed3D, 20h
test eax, eax
jz wheel_handle_done
cmp wheelArtReady3D,1
jne wheel_handle_done
mov eax, wheelStake3D
test eax, eax
jz wheel_handle_done
@ -256,51 +322,28 @@ HandleNativeWheel3D ENDP
RenderNativeWheel3D PROC uses ebx esi edi
local x:DWORD,y:DWORD,ink:DWORD,angle:DWORD
invoke DrawBox,0,0,320,240,00142D22h
call RenderWheelScene3D
cmp wheelArtReady3D,1
jne wheel_render_missing
invoke DrawBox,0,0,320,20,0014191Dh
invoke DrawString8x8,16,12,OFFSET wheelTitle3D,00E5D29Eh
invoke DrawNumber,206,12,bankroll,00F4E7C8h
invoke DrawCircle,160,66,42,00886E42h
invoke DrawCircle,160,66,38,00232C29h
invoke DrawCircle,160,66,24,005E422Bh
xor esi, esi
wheel_result_slot:
mov eax, wheelOrder3D[esi*4]
cmp eax, wheelResult3D
je wheel_slot_found
inc esi
cmp esi, 37
jb wheel_result_slot
wheel_slot_found:
mov eax, esi
imul eax, 512
xor edx, edx
mov ecx, 37
div ecx
cmp roulState, 1
jne @F
mov ecx, 4200
sub ecx, wheelTime3D
imul ecx, 3
add eax, ecx
@@:
and eax, 511
mov angle, eax
mov ebx, tableCos[eax*4]
imul ebx, 34
sar ebx, 16
add ebx, 160
mov edx, tableSin[eax*4]
imul edx, 34
sar edx, 16
add edx, 66
invoke DrawCircle,ebx,edx,3,00F4EAD8h
invoke DrawNumber,153,60,wheelResult3D,00FFFFFFh
invoke DrawBoxBorder,252,3,60,14,00252D2Ah,0086754Eh,1
invoke DrawString8x8,266,6,OFFSET wheelExitText3D,00E5D29Eh
cmp roulState,1
je wheel_render_spin_hud
cmp roulState,2
je wheel_render_result_hud
invoke DrawBox,8,101,304,123,00142D22h
invoke DrawString8x8,18,104,OFFSET wheelAmountText3D,00DEDACAh
invoke DrawNumber,74,104,wheelChip3D,00FFFFFFh
invoke DrawString8x8,116,104,OFFSET wheelStakedText3D,00DEDACAh
invoke DrawNumber,180,104,wheelStake3D,00FFFFFFh
invoke DrawNumber,78,104,wheelChip3D,00FFFFFFh
invoke DrawBoxBorder,145,104,14,16,003B3024h,00C6AA66h,1
invoke DrawString8x8,148,108,OFFSET wheelPlusText3D,00FFFFFFh
invoke DrawBoxBorder,162,104,14,16,003B3024h,00C6AA66h,1
invoke DrawString8x8,165,108,OFFSET wheelMinusText3D,00FFFFFFh
invoke DrawBoxBorder,180,104,46,16,003B3024h,00C6AA66h,1
invoke DrawString8x8,183,108,OFFSET wheelClear3D,00FFFFFFh
invoke DrawBoxBorder,230,104,70,16,00423929h,00C6AA66h,1
invoke DrawString8x8,250,108,OFFSET wheelSpin3D,00FFFFFFh
invoke DrawString8x8,237,108,OFFSET wheelSpin3D,00FFFFFFh
invoke DrawBoxBorder,24,123,34,60,00174A33h,009C9A76h,1
invoke DrawNumber,36,147,0,00FFFFFFh
cmp wheelBets3D, 0
@ -317,6 +360,8 @@ wheel_render_numbers:
imul eax, 20
add eax, 60
mov x, eax
neg edx
add edx,2
imul edx, 20
add edx, 123
mov y, edx
@ -368,11 +413,489 @@ wheel_render_outside:
inc esi
cmp esi, 12
jb wheel_render_outside
invoke DrawString8x8,24,228,OFFSET wheelPaidText3D,00D3C08Fh
invoke DrawNumber,88,228,wheelReturn3D,00FFFFFFh
invoke DrawString8x8,24,228,OFFSET wheelStakedText3D,00D3C08Fh
invoke DrawNumber,88,228,wheelStake3D,00FFFFFFh
invoke DrawNumber,206,26,bankroll,00F4E7C8h
ret
wheel_render_spin_hud:
invoke DrawBox,0,220,320,20,0014191Dh
mov eax,OFFSET wheelSpinningText3D
cmp wheelExitQueued3D,0
je @F
mov eax,OFFSET wheelLeavingText3D
@@:
invoke DrawString8x8,76,226,eax,00E5D29Eh
ret
wheel_render_result_hud:
invoke DrawBox,0,219,320,21,0014191Dh
invoke DrawString8x8,8,225,OFFSET wheelResultText3D,00E5D29Eh
invoke DrawNumber,61,225,wheelResult3D,00FFFFFFh
invoke DrawBoxBorder,120,220,80,17,003B3024h,00BA9955h,1
invoke DrawString8x8,144,225,OFFSET wheelBetView3D,00FFFFFFh
invoke DrawString8x8,208,223,OFFSET wheelPaidText3D,00E5D29Eh
invoke DrawNumber,208,232,wheelReturn3D,00FFFFFFh
ret
wheel_render_missing:
invoke DrawBox,0,0,320,240,0014191Dh
invoke DrawString8x8,64,108,OFFSET wheelMissingArt3D,00E5D29Eh
ret
RenderNativeWheel3D ENDP
LoadNativeWheelArt3D PROC
cmp wheelArtReady3D,1
je wheel_art_loaded
invoke LoadAnimPack3D,OFFSET wheelTableArt3D,OFFSET wheelTablePath3D
invoke LoadAnimPack3D,OFFSET wheelCloseArt3D,OFFSET wheelClosePath3D
invoke LoadAnimPack3D,OFFSET wheelRotorArt3D,OFFSET wheelRotorPath3D
invoke ValidateCityPack3D,OFFSET wheelTableArt3D,640,480,1
invoke ValidateCityPack3D,OFFSET wheelCloseArt3D,640,480,1
invoke ValidateCityPack3D,OFFSET wheelRotorArt3D,480,480,1
cmp wheelTableArt3D.pixels,0
je wheel_art_failed
cmp wheelCloseArt3D.pixels,0
je wheel_art_failed
cmp wheelRotorArt3D.pixels,0
je wheel_art_failed
mov wheelArtReady3D,1
wheel_art_loaded:
mov eax,1
ret
wheel_art_failed:
call FreeNativeWheelArt3D
xor eax,eax
ret
LoadNativeWheelArt3D ENDP
FreeNativeWheelArt3D PROC
invoke FreeAnimPack3D,OFFSET wheelTableArt3D
invoke FreeAnimPack3D,OFFSET wheelCloseArt3D
invoke FreeAnimPack3D,OFFSET wheelRotorArt3D
mov wheelArtReady3D,0
ret
FreeNativeWheelArt3D ENDP
WheelEscape3D PROC
mov paused3D,0
cmp roulState,1
jne wheel_leave_hall
mov wheelExitQueued3D,1
call ClearKeys3D
ret
wheel_leave_hall:
mov wheelExitQueued3D,0
mov engineMode,MODE_WALK
call ClearKeys3D
invoke PlayCasinoSound3D,0,0,0
ret
WheelEscape3D ENDP
; Integer quadratic easing, preserving the 64-bit product until division.
WheelTravel3D PROC uses ebx timeMs:DWORD,durationMs:DWORD,total:DWORD
mov eax,timeMs
cmp eax,durationMs
jae wheel_travel_end
mov eax,durationMs
sub eax,timeMs
mul eax
mul total
mov ebx,durationMs
imul ebx,ebx
div ebx
neg eax
add eax,total
ret
wheel_travel_end:
mov eax,total
ret
WheelTravel3D ENDP
; Rotate the exact numbered rotor by inverse sampling, then project onto the bowl ellipse.
; Physical framebuffer coordinates; rendering cannot change the selected outcome or bank.
DrawNativeWheelDisc3D PROC uses ebx esi edi centerX:DWORD,centerY:DWORD,radiusX:DWORD,radiusY:DWORD,rotorAngle:DWORD
local row:DWORD,column:DWORD,minX:DWORD,maxX:DWORD,maxY:DWORD
local circleX:DWORD,circleY:DWORD,cosine:DWORD,sine:DWORD,sourceX:DWORD
mov eax,rotorAngle
and eax,511
mov ecx,tableCos[eax*4]
mov cosine,ecx
mov ecx,tableSin[eax*4]
mov sine,ecx
mov eax,centerX
sub eax,radiusX
jns @F
xor eax,eax
@@:
mov minX,eax
mov eax,centerX
add eax,radiusX
cmp eax,SCREEN_W
jl @F
mov eax,SCREEN_W-1
@@:
mov maxX,eax
mov eax,centerY
add eax,radiusY
cmp eax,SCREEN_H
jl @F
mov eax,SCREEN_H-1
@@:
mov maxY,eax
mov eax,centerY
sub eax,radiusY
jns @F
xor eax,eax
@@:
mov row,eax
wheel_disc_row:
mov eax,row
sub eax,centerY
imul eax,224
cdq
idiv radiusY
mov circleY,eax
mov eax,row
imul eax,SCREEN_W*4
add eax,OFFSET screenBuffer
mov ecx,minX
lea edi,[eax+ecx*4]
mov column,ecx
wheel_disc_pixel:
mov eax,column
sub eax,centerX
imul eax,224
cdq
idiv radiusX
mov circleX,eax
imul eax,eax
mov ecx,circleY
imul ecx,ecx
add eax,ecx
cmp eax,224*224
ja wheel_disc_next
mov eax,circleX
imul eax,cosine
mov ecx,circleY
imul ecx,sine
add eax,ecx
sar eax,16
add eax,240
mov sourceX,eax
mov eax,circleY
imul eax,cosine
mov ecx,circleX
imul ecx,sine
sub eax,ecx
sar eax,16
add eax,240
imul eax,480
add eax,sourceX
mov esi,wheelRotorArt3D.pixels
mov eax,[esi+eax*4]
cmp eax,80000000h
jb wheel_disc_next
and eax,00FFFFFFh
mov [edi],eax
wheel_disc_next:
add edi,4
inc column
mov eax,column
cmp eax,maxX
jle wheel_disc_pixel
inc row
mov eax,row
cmp eax,maxY
jle wheel_disc_row
ret
DrawNativeWheelDisc3D ENDP
RenderWheelScene3D PROC uses ebx esi edi
local centerX:DWORD,centerY:DWORD,radiusX:DWORD,radiusY:DWORD
local cameraPhase:DWORD,ballRadius:DWORD,ballAngle:DWORD,oldOpacity:DWORD
cmp wheelArtReady3D,1
jne wheel_scene_done
mov esi,wheelTableArt3D.pixels
lea edi,screenBuffer
mov ecx,SCREEN_SIZE
rep movsd
mov centerX,206
mov centerY,170
mov radiusX,142
mov radiusY,46
mov cameraPhase,0
cmp roulState,0
je wheel_scene_geometry
mov cameraPhase,FP_1
cmp roulState,1
jne wheel_scene_close
invoke CityEase3D,wheelTime3D,600
mov cameraPhase,eax
wheel_scene_close:
mov eax,animOpacity3D
mov oldOpacity,eax
mov eax,cameraPhase
imul eax,255
shr eax,16
mov animOpacity3D,eax
invoke DrawAnimSprite3D,OFFSET wheelCloseArt3D,0,0,0,640,480
mov eax,oldOpacity
mov animOpacity3D,eax
mov eax,cameraPhase
imul eax,114
shr eax,16
add centerX,eax
mov eax,cameraPhase
imul eax,108
shr eax,16
add centerY,eax
mov eax,cameraPhase
imul eax,63
shr eax,16
add radiusX,eax
mov eax,cameraPhase
imul eax,42
shr eax,16
add radiusY,eax
wheel_scene_geometry:
mov wheelRotorAngle3D,0
cmp roulState,1
jne @F
invoke WheelTravel3D,wheelTime3D,4200,4096
and eax,511
mov wheelRotorAngle3D,eax
@@:
invoke DrawNativeWheelDisc3D,centerX,centerY,radiusX,radiusY,wheelRotorAngle3D
cmp roulState,2
je @F
invoke DrawWheelLabels3D,centerX,centerY,radiusX,radiusY,wheelRotorAngle3D
@@:
cmp roulState,0
je wheel_scene_done
xor esi,esi
wheel_find_pocket:
mov eax,wheelOrder3D[esi*4]
cmp eax,wheelResult3D
je wheel_found_pocket
inc esi
cmp esi,37
jb wheel_find_pocket
jmp wheel_scene_done
wheel_found_pocket:
mov wheelResultSlot3D,esi
imul eax,esi,512
xor edx,edx
mov ecx,37
div ecx
add eax,wheelRotorAngle3D
mov ballAngle,eax
mov ballRadius,170
cmp roulState,1
jne wheel_ball_place
invoke WheelTravel3D,wheelTime3D,4200,6144
sub eax,6144
add ballAngle,eax
mov ballRadius,216
cmp wheelTime3D,2600
jb wheel_ball_place
mov eax,wheelTime3D
sub eax,2600
cmp eax,1600
jbe @F
mov eax,1600
@@:
imul eax,46
xor edx,edx
mov ecx,1600
div ecx
sub ballRadius,eax
wheel_ball_place:
mov eax,ballAngle
and eax,511
mov wheelBallAngle3D,eax
mov ebx,tableCos[eax*4]
imul ebx,ballRadius
sar ebx,16
imul ebx,radiusX
mov eax,ebx
cdq
mov ecx,224
idiv ecx
add eax,centerX
mov wheelBallX3D,eax
mov eax,wheelBallAngle3D
mov ebx,tableSin[eax*4]
imul ebx,ballRadius
sar ebx,16
imul ebx,radiusY
mov eax,ebx
cdq
idiv ecx
add eax,centerY
mov wheelBallY3D,eax
sar eax,1
mov edi,eax
mov ebx,wheelBallX3D
sar ebx,1
invoke DrawCircle,ebx,edi,3,003C3026h
invoke DrawCircle,ebx,edi,2,00F6EFE0h
cmp roulState,2
jne wheel_scene_done
invoke DrawWheelLabels3D,centerX,centerY,radiusX,radiusY,wheelRotorAngle3D
invoke DrawBuckshotPanel3D,278,245,84,61,002D211Bh
invoke DrawBuckshotText3D,296,253,OFFSET wheelLandedText3D,00E5D29Eh
mov ecx,00F4E7C8h
cmp wheelResult3D,0
je @F
mov eax,wheelResult3D
bt wheelRedMask3D,eax
jnc @F
mov ecx,00FFB4ADh
@@:
invoke DrawWheelValue3D,320,268,wheelResult3D,6,ecx
wheel_scene_done:
ret
RenderWheelScene3D 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 digit,9
ja wheel_digit_done
mov eax,digit
imul eax,5
lea esi,wheelDigitMasks3D[eax]
mov row,0
wheel_digit_row:
mov eax,row
movzx eax,BYTE PTR [esi+eax]
mov bits,eax
mov column,0
wheel_digit_column:
test bits,4
jz wheel_digit_next
mov eax,column
imul eax,scale
add eax,x
mov px,eax
mov eax,row
imul eax,scale
add eax,y
mov py,eax
invoke DrawBuckshotPanel3D,px,py,scale,scale,ink
wheel_digit_next:
shl bits,1
inc column
cmp column,3
jb wheel_digit_column
inc row
cmp row,5
jb wheel_digit_row
wheel_digit_done:
ret
DrawWheelDigit3D ENDP
DrawWheelValue3D PROC uses ebx centerX:DWORD,y:DWORD,value:DWORD,scale:DWORD,ink:DWORD
local x:DWORD,ones:DWORD
mov eax,scale
imul eax,3
cmp value,10
jb @F
mov eax,scale
imul eax,7
@@:
shr eax,1
neg eax
add eax,centerX
mov x,eax
mov eax,value
cmp eax,10
jb wheel_value_single
xor edx,edx
mov ecx,10
div ecx
mov ones,edx
invoke DrawWheelDigit3D,x,y,eax,scale,ink
mov eax,scale
shl eax,2
add x,eax
mov eax,ones
wheel_value_single:
invoke DrawWheelDigit3D,x,y,eax,scale,ink
ret
DrawWheelValue3D ENDP
DrawWheelLabels3D PROC uses ebx esi edi centerX:DWORD,centerY:DWORD,radiusX:DWORD,radiusY:DWORD,angle:DWORD
local phase:DWORD,x:DWORD,y:DWORD
mov wheelNumberScale3D,1
cmp radiusY,80
jb @F
mov wheelNumberScale3D,2
@@:
xor esi,esi
wheel_label_pocket:
mov eax,esi
imul eax,512
xor edx,edx
mov ecx,37
div ecx
add eax,angle
and eax,511
mov phase,eax
mov eax,tableCos[eax*4]
imul eax,195
sar eax,16
imul eax,radiusX
cdq
mov ecx,224
idiv ecx
add eax,centerX
mov x,eax
mov wheelNumberX3D[esi*4],eax
mov eax,phase
mov eax,tableSin[eax*4]
imul eax,195
sar eax,16
imul eax,radiusY
cdq
idiv ecx
add eax,centerY
mov wheelNumberY3D[esi*4],eax
mov ebx,wheelNumberScale3D
imul ebx,5
shr ebx,1
sub eax,ebx
mov y,eax
invoke DrawWheelValue3D,x,y,wheelOrder3D[esi*4],wheelNumberScale3D,00FFF8DEh
inc esi
cmp esi,37
jb wheel_label_pocket
cmp roulState,2
jne wheel_labels_done
; Highlight the actual winning label after drawing the ball, preserving every digit.
xor esi,esi
wheel_label_find_result:
mov eax,wheelOrder3D[esi*4]
cmp eax,wheelResult3D
je wheel_label_highlight
inc esi
cmp esi,37
jb wheel_label_find_result
jmp wheel_labels_done
wheel_label_highlight:
mov ebx,wheelNumberX3D[esi*4]
mov edi,wheelNumberY3D[esi*4]
sub ebx,9
sub edi,6
invoke DrawBuckshotPanel3D,ebx,edi,18,12,00E5C978h
inc ebx
inc edi
invoke DrawBuckshotPanel3D,ebx,edi,16,10,00121916h
mov eax,wheelNumberY3D[esi*4]
sub eax,5
invoke DrawWheelValue3D,wheelNumberX3D[esi*4],eax,wheelResult3D,2,00FFF8DEh
wheel_labels_done:
ret
DrawWheelLabels3D ENDP
DrawNumber PROC x:DWORD,y:DWORD,value:DWORD,ink:DWORD
invoke FormatNumber,value,OFFSET hudNumBuffer
invoke DrawString8x8,x,y,OFFSET hudNumBuffer,ink

View file

@ -3,7 +3,44 @@ HandleNativeWheel3D PROTO
RenderNativeWheel3D PROTO
WheelSettle3D PROTO
WheelAddBet3D PROTO :DWORD
LoadNativeWheelArt3D PROTO
FreeNativeWheelArt3D PROTO
WheelEscape3D PROTO
WheelTravel3D PROTO :DWORD,:DWORD,:DWORD
DrawNativeWheelDisc3D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
RenderWheelScene3D PROTO
DrawWheelDigit3D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
DrawWheelValue3D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
DrawWheelLabels3D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
.data
wheelTableArt3D AnimPack3D <>
wheelCloseArt3D AnimPack3D <>
wheelRotorArt3D AnimPack3D <>
wheelTablePath3D db "assets\animations\wheel\table-v1.bin",0
wheelClosePath3D db "assets\animations\wheel\closeup-v1.bin",0
wheelRotorPath3D db "assets\animations\wheel\rotor-v2.bin",0
wheelArtReady3D dd 0
wheelExitQueued3D dd 0
wheelBallAngle3D dd 0
wheelRotorAngle3D dd 0
wheelResultSlot3D dd 0
wheelBallX3D dd 0
wheelBallY3D dd 0
wheelNumberX3D dd 37 dup(0)
wheelNumberY3D dd 37 dup(0)
wheelNumberScale3D dd 1
wheelDigitMasks3D db 7,5,5,5,7, 2,6,2,2,7, 7,1,7,4,7, 7,1,7,1,7
db 5,5,7,1,1, 7,4,7,1,7, 7,4,7,5,7, 7,1,2,2,2
db 7,5,7,5,7, 7,5,7,1,7
wheelLandedText3D db 194,219,207,192,203,206,0
wheelBetView3D db 209,210,192,194,202,200,0
wheelExitText3D db 194,219,213,206,196,0
wheelSpinningText3D db 209,210,192,194,202,200,32,199,192,202,208,219,210,219,0
wheelLeavingText3D db 194,219,213,206,196,32,207,206,209,203,197,32,194,208,192,217,197,205,200,223,0
wheelResultText3D db 215,200,209,203,206,0
wheelMissingArt3D db 205,197,210,32,209,207,208,192,201,210,206,194,32,208,211,203,197,210,202,200,0
wheelPlusText3D db "+",0
wheelMinusText3D db "-",0
wheelBets3D dd 49 dup(0)
wheelStake3D dd 0
wheelChip3D dd 50
@ -14,22 +51,22 @@ wheelOrder3D dd 0,32,15,19,4,21,2,25,17,34,6,27,13,36,11,30,8,23,10,5,24,16,33,1
wheelRedMask3D dd 4AAD52AAh,15h
DrawNumber PROTO :DWORD,:DWORD,:DWORD,:DWORD
wheelLabels3D dd OFFSET wheelRed3D,OFFSET wheelBlack3D,OFFSET wheelEven3D,OFFSET wheelOdd3D,OFFSET wheelLow3D,OFFSET wheelHigh3D,OFFSET wheelDozen13D,OFFSET wheelDozen23D,OFFSET wheelDozen33D,OFFSET wheelColumn13D,OFFSET wheelColumn23D,OFFSET wheelColumn33D
wheelRed3D db "RED",0
wheelBlack3D db "BLACK",0
wheelEven3D db "EVEN",0
wheelOdd3D db "ODD",0
wheelRed3D db 202,208,192,209,205,0
wheelBlack3D db 215,168,208,205,0
wheelEven3D db 215,168,210,0
wheelOdd3D db 205,197,215,168,210,0
wheelLow3D db "1-18",0
wheelHigh3D db "19-36",0
wheelDozen13D db "1-12",0
wheelDozen23D db "13-24",0
wheelDozen33D db "25-36",0
wheelColumn13D db "COL 1",0
wheelColumn23D db "COL 2",0
wheelColumn33D db "COL 3",0
wheelTitle3D db "EUROPEAN ROULETTE",0
wheelSpin3D db "SPIN",0
wheelClear3D db "CLEAR",0
wheelAmountText3D db "CHIP $",0
wheelStakedText3D db "STAKE $",0
wheelPaidText3D db "RETURN $",0
wheelColumn13D db 208,223,196,32,49,0
wheelColumn23D db 208,223,196,32,50,0
wheelColumn33D db 208,223,196,32,51,0
wheelTitle3D db 197,194,208,206,207,197,201,209,202,192,223,32,208,211,203,197,210,202,192,0
wheelSpin3D db 202,208,211,210,200,210,220,0
wheelClear3D db 209,193,208,206,209,0
wheelAmountText3D db 212,200,216,202,192,32,36,0
wheelStakedText3D db 209,210,192,194,202,192,32,36,0
wheelPaidText3D db 194,219,207,203,192,210,192,0
.code

View file

@ -1,4 +1,4 @@
; Events are processed on one thread. No secondary window or console is created.
; Main and owned coin-detail windows are processed on the same thread; no CMD fallback.
WindowEvents3D PROC uses ebx esi edi hwnd:DWORD, uMsg:DWORD, wp:DWORD, lp:DWORD
cmp uMsg, 102h
jne @F
@ -379,6 +379,10 @@ Present3D ENDP
ToggleFullscreen3D PROC uses ebx esi edi
local visibleStyle:DWORD
local detailOpen:DWORD
mov eax,coinDetailWnd3D
mov detailOpen,eax
call CloseCoinDetail3D
invoke GetWindowLongA, h3DWnd, -16
and eax, 10000000h
mov visibleStyle, eax
@ -398,7 +402,7 @@ ToggleFullscreen3D PROC uses ebx esi edi
sub ebx, monitor3D[8]
invoke SetWindowPos, h3DWnd, 0, monitor3D[4], monitor3D[8], eax, ebx, 24h
mov fullscreen3D, 1
ret
jmp fullscreen_done
restore_window:
mov eax, visibleStyle
or eax, WS_OVERLAPPEDWINDOW
@ -410,6 +414,10 @@ restore_window:
invoke SetWindowPos, h3DWnd, 0, savedRect3D, savedRect3D[4], eax, ebx, 24h
mov fullscreen3D, 0
fullscreen_done:
cmp detailOpen,0
je @F
call OpenCoinDetail3D
@@:
ret
ToggleFullscreen3D ENDP

View file

@ -69,60 +69,66 @@ savePath3D db "runtime\casino3d-progress.dat",0
saveData3D dd 32443343h, 10000, 0, 0
saveIo3D dd 0
previewBackMap3D dd 0,3,5,2
textPause3D db "CASINO / SETTINGS",0
textResume3D db "ESC - RESUME",0
textQuit3D db "Q - SAVE AND EXIT",0
textDisplayFill3D db "1 DISPLAY: FULL SCREEN",0
textDisplayFit3D db "1 DISPLAY: CLASSIC 4:3",0
textLowSpec3D db "2 PERFORMANCE LEVEL",0
textSoundLevel3D db "VOLUME",0
textVolumeKeys3D db "+ / - CHANGE VOLUME",0
textFullscreenKey3D db "F11 FULLSCREEN / WINDOW",0
textLowSpecOff3D db "OFF",0
textLowSpecEco3D db "ECO",0
textLowSpecQuiet3D db "QUIET",0
textMissing3D db "Cannot load assets/textures/casino3d-hd.bin. Keep the assets folder next to the game.",0
textPause3D db 202,192,199,200,205,206,32,47,32,205,192,209,210,208,206,201,202,200,0
textResume3D db 69,83,67,32,45,32,207,208,206,196,206,203,198,200,210,220,0
textQuit3D db 81,32,45,32,209,206,213,208,192,205,200,210,220,32,200,32,194,219,201,210,200,0
textDisplayFill3D db 49,32,221,202,208,192,205,58,32,194,197,209,220,32,204,206,205,200,210,206,208,0
textDisplayFit3D db 49,32,221,202,208,192,205,58,32,202,203,192,209,209,200,202,192,32,52,58,51,0
textLowSpec3D db 50,32,208,197,198,200,204,0
textSoundLevel3D db 195,208,206,204,202,206,209,210,220,0
textVolumeKeys3D db 43,32,47,32,45,32,195,208,206,204,202,206,209,210,220,0
textFullscreenKey3D db 70,49,49,32,207,206,203,205,219,201,32,221,202,208,192,205,32,47,32,206,202,205,206,0
textLowSpecOff3D db 207,206,203,205,219,201,0
textLowSpecEco3D db 221,202,206,0
textLowSpecQuiet3D db 210,200,213,200,201,0
textMissing3D db 205,229,32,243,228,224,235,238,241,252,32,231,224,227,240,243,231,232,242,252,32,97,115,115,101,116,115,47,116,101,120,116
db 117,114,101,115,47,99,97,115,105,110,111,51,100,45,104,100,46,98,105,110,46,32,207,224,239,234,224,32,97,115,115,101
db 116,115,32,228,238,235,230,237,224,32,237,224,245,238,228,232,242,252,241,255,32,240,255,228,238,236,32,241,32,232,227,240
db 238,233,46,0
mutexName3D db "Local\AssemblerCasinoGraphical",0
user32Name3D db "user32.dll",0
dpiName3D db "SetProcessDPIAware",0
textWalkKeys3D db "W/S: MOVE A/D: TURN E: USE",0
textWindowKeys3D db "F11 FULL / ESC",0
textBank3D db "BANK $",0
textTip3D db "TIP $100",0
textUnavailable3D db "THIS TABLE IS NOT PORTED YET",0
textSlots3D db "777 / SLOT MACHINE",0
textSlotsKeys3D db "SPACE: SPIN UP/DOWN: BET",0
textBack3D db "ESC: BACK TO CASINO",0
textBet3D db "BET $",0
textDealer3D db "DEALER",0
textPlayer3D db "YOUR HAND",0
textBJKeys3D db "H: HIT S: STAND",0
textDeal3D db "SPACE: DEAL UP/DOWN: BET",0
textNext3D db "SPACE: NEXT HAND",0
textRed3D db "1 RED",0
textBlack3D db "2 BLACK",0
textZero3D db "3 ZERO",0
textEven3D db "4 EVEN",0
textOdd3D db "5 ODD",0
textSpin3D db "SPACE: SPIN ESC: LEAVE",0
textResult3D db "NUMBER",0
textShop3D db "CASINO BOUTIQUE",0
textItem1_3D db "1 GOLD CARD BACK - $1000",0
textItem2_3D db "2 EMERALD BACK - $1500",0
textItem3_3D db "3 CRIMSON BACK - $1500",0
textShopHint3D db "BUY ONCE / PRESS AGAIN TO EQUIP",0
textBought3D db "CARD BACK EQUIPPED",0
textWalkKeys3D db 87,47,83,58,32,213,206,196,32,32,65,47,68,58,32,207,206,194,206,208,206,210,32,32,69,58,32,196,197,201,209,210
db 194,200,197,0
textWindowKeys3D db 70,49,49,32,221,202,208,192,205,32,47,32,69,83,67,0
textBank3D db 193,192,205,202,32,36,0
textTip3D db 215,192,197,194,219,197,32,36,49,48,48,0
textUnavailable3D db 207,206,202,192,32,205,197,32,208,197,192,203,200,199,206,194,192,205,206,0
textSlots3D db 55,55,55,32,47,32,192,194,210,206,204,192,210,0
textSlotsKeys3D db 207,208,206,193,197,203,58,32,200,195,208,192,32,32,194,194,197,208,213,47,194,205,200,199,58,32,209,210,192,194,202,192
db 0
textBack3D db 69,83,67,58,32,205,192,199,192,196,32,194,32,202,192,199,200,205,206,0
textBet3D db 209,210,192,194,202,192,32,36,0
textDealer3D db 196,200,203,197,208,0
textPlayer3D db 194,192,216,200,32,202,192,208,210,219,0
textBJKeys3D db 72,58,32,202,192,208,210,192,32,32,83,58,32,213,194,192,210,200,210,0
textDeal3D db 207,208,206,193,197,203,58,32,209,196,192,210,220,32,32,194,194,197,208,213,47,194,205,200,199,58,32,209,210,192,194,202
db 192,0
textNext3D db 207,208,206,193,197,203,58,32,209,203,197,196,211,222,217,192,223,32,208,192,199,196,192,215,192,0
textRed3D db 49,32,202,208,192,209,205,206,197,0
textBlack3D db 50,32,215,168,208,205,206,197,0
textZero3D db 51,32,205,206,203,220,0
textEven3D db 52,32,215,168,210,0
textOdd3D db 53,32,205,197,215,168,210,0
textSpin3D db 207,208,206,193,197,203,58,32,200,195,208,192,32,32,69,83,67,58,32,194,219,213,206,196,0
textResult3D db 215,200,209,203,206,0
textShop3D db 204,192,195,192,199,200,205,32,202,192,199,200,205,206,0
textItem1_3D db 49,32,199,206,203,206,210,192,223,32,208,211,193,192,216,202,192,32,45,32,36,49,48,48,48,0
textItem2_3D db 50,32,200,199,211,204,208,211,196,205,192,223,32,208,211,193,192,216,202,192,32,45,32,36,49,53,48,48,0
textItem3_3D db 51,32,193,192,195,208,206,194,192,223,32,208,211,193,192,216,202,192,32,45,32,36,49,53,48,48,0
textShopHint3D db 202,211,207,200,210,220,32,47,32,194,219,193,208,192,210,220,32,208,211,193,192,216,202,211,0
textBought3D db 208,211,193,192,216,202,192,32,194,219,193,208,192,205,192,0
ownedBacks3D dd 1
selectedBack3D dd 0
cardBackColors3D dd 00434A4Dh,00244780h,009A3248h,00B79835h,0031252Bh,002C7958h
dd 00654782h,00202528h,002D6184h,0085663Dh,009D8532h,00276E76h,00613749h
textATM3D db "CASHIER",0
textATMNote3D db "BALANCE IS SAVED ON EXIT",0
textATMNote2_3D db "SEPARATE 2.5D PREVIEW PROFILE",0
textNoCash3D db "NOT ENOUGH CHIPS",0
textWin3D db "WIN",0
textLose3D db "LOSS",0
textTie3D db "PUSH",0
textBJ3D db "21 / BLACKJACK",0
textTips3D db "THANK YOU FOR THE TIP",0
textATM3D db 202,192,209,209,192,0
textATMNote3D db 193,192,203,192,205,209,32,209,206,213,208,192,205,223,197,210,209,223,32,207,208,200,32,194,219,213,206,196,197,0
textATMNote2_3D db 203,206,202,192,203,220,205,219,201,32,207,208,206,212,200,203,220,32,200,195,208,206,202,192,0
textNoCash3D db 205,197,196,206,209,210,192,210,206,215,205,206,32,212,200,216,197,202,0
textWin3D db 194,219,200,195,208,219,216,0
textLose3D db 207,208,206,200,195,208,219,216,0
textTie3D db 205,200,215,220,223,0
textBJ3D db 50,49,32,47,32,193,203,221,202,196,198,197,202,0
textTips3D db 209,207,192,209,200,193,206,32,199,192,32,215,192,197,194,219,197,0
.code

View file

@ -113,7 +113,7 @@ rr_table_hud:
invoke DrawBuckshotText3D,16,414,OFFSET rrCartridgeText3D,00EBD5A0h
invoke DrawBuckshotNumber3D,160,414,rr3d_bullets,00FFFFFFh
invoke DrawBuckshotText3D,456,414,OFFSET bs3d_textBet,00EBD5A0h
invoke DrawBuckshotNumber3D,512,414,rr3d_bet,00FFFFFFh
invoke DrawBuckshotNumber3D,528,414,rr3d_bet,00FFFFFFh
mov ebx,OFFSET rrPlayerText3D
cmp rr3d_turn,0
je @F

View file

@ -23,7 +23,7 @@ startupPixels3D dd 0
startupHeap3D dd 0
startupHeader3D dd 4 dup(0)
startupPath3D db "assets\animations\intro\eblan-games.bin",0
startupEnter3D db "ENTER",0
startupEnter3D db 69,78,84,69,82,0
startupName3D db "EBLAN",0
startupGames3D db "GAMES",0
.code

View file

@ -0,0 +1,35 @@
diff --git a/casino3d.inc b/casino3d.inc
--- a/casino3d.inc
+++ b/casino3d.inc
@@ -154,1 +154,2 @@
include src_graphics\native_slots_data.inc
+include src_graphics\native_coin_data.inc
@@ -2650,7 +2651,4 @@
InitCoinGame PROC
- mov coin3d_state, 0
- mov coin3d_timer, 0
- mov coin3d_streak, 0
- mov coin3d_win, 0
+ call InitNativeCoin3D
ret
InitCoinGame ENDP
@@ -2658,1 +2656,5 @@
HandleCoinInput PROC
+ call HandleNativeCoin3D
+ ret
+; Retained legacy frame-count prototype, excluded from the executable.
+IF 0
@@ -2779,1 +2781,2 @@
+ENDIF
HandleCoinInput ENDP
@@ -2786,1 +2789,4 @@
+ call RenderNativeCoin3D
+ ret
+IF 0
; Background & Cabinet border
@@ -2898,1 +2904,2 @@
+ENDIF
RenderCoinGame ENDP
@@ -5107,1 +5114,2 @@
include src_graphics\native_slots.inc
+include src_graphics\native_coin.inc

View file

@ -0,0 +1,46 @@
"""Offline, byte-preserving replacement of explicitly listed MASM UI labels."""
import argparse
import json
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
MAP = ROOT / "assets/data/ui-ru.json"
def compile_ui(check=False):
translations = json.loads(MAP.read_text(encoding="utf-8"))
declarations = re.compile(rb"^(\w+)([ \t]+db[ \t]+)([^\r\n]+(?:\r?\n[ \t]+db[ \t]+[^\r\n]+)*)", re.M)
seen = set()
changes = []
paths = [ROOT / "casino3d.inc", *sorted((ROOT / "src_graphics").glob("*_data.inc"))]
for path in paths:
original = path.read_bytes()
def replace(match):
symbol = match[1].decode("ascii")
if symbol not in translations:
return match[0]
assert symbol not in seen, f"Duplicate declaration: {symbol}"
seen.add(symbol)
values = [*translations[symbol].encode("cp1251"), 0]
chunks = [values[i:i + 32] for i in range(0, len(values), 32)]
newline = b"\r\n" if b"\r\n" in original else b"\n"
encoded = (newline + b" db ").join(",".join(map(str, chunk)).encode("ascii") for chunk in chunks)
return match[1] + match[2] + encoded
updated = declarations.sub(replace, original)
if updated != original:
changes.append(path.relative_to(ROOT).as_posix())
if not check:
path.write_bytes(updated)
assert seen == translations.keys(), f"Missing labels: {translations.keys() - seen}"
if check:
assert not changes, f"Translation not compiled: {changes}"
print(f"PASS: {len(seen)} Russian labels, CP1251 bytes, {len(changes)} files updated")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--check", action="store_true")
compile_ui(parser.parse_args().check)

View file

@ -0,0 +1,110 @@
"""Normalize generated art offline; all toss animation and play are MASM."""
import hashlib
import json
from collections import deque
from pathlib import Path
from PIL import Image
import pack_arrival_assets as packs
ROOT = Path(__file__).resolve().parents[2]
SOURCE = ROOT / "assets/sprites/coin"
def isolated_hand(frame):
"""Remove disconnected previous-row cuff spill without changing the hand anchor."""
width, height = frame.size
alpha = frame.getchannel("A").tobytes()
visited = bytearray(width * height)
largest = []
for seed, value in enumerate(alpha):
if not value or visited[seed]:
continue
visited[seed] = 1
queue = deque([seed])
component = []
while queue:
at = queue.popleft()
component.append(at)
x, y = at % width, at // width
for dx, dy in ((-1, 0), (1, 0), (0, -1), (0, 1)):
nx, ny = x + dx, y + dy
if 0 <= nx < width and 0 <= ny < height:
neighbor = ny * width + nx
if alpha[neighbor] and not visited[neighbor]:
visited[neighbor] = 1
queue.append(neighbor)
if len(component) > len(largest):
largest = component
mask = bytearray(width * height)
for at in largest:
mask[at] = 255
frame.putalpha(Image.frombytes("L", frame.size, bytes(mask)))
return frame
def main():
packs.OUTPUT = ROOT / "assets/animations/coin"
preview = ROOT / "build/coin"
preview.mkdir(parents=True, exist_ok=True)
background = Image.open(SOURCE / "table-v1.png").convert("RGBA")
background = background.resize((640, 480), Image.Resampling.NEAREST)
# Reserve the renderer's 40-pixel header without cropping the dealer's head.
framed = Image.new("RGBA", (640, 480), (20, 25, 29, 255))
framed.paste(background.crop((0, 0, 640, 440)), (0, 40))
background = framed
background.putalpha(255)
packs.write_pack("table-v1.bin", [background])
background.save(preview / "table.png")
source = Image.open(SOURCE / "faces-v1.png").convert("RGBA")
assert source.getchannel("A").getextrema()[0] == 0
faces = []
for column in range(2):
frame = source.crop((column * source.width // 2, 0,
(column + 1) * source.width // 2, source.height))
frame = frame.crop(frame.getchannel("A").getbbox())
frame = frame.resize((128, 128), Image.Resampling.NEAREST)
frame = packs.solid_alpha(frame)
faces.append(frame)
frame.save(preview / ("face-" + str(column) + ".png"))
packs.write_pack("faces-v1.bin", faces)
source = Image.open(SOURCE / "hands-v2.png").convert("RGBA")
assert source.getchannel("A").getextrema()[0] == 0
hands = []
held_centers = [(79, 70), (76, 77), (74, 82), (77, 96), (70, 98)]
for row in range(4):
for column in range(4):
frame = source.crop((column * source.width // 4, row * source.height // 4,
(column + 1) * source.width // 4, (row + 1) * source.height // 4))
# Keep common cell scale rather than stretching each pose's alpha bounds.
frame = frame.resize((224, 224), Image.Resampling.NEAREST)
frame = packs.solid_alpha(frame)
# Remove the generator's saturated red alpha-matte fringe, not skin highlights.
frame.putdata([(r, g, b, 0 if r > 120 and g < 50 and b < 50 else a)
for r, g, b, a in frame.get_flattened_data()])
index = len(hands)
if index >= len(held_centers):
frame = isolated_hand(frame)
if index < len(held_centers):
# The held coin and fingers share one sprite and the release-point anchor.
cx, cy = held_centers[index]
aligned = Image.new("RGBA", (224, 224))
aligned.paste(frame, (88 - cx, 24 - cy))
frame = aligned
frame.putdata([(r, g, b, a) if a else (0, 0, 0, 0)
for r, g, b, a in frame.get_flattened_data()])
assert frame.getchannel("A").getbbox()
hands.append(frame)
frame.save(preview / ("hand-" + str(len(hands) - 1) + ".png"))
assert len({hashlib.sha256(frame.tobytes()).hexdigest() for frame in hands}) == 16
packs.write_pack("hands-v2.bin", hands)
(packs.OUTPUT / "manifest-v1.json").write_text(json.dumps({
"table": [640, 480, 1], "faces": [128, 128, 2], "hands": [224, 224, 16],
"hand_anchor": "held coin (88,24); empty hand shared grid cell",
"held_frames": 5, "alpha": "opaque table, binary cutouts"
}, indent=2) + "\n", encoding="ascii")
print("Packed coin table, two embossed sides and sixteen distinct hand poses")
if __name__ == "__main__":
main()

View file

@ -0,0 +1,78 @@
"""Offline scene packing and exact 37-pocket rotor for the MASM roulette renderer."""
import json
import math
import argparse
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
import pack_arrival_assets as packs
ROOT = Path(__file__).resolve().parents[2]
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}
def rotor(numbered=True):
im = Image.new("RGBA", (512, 512))
pixels = im.load()
for y in range(512):
for x in range(512):
dx, dy = x - 256, y - 256
radius = math.hypot(dx, dy)
if radius > 240:
continue
noise = ((x * 193 + y * 37 + (x ^ y) * 13) % 17) - 8
base = (82, 41, 25)
if radius >= 236 or 181 <= radius < 185 or 225 <= radius < 229:
base = (199, 158, 74)
elif radius >= 185:
angle = math.atan2(dy, dx) % math.tau
slot = int((angle / math.tau * 37 + .5) % 37)
value = ORDER[slot]
base = (20, 95, 56) if value == 0 else (
(132, 32, 38) if value in RED else (25, 30, 28))
boundary = abs((angle / math.tau * 37 + .5) % 1 - .5)
if boundary > .465:
base = (211, 176, 94)
elif radius < 28:
base = (191, 145, 58) if radius > 22 else (234, 207, 137)
pixels[x, y] = tuple(max(0, min(255, c + noise)) for c in base) + (255,)
if not numbered:
return im
font = ImageFont.truetype("C:/Windows/Fonts/arialbd.ttf", 20)
for index, value in enumerate(ORDER):
angle = index * math.tau / 37
label = Image.new("RGBA", (44, 26))
draw = ImageDraw.Draw(label)
draw.text((22, 13), str(value), font=font, anchor="mm", fill=(247, 238, 211))
label = label.rotate(-index * 360 / 37 - 90, expand=True, resample=Image.Resampling.NEAREST)
cx, cy = round(256 + 208 * math.cos(angle)), round(256 + 208 * math.sin(angle))
im.alpha_composite(label, (cx - label.width // 2, cy - label.height // 2))
return im
def main(rotor_only=False):
packs.OUTPUT = ROOT / "assets/animations/wheel"
preview = ROOT / "build/wheel"
preview.mkdir(parents=True, exist_ok=True)
manifest = {"order": ORDER, "zero_pockets": 1}
for name in (() if rotor_only else ("table", "closeup")):
image = Image.open(ROOT / "assets/sprites/wheel" / (name + "-v1.png")).convert("RGBA")
image = image.resize((640, 480), Image.Resampling.NEAREST)
image.putalpha(255)
packs.write_pack(name + "-v1.bin", [image])
image.save(preview / (name + ".png"))
# Numerals now belong to the native screen-space renderer, not a warped image.
disk = rotor(numbered=False).resize((480, 480), Image.Resampling.NEAREST)
packs.write_pack("rotor-v2.bin", [disk])
disk.save(preview / "rotor-v2.png")
(packs.OUTPUT / "manifest-v1.json").write_text(json.dumps(manifest, indent=2) + "\n", encoding="ascii")
print("Packed the exact 37-pocket rotor; labels are rendered by MASM")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--rotor-only", action="store_true")
main(parser.parse_args().rotor_only)

View file

@ -0,0 +1,58 @@
"""Offline clean, tightly bounded furniture frames for the native MASM hall."""
import json
from pathlib import Path
from PIL import Image
from pack_arrival_assets import solid_alpha, write_pack
import pack_arrival_assets as packs
from pack_world_v4 import clean_cell
ROOT = Path(__file__).resolve().parents[2]
def frame(source, bounds, size):
item = clean_cell(solid_alpha(source.crop(bounds).convert("RGBA")))
# Independent frames have no nominal atlas-grid margins or neighboring limbs.
item = item.resize(size, Image.Resampling.NEAREST)
return solid_alpha(item)
def main():
packs.OUTPUT = ROOT / "assets/animations/hall"
atlas = Image.open(ROOT / "assets/sprites/casino-atlas-v1.png")
assert atlas.size == (1254, 1254)
def atlas_bounds(box):
return tuple(round(value * 1254 / 1280) for value in box)
items = [
("card-table", atlas, atlas_bounds((933, 415, 1266, 631)), (320, 208)),
("roulette-table", atlas, atlas_bounds((0, 646, 315, 938)), (288, 268)),
("backroom-table", atlas, atlas_bounds((320, 700, 625, 937)), (304, 236)),
]
source = Image.open(ROOT / "assets/sprites/hall/furniture-v5.png")
assert source.size == (1536, 1024)
items += [
("skill-cabinet", source, (210, 0, 635, 585), (224, 368)),
("lounge-chair", source, (907, 42, 1410, 579), (240, 256)),
("lamp-table", source, (241, 584, 627, 1024), (192, 224)),
("velvet-rope", source, (860, 584, 1439, 1024), (288, 192)),
]
manifest = {}
output = ROOT / "build/hall"
output.mkdir(parents=True, exist_ok=True)
for name, image, bounds, size in items:
result = frame(image, bounds, size)
write_pack(name + "-v5.bin", [result])
result.save(output / (name + "-v5.png"))
manifest[name] = {"size": size, "bounds": bounds, "frames": 1}
slots = [frame(atlas, atlas_bounds(box), (224, 256)) for box in
[(38, 348, 294, 643), (352, 348, 618, 643), (674, 348, 931, 643)]]
write_pack("slot-cabinets-v5.bin", slots)
manifest["slot-cabinets"] = {"size": (224, 256), "frames": 3}
(packs.OUTPUT / "manifest-furniture-v5.json").write_text(
json.dumps(manifest, indent=2) + "\n", encoding="ascii")
print("Packed seven separate furniture assets without atlas bleed")
if __name__ == "__main__":
main()

View file

@ -0,0 +1,52 @@
"""Normalize generated slot art and export ANM1 packs for the MASM runtime."""
import json
import struct
from pathlib import Path
from PIL import Image
ROOT = Path(__file__).resolve().parents[2]
SOURCE = ROOT / "assets/sprites/slots"
OUTPUT = ROOT / "assets/animations/slots"
def pack(name, frames):
width, height = frames[0].size
assert all(frame.size == (width, height) for frame in frames)
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)
pack("cabinet-v1.bin", [cabinet])
sheet = Image.open(SOURCE / "symbols-v1.png").convert("RGBA")
assert 1.8 < sheet.width / sheet.height < 2.2
symbols = []
for index in range(8):
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((52, 52), Image.Resampling.NEAREST)
frame = Image.new("RGBA", (64, 64))
frame.paste(cell, ((64 - cell.width) // 2, (64 - cell.height) // 2))
# Opaque pixel clusters allow the native clipped reel sampler to stay cheap.
frame.putalpha(frame.getchannel("A").point(lambda value: 255 if value >= 128 else 0))
symbols.append(frame)
pack("symbols-v1.bin", symbols)
manifest = {"symbols": ["seven", "cherries", "lemon", "bell", "BAR",
"grapes", "diamond", "crown"],
"cabinet": [640, 480], "symbol": [64, 64],
"pipeline": "offline normalization only; gameplay and reel animation are MASM"}
(OUTPUT / "manifest.json").write_text(json.dumps(manifest, indent=2) + "\n", encoding="ascii")
print("Packed native 777 cabinet and 8 alpha-tested 64x64 reel symbols")
if __name__ == "__main__":
main()

View file

@ -0,0 +1,6 @@
diff --git a/casino3d.inc b/casino3d.inc
--- a/casino3d.inc
+++ b/casino3d.inc
@@ -5373 +5373 @@
- invoke DrawString8x8, 108, 130, OFFSET hudNumBuffer, 00FFFFFFh
+ invoke DrawString8x8, 124, 130, OFFSET hudNumBuffer, 00FFFFFFh

View file

@ -13,6 +13,9 @@ actionReportText db "PASS: fullscreen/startup/audio/dice/2000 roulette rounds; 9
db "World: complete camera/car route; staff/front bar access; cabaret collision; six pocket throats; 100 varied-force breaks; 3 distinct drinks",13,10
db "Cards: exit/cancel from every active phase, frozen hand, mouse/keyboard, no refunds/double payouts; street lamp/window lightmap",13,10
db "Durak: 500 native complete games, 5184 beat pairs, transfers/takes/refill/trump, 36-card conservation, paging, exit in every phase",13,10
db "777: 8000 stop combinations, 500 complete spins at four rates, monotonic reels, payouts, safe queued exit, native framebuffer snapshots",13,10
db "Coin: 500 paid flips at four rates, safe exit in all phases, 1701 continuous poses, payouts, controls, missing art, 10 native frames",13,10
db "Coin detail: owned window, monitor bounds, restoration, F11, no quit/focus stealing; five private-buffer captures",13,10
actionReportSize equ ($-actionReportText)
actionDiceTimes dd 0,200,500,800,1200,1600
actionDice0 db "build\actions\dice-0.bmp",0
@ -89,6 +92,9 @@ include tools\tests\unavailable_test.inc
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\coin_detail_test.inc
include tools\tests\coin_test.inc
RunCasinoActionsTest3D PROC uses ebx esi edi
local savedSeed:DWORD,iterations:DWORD,chamberMask:DWORD,value1:DWORD,value2:DWORD,count:DWORD
@ -132,6 +138,11 @@ RunCasinoActionsTest3D PROC uses ebx esi edi
call ToggleFullscreen3D
cmp fullscreen3D, 1
jne action_fail_14
call RunCoinDetailWindowTest3D
test eax,eax
jz @F
ret
@@:
invoke ReleaseDC, h3DWnd, h3DDC
mov h3DDC, 0
invoke DestroyWindow, h3DWnd
@ -496,6 +507,16 @@ action_round_done:
test eax, eax
jz @F
ret
@@:
call RunSlotsTest3D
test eax,eax
jz @F
ret
@@:
call RunCoinTest3D
test eax,eax
jz @F
ret
@@:
call RunHallTest3D
test eax, eax

View file

@ -0,0 +1,103 @@
"""Validate native auxiliary-window frames and Russian CP1251 glyphs."""
import argparse
import hashlib
import json
import re
from pathlib import Path
from PIL import Image, ImageDraw
from check_card_exit_build import Coff
ROOT = Path(__file__).resolve().parents[2]
NAMES = ("idle", "air", "heads", "tails", "loss")
def font():
values = []
for line in (ROOT / "font8x8.inc").read_text(encoding="cp1251").splitlines():
if line.startswith("db "):
values.extend(int(v.strip()[:-1], 16) for v in line[3:].split(";")[0].split(","))
assert len(values) == 256 * 8
glyphs = [values[i * 8:(i + 1) * 8] for i in range(256)]
labels = json.loads((ROOT / "assets/data/ui-ru.json").read_text(encoding="utf-8"))
for text in labels.values():
for code in text.encode("cp1251"):
if code >= 128:
assert any(glyphs[code]), f"Empty Cyrillic glyph {code:02X}"
assert glyphs[code] != glyphs[ord("?")], f"Question mark instead of {code:02X}"
assert glyphs[0xA8] != glyphs[ord("E")], "Russian YO must have its own glyph"
widths = {
"coinFlipText3D": 6, "coinAgainText3D": 7, "coinExitText3D": 5,
"wheelRed3D": 5, "wheelBlack3D": 5, "wheelEven3D": 5, "wheelOdd3D": 5,
"wheelSpin3D": 7, "wheelClear3D": 5,
"pkCheckText3D": 8, "pkCallText3D": 8, "pkRaiseText3D": 8,
"pkFoldText3D": 8, "pkAllInText3D": 8, "cbHitText3D": 8,
"cbStandText3D": 8, "cbDoubleText3D": 8, "merchantBuy3D": 12,
"bs3d_textCuffKey": 8, "entryChoose3D": 16, "entryNew3D": 16,
"poolOpenText3D": 16, "poolSolidsText3D": 16, "poolStripesText3D": 16,
}
for symbol, maximum in widths.items():
assert len(labels[symbol]) <= maximum, f"Russian button overflow: {symbol}"
print(f"PASS: {len(labels)} translated labels have CP1251 glyphs including YO; narrow buttons fit")
return glyphs
def text_matches(frame, text, x, y, scale, ink, glyphs):
for code in text.encode("cp1251"):
for row, mask in enumerate(glyphs[code]):
for column in range(8):
if mask & (128 >> column):
for dy in range(scale):
for dx in range(scale):
assert frame.getpixel((x + column * scale + dx, y + row * scale + dy)) == ink, (text, x, y)
x += scale * 8
def captures(glyphs):
frames = {}
sheet = Image.new("RGB", (320 * 5, 424), (20, 25, 29))
draw = ImageDraw.Draw(sheet)
for i, name in enumerate(NAMES):
path = ROOT / "build/actions" / f"coin-detail-{name}.bmp"
frame = Image.open(path).convert("RGB")
assert frame.size == (320, 400), name
assert len(frame.getcolors(128000)) > 100, name
frame.save(path.with_suffix(".png"))
frames[name] = frame
sheet.paste(frame, (i * 320, 24))
draw.text((i * 320 + 8, 6), name, fill="white")
assert frame.getpixel((319, 399)) == (20, 25, 29)
assert len({hashlib.sha256(f.tobytes()).digest() for f in frames.values()}) == 5
for name, side, x in (("heads", "ОРЁЛ", 96), ("tails", "РЕШКА", 80), ("loss", "РЕШКА", 80)):
text_matches(frames[name], side, x, 284, 4, (244, 231, 200), glyphs)
text_matches(frames["heads"], "ВЫИГРЫШ", 104, 336, 2, (135, 212, 165), glyphs)
text_matches(frames["loss"], "ПРОИГРЫШ", 96, 336, 2, (233, 147, 131), glyphs)
text_matches(frames["air"], "МОНЕТА В ПОЛЁТЕ", 40, 310, 2, (229, 210, 158), glyphs)
assert frames["heads"].crop((60, 66, 260, 266)).tobytes() != frames["tails"].crop((60, 66, 260, 266)).tobytes()
assert frames["tails"].crop((60, 66, 260, 266)).tobytes() == frames["loss"].crop((60, 66, 260, 266)).tobytes()
sheet.save(ROOT / "build/actions/coin-detail-contact.png")
print("PASS: five distinct native detail frames; exact Russian result/win/loss glyphs and matching faces")
def parity():
tested = Coff(ROOT / "build/actions/actions-test.obj")
shipped = Coff(ROOT / "build/graphics/poker.obj")
for name, count in (("OpenCoinDetail3D", 0), ("CloseCoinDetail3D", 0), ("LayoutCoinDetail3D", 0),
("UpdateCoinDetail3D", 0), ("RenderCoinDetail3D", 0), ("CoinDetailEvents3D", 16),
("CoinDetailBox3D", 20), ("CoinDetailText3D", 20), ("CoinDetailFace3D", 8),
("CoinDetailNumber3D", 16), ("ToggleFullscreen3D", 0)):
symbol = f"_{name}@{count}"
assert tested.signature(symbol) == shipped.signature(symbol), name
print("PASS: shipping auxiliary-window lifecycle, renderer and fullscreen match native-tested MASM")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--parity", action="store_true")
parser.add_argument("--font-only", action="store_true")
args = parser.parse_args()
glyphs = font()
if not args.font_only:
captures(glyphs)
if args.parity:
parity()

View file

@ -0,0 +1,84 @@
"""Check coin pack layout, native framebuffer captures and shipping MASM parity."""
import argparse
import hashlib
import json
import struct
from pathlib import Path
from PIL import Image, ImageDraw
from check_card_exit_build import Coff
ROOT = Path(__file__).resolve().parents[2]
NAMES = ("idle", "hold", "release", "air", "apex", "descent", "land", "bounce", "result", "tails")
def assets():
manifest = json.loads((ROOT / "assets/animations/coin/manifest-v1.json").read_text())
for name, shape in (("table", (640, 480, 1)), ("faces", (128, 128, 2)), ("hands", (224, 224, 16))):
version = 2 if name == "hands" else 1
data = (ROOT / "assets/animations/coin" / f"{name}-v{version}.bin").read_bytes()
width, height, count = shape
size = width * height * 4
assert manifest[name] == list(shape), name
assert struct.unpack_from("<4s4I", data) == (b"ANM1", *shape, size * count), name
assert len(data) == 20 + size * count, name
assert set(data[23::4]) == ({255} if name == "table" else {0, 255}), name
hashes = []
for i in range(count):
pixels = data[20 + i * size:20 + (i + 1) * size]
hashes.append(hashlib.sha256(pixels).hexdigest())
opaque = sum(alpha == 255 for alpha in pixels[3::4])
assert opaque > width * height // 10, (name, i)
if name == "hands" and i < 5:
frame = Image.frombytes("RGBA", (width, height), pixels, "raw", "BGRA")
anchor = frame.crop((64, 12, 112, 32))
gold = sum(a == 255 and r > 130 and g * 100 > r * 70 and b * 100 < r * 40
for r, g, b, a in anchor.get_flattened_data())
assert gold > 10, ("Held coin lost its contact anchor", i, gold)
assert len(set(hashes)) == count, name
print("PASS: opaque table, two coin faces, sixteen hand frames; held coin/fingers share one sprite")
def captures():
sheet = Image.new("RGB", (1280, 5 * 504), (20, 25, 29))
labels = ImageDraw.Draw(sheet)
hashes = []
for i, name in enumerate(NAMES):
path = ROOT / "build/actions" / f"coin-{name}.bmp"
frame = Image.open(path).convert("RGB")
assert frame.size == (640, 480), name
assert len(frame.getcolors(640 * 480)) > 1000, name
hashes.append(hashlib.sha256(frame.tobytes()).hexdigest())
frame.save(path.with_suffix(".png"))
x, y = i % 2 * 640, i // 2 * 504
sheet.paste(frame, (x, y + 24))
labels.text((x + 8, y + 6), name, fill="white")
assert len(set(hashes)) == len(NAMES), "Toss captures must be distinct"
sheet.save(ROOT / "build/actions/coin-contact-v1.png")
print("PASS: 10 distinct, nonblank native frames including both result faces")
def parity():
tested = Coff(ROOT / "build/actions/actions-test.obj")
shipped = Coff(ROOT / "build/graphics/poker.obj")
for name, arguments in (("InitCoinGame", 0), ("HandleCoinInput", 0), ("RenderCoinGame", 0),
("InitNativeCoin3D", 0), ("LoadNativeCoinArt3D", 0),
("FreeNativeCoinArt3D", 0), ("HandleNativeCoin3D", 0),
("RenderNativeCoin3D", 0), ("CoinEscape3D", 0),
("CoinSettle3D", 0), ("CoinPose3D", 4), ("DrawCoinShadow3D", 20),
("CardTableEscape3D", 0)):
symbol = f"_{name}@{arguments}"
assert tested.signature(symbol) == shipped.signature(symbol), name
print("PASS: published coin renderer, controls, payout and exit match native-tested MASM")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--assets-only", action="store_true")
parser.add_argument("--parity", action="store_true")
options = parser.parse_args()
assets()
if not options.assets_only:
captures()
if options.parity:
parity()

View file

@ -0,0 +1,85 @@
"""Validate roulette assets, native captures and tested/shipping procedure parity."""
import argparse
import hashlib
import json
import re
import struct
from pathlib import Path
from PIL import Image, ImageDraw
from check_card_exit_build import Coff
ROOT = Path(__file__).resolve().parents[2]
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}
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")
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()
mask = int(low, 16) | int(high, 16) << 32
assert {n for n in range(37) if mask & (1 << n)} == RED
for name, size in (("table", (640, 480)), ("closeup", (640, 480)), ("rotor", (480, 480))):
version = 2 if name == "rotor" else 1
data = (ROOT / "assets/animations/wheel" / f"{name}-v{version}.bin").read_bytes()
assert struct.unpack_from("<4s4I", data) == (
b"ANM1", *size, 1, size[0] * size[1] * 4), name
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
print("PASS: three valid packs; exact European order, one zero and correct red/black colors")
def captures():
names = ("draft", "launch", "fast", "slow", "land", "result")
contact = Image.new("RGB", (1280, 3 * 504), (20, 25, 29))
labels = ImageDraw.Draw(contact)
hashes = []
for i, name in enumerate(names):
path = ROOT / "build/actions" / ("wheel-" + name + "-v1.bmp")
frame = Image.open(path).convert("RGB")
assert frame.size == (640, 480), name
assert len(frame.getcolors(640 * 480)) > 1000, name
hashes.append(hashlib.sha256(frame.tobytes()).hexdigest())
frame.save(path.with_suffix(".png"))
x, y = i % 2 * 640, i // 2 * 504
contact.paste(frame, (x, y + 24))
labels.text((x + 8, y + 6), name, fill="white")
assert len(set(hashes)) == len(names), "Animation captures must be distinct"
contact.save(ROOT / "build/actions/wheel-contact-v1.png")
print("PASS: six distinct nonblank native roulette frames at draft/spin/landing/result")
def parity():
tested = Coff(ROOT / "build/actions/actions-test.obj")
shipped = Coff(ROOT / "build/graphics/poker.obj")
functions = (("InitNativeWheel3D", 0), ("HandleNativeWheel3D", 0),
("RenderNativeWheel3D", 0), ("WheelSettle3D", 0),
("WheelAddBet3D", 4), ("WheelBetMultiplier3D", 8),
("WheelEscape3D", 0), ("WheelTravel3D", 12),
("DrawNativeWheelDisc3D", 20), ("RenderWheelScene3D", 0),
("LoadNativeWheelArt3D", 0), ("FreeNativeWheelArt3D", 0),
("DrawWheelDigit3D", 20), ("DrawWheelValue3D", 20), ("DrawWheelLabels3D", 20),
("AnimateCityPedestrians3D", 0), ("HandleArrival3D", 0))
for name, arguments in functions:
symbol = f"_{name}@{arguments}"
assert tested.signature(symbol) == shipped.signature(symbol), name
print("PASS: shipping roulette and street animation procedures match native-tested code")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--assets-only", action="store_true")
parser.add_argument("--parity", action="store_true")
options = parser.parse_args()
assets()
if not options.assets_only:
captures()
if options.parity:
parity()

View file

@ -0,0 +1,64 @@
"""Validate tight furniture assets, native captures and tested/shipping code parity."""
import argparse
import struct
from pathlib import Path
from PIL import Image
from check_card_exit_build import Coff
ROOT = Path(__file__).resolve().parents[2]
EXPECTED = {
"card-table": (320, 208, 1), "roulette-table": (288, 268, 1),
"backroom-table": (304, 236, 1), "skill-cabinet": (224, 368, 1),
"lounge-chair": (240, 256, 1), "lamp-table": (192, 224, 1),
"velvet-rope": (288, 192, 1), "slot-cabinets": (224, 256, 3),
}
def assets():
for name, (width, height, count) in EXPECTED.items():
data = (ROOT / "assets/animations/hall" / (name + "-v5.bin")).read_bytes()
assert struct.unpack_from("<4s4I", data) == (
b"ANM1", width, height, count, width * height * count * 4), name
assert len(data) == 20 + width * height * count * 4
for index in range(count):
offset = 20 + index * width * height * 4
frame = Image.frombytes("RGBA", (width, height),
data[offset:offset + width * height * 4], "raw", "BGRA")
alpha = frame.getchannel("A")
assert set(alpha.tobytes()) == {0, 255}, (name, index)
assert alpha.getbbox() == (0, 0, width, height), (name, index)
assert len(frame.convert("RGB").getcolors(width * height)) > 100, name
print("PASS: eight tight floor-anchored furniture packs; binary alpha and complete bounds")
def captures():
for name in ("hall-card-furniture-v5", "hall-skill-furniture-v5", "hall-lounge-v5"):
frame = Image.open(ROOT / "build/actions" / (name + ".bmp")).convert("RGB")
assert frame.size == (640, 480)
assert len(frame.getcolors(640 * 480)) > 1000
frame.save(ROOT / "build/actions" / (name + ".png"))
print("PASS: native close-up table, complete skill cabinet and lounge snapshots")
def parity():
tested = Coff(ROOT / "build/actions/actions-test.obj")
shipped = Coff(ROOT / "build/graphics/poker.obj")
for name, arguments in (("RenderSprites", 0), ("LoadHallAssets3D", 0),
("FreeHallAssets3D", 0), ("RenderHall3D", 0),
("CanWalkHall3D", 8)):
symbol = f"_{name}@{arguments}"
assert tested.signature(symbol) == shipped.signature(symbol), name
print("PASS: shipping hall furniture rendering/collision matches native test code")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--assets-only", action="store_true")
parser.add_argument("--parity", action="store_true")
options = parser.parse_args()
assets()
if not options.assets_only:
captures()
if options.parity:
parity()

View file

@ -13,8 +13,9 @@ 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') == 8
assert interaction.count('call BeginUnavailable3D') == 7
assert 'call InitNativeDurak3D' in interaction
assert 'call InitNativeSlots3D' in interaction
panel = Image.open(OUTPUT / 'unavailable.bmp').convert('RGB')
assert panel.size == (640, 480)
assert panel.getpixel((110, 190)) == (23, 29, 32)

View file

@ -0,0 +1,26 @@
"""Require the shipped slot machine to use the same code as the native tests."""
from pathlib import Path
from check_card_exit_build import Coff
ROOT = Path(__file__).resolve().parents[2]
def main():
tested = Coff(ROOT / "build/actions/actions-test.obj")
shipped = Coff(ROOT / "build/graphics/poker.obj")
functions = {"InitNativeSlots3D": 0, "FreeNativeSlots3D": 0,
"StartNativeSlots3D": 0, "HandleNativeSlots3D": 0,
"RenderNativeSlots3D": 0, "SlotMultiplier3D": 12,
"SettleNativeSlots3D": 0, "SlotEscape3D": 0, "SlotExitMouse3D": 0,
"SlotReelPosition3D": 4, "DrawSlotReel3D": 4,
"DrawSlotSymbol3D": 12, "Trigger3DInteraction": 0,
"StartCasino3D": 0, "CardTableEscape3D": 0}
for name, size in functions.items():
symbol = f"_{name}@{size}"
assert tested.signature(symbol) == shipped.signature(symbol), name
print("PASS: shipping slot rules, input, reels, exit and renderer match tested MASM code")
if __name__ == "__main__":
main()

View file

@ -0,0 +1,65 @@
"""Inspect snapshots from the real MASM reel renderer, not a Python simulation."""
import hashlib
import struct
from pathlib import Path
from PIL import Image, ImageChops
ROOT = Path(__file__).resolve().parents[2]
OUTPUT = ROOT / "build/actions"
NAMES = ["slots-ready", "slots-600", "slots-1799", "slots-1800",
"slots-2400", "slots-win", "slots-paytable", "slots-exit"]
def main():
packs = ROOT / "assets/animations/slots"
raw = (packs / "cabinet-v1.bin").read_bytes()
assert struct.unpack_from("<4s4I", raw) == (b"ANM1", 640, 480, 1, 640 * 480 * 4)
cabinet = Image.frombytes("RGBA", (640, 480), raw[20:], "raw", "BGRA").convert("RGB")
assert set(raw[23::4]) == {255}, "Fast cabinet copy requires fully opaque pixels"
symbols = (packs / "symbols-v1.bin").read_bytes()
assert struct.unpack_from("<4s4I", symbols) == (b"ANM1", 64, 64, 8, 64 * 64 * 4 * 8)
assert len(symbols) == 20 + 64 * 64 * 4 * 8
assert set(symbols[23::4]) == {0, 255}
stride = 64 * 64 * 4
assert len({hashlib.sha256(symbols[20 + index * stride:20 + (index + 1) * stride]).digest()
for index in range(8)}) == 8
frames = {}
for name in NAMES:
frame = Image.open(OUTPUT / (name + ".bmp")).convert("RGB")
assert frame.size == (640, 480)
assert len(frame.getcolors(640 * 480)) > 2000, name
frames[name] = frame
frame.save(OUTPUT / (name + ".png"))
assert len({hashlib.sha256(frame.tobytes()).digest() for frame in frames.values()}) == len(NAMES)
# No symbol may cover the reel separators, chrome frame or marquee.
for name in NAMES:
if name == "slots-paytable":
continue
difference = ImageChops.difference(frames[name], cabinet)
for y in range(100, 272):
if 188 <= y < 194:
continue # Payline markers and win illumination are intentional.
for x in range(170, 477):
in_reel = 124 <= y < 260 and any(left <= x < left + 88 for left in (180, 278, 376))
if not in_reel:
assert difference.getpixel((x, y)) == (0, 0, 0), (name, x, y)
first = (180, 124, 268, 260)
second = (278, 124, 366, 260)
third = (376, 124, 464, 260)
assert frames["slots-1800"].crop(first).tobytes() == frames["slots-2400"].crop(first).tobytes()
# The gold winning line is the only permitted difference on a stopped drum.
for rect in (first, second):
left, top, right, bottom = rect
for box in ((left, top, right, 188), (left, 194, right, bottom)):
assert frames["slots-2400"].crop(box).tobytes() == frames["slots-win"].crop(box).tobytes()
assert frames["slots-2400"].crop(third).tobytes() != frames["slots-win"].crop(third).tobytes()
contact = Image.new("RGB", (640 * 4, 480 * 2))
for index, name in enumerate(NAMES):
contact.paste(frames[name], ((index % 4) * 640, (index // 4) * 480))
contact.save(OUTPUT / "slots-contact.png")
print("PASS: 8 distinct native slot frames; pixel-accurate reel clipping; sequential stops; opaque cabinet and 8 real symbol sprites")
if __name__ == "__main__":
main()

View file

@ -13,8 +13,10 @@ PROPS = ["bench", "hydrant", "mailbox", "newspaper_box", "bollard",
def main():
pixels = struct.unpack("<12I", (OUTPUT / "street-visibility-v5.bin").read_bytes())
visibility = dict(zip(PROPS + ["walker_1", "walker_2", "walker_3", "walker_4"], pixels))
data = (OUTPUT / "street-visibility-v5.bin").read_bytes()
pixels = struct.unpack(f"<{len(data)//4}I", data)
assert len(pixels) == 14
visibility = dict(zip(PROPS + [f"walker_{i+1}" for i in range(6)], pixels))
assert pixels[5] == 0, ('Plants must be removed', visibility)
assert min(pixels[:5] + pixels[6:]) >= 128, visibility
names = [f"street-v5-{index}" for index in range(6)] + ["street-v5-door"]
@ -35,7 +37,7 @@ def main():
(OUTPUT / "street-v5-qa.json").write_text(
json.dumps({"visible_pixels": visibility, "native_frames": frames}, indent=2),
encoding="ascii")
print("PASS: two world-geometry benches, six other street prop types, four walkers; no plants;")
print("PASS: two world-geometry benches, six other street prop types, six outdoor walkers; no plants;")
print("seven distinct native route snapshots; " + json.dumps(visibility))

View file

@ -0,0 +1,200 @@
.data
coinDetailBmpHeader3D db 42h,4Dh
dd 512054
dw 0,0
dd 54,40,320,-400
dw 1,32
dd 0,512000,0,0,0,0
coinDetailCapture0 db "build\actions\coin-detail-idle.bmp",0
coinDetailCapture1 db "build\actions\coin-detail-air.bmp",0
coinDetailCapture2 db "build\actions\coin-detail-heads.bmp",0
coinDetailCapture3 db "build\actions\coin-detail-tails.bmp",0
coinDetailCapture4 db "build\actions\coin-detail-loss.bmp",0
coinDetailCaptures3D dd OFFSET coinDetailCapture0,OFFSET coinDetailCapture1,OFFSET coinDetailCapture2,OFFSET coinDetailCapture3,OFFSET coinDetailCapture4
.code
RunCoinDetailWindowTest3D PROC uses ebx esi edi
local before:CoinDetailRect3D,after:CoinDetailRect3D,panel:CoinDetailRect3D,message:C3D_MSG
invoke GetWindowRect,h3DWnd,ADDR before
call OpenCoinDetail3D
cmp coinDetailWnd3D,0
je detail_window_test_fail
mov ebx,coinDetailWnd3D
call OpenCoinDetail3D
cmp coinDetailWnd3D,ebx
jne detail_window_test_fail
invoke GetWindow,ebx,4
cmp eax,h3DWnd
jne detail_window_test_fail
invoke IsWindowVisible,ebx
test eax,eax
jnz detail_window_test_fail
call LayoutCoinDetail3D
invoke GetWindowRect,ebx,ADDR panel
invoke GetWindowRect,h3DWnd,ADDR after
mov eax,after.right
add eax,8
cmp panel.left,eax
jne detail_window_test_fail
mov eax,panel.right
cmp eax,coinDetailMonitor3D[28]
jg detail_window_test_fail
mov eax,panel.top
cmp eax,coinDetailMonitor3D[24]
jl detail_window_test_fail
mov eax,panel.bottom
cmp eax,coinDetailMonitor3D[32]
jg detail_window_test_fail
call CloseCoinDetail3D
cmp coinDetailWnd3D,0
jne detail_window_test_fail
invoke IsWindow,ebx
test eax,eax
jnz detail_window_test_fail
invoke GetWindowRect,h3DWnd,ADDR after
lea esi,before
lea edi,after
mov ecx,4
repe cmpsd
jne detail_window_test_fail
invoke PeekMessageA,ADDR message,0,WM_QUIT,WM_QUIT,0
test eax,eax
jnz detail_window_test_fail
; F11 preserves a live detail window without saving its temporary resize.
call OpenCoinDetail3D
call ToggleFullscreen3D
cmp fullscreen3D,0
jne detail_window_test_fail
cmp coinDetailWnd3D,0
je detail_window_test_fail
call ToggleFullscreen3D
cmp fullscreen3D,1
jne detail_window_test_fail
call CloseCoinDetail3D
invoke GetWindowRect,h3DWnd,ADDR after
lea esi,before
lea edi,after
mov ecx,4
repe cmpsd
jne detail_window_test_fail
; Closing the preview must not undo an explicit later move of the owner.
call OpenCoinDetail3D
invoke SetWindowPos,h3DWnd,0,coinDetailMonitor3D[20],coinDetailMonitor3D[24],600,300,14h
invoke GetWindowRect,h3DWnd,ADDR panel
call CloseCoinDetail3D
invoke GetWindowRect,h3DWnd,ADDR after
lea esi,panel
lea edi,after
mov ecx,4
repe cmpsd
jne detail_window_test_fail
mov eax,before.right
sub eax,before.left
mov edx,before.bottom
sub edx,before.top
invoke SetWindowPos,h3DWnd,0,before.left,before.top,eax,edx,14h
xor eax,eax
ret
detail_window_test_fail:
mov eax,8601
ret
RunCoinDetailWindowTest3D ENDP
WriteCoinDetailSnapshot3D PROC uses ebx path:DWORD
local count:DWORD
invoke CreateFileA,path,40000000h,0,0,2,80h,0
cmp eax,-1
je detail_snapshot_fail
mov ebx,eax
invoke WriteFile,ebx,OFFSET coinDetailBmpHeader3D,54,ADDR count,0
invoke WriteFile,ebx,OFFSET coinDetailBuffer3D,512000,ADDR count,0
invoke CloseHandle,ebx
cmp count,512000
jne detail_snapshot_fail
mov eax,1
ret
detail_snapshot_fail:
xor eax,eax
ret
WriteCoinDetailSnapshot3D ENDP
RunCoinDetailRenderTest3D PROC uses ebx esi edi
local oldBank:DWORD,oldSeed:DWORD,oldTimer:DWORD,oldMode:DWORD,oldState:DWORD,oldChoice:DWORD,oldResult:DWORD
mov eax,bankroll
mov oldBank,eax
mov eax,rngSeed3D
mov oldSeed,eax
mov eax,coin3d_timer
mov oldTimer,eax
mov eax,engineMode
mov oldMode,eax
mov eax,coin3d_state
mov oldState,eax
mov eax,coin3d_choice
mov oldChoice,eax
mov eax,coin3d_result
mov oldResult,eax
mov edi,OFFSET screenBuffer
mov ecx,SCREEN_SIZE
mov eax,00123456h
rep stosd
mov engineMode,MODE_COIN
mov coin3d_choice,1
mov coin3d_result,1
mov coinStartPhase3D,96
xor ebx,ebx
detail_render_test_frame:
mov coin3d_state,COIN_IDLE
mov coin3d_timer,0
test ebx,ebx
jz detail_render_test_capture
mov coin3d_state,COIN_TOSS
mov coin3d_timer,600
cmp ebx,1
je detail_render_test_capture
mov coin3d_state,COIN_RESULT
mov coin3d_timer,COIN_TOTAL_MS
mov coin3d_result,1
cmp ebx,2
je detail_render_test_capture
mov coin3d_result,2
mov coin3d_choice,2
cmp ebx,3
je detail_render_test_capture
mov coin3d_choice,1
detail_render_test_capture:
call RenderCoinDetail3D
mov eax,oldBank
cmp bankroll,eax
jne detail_render_test_fail
mov eax,oldSeed
cmp rngSeed3D,eax
jne detail_render_test_fail
mov esi,OFFSET screenBuffer
mov ecx,SCREEN_SIZE
detail_render_test_scene:
cmp DWORD PTR [esi],00123456h
jne detail_render_test_fail
add esi,4
loop detail_render_test_scene
invoke WriteCoinDetailSnapshot3D,coinDetailCaptures3D[ebx*4]
test eax,eax
jz detail_render_test_fail
inc ebx
cmp ebx,5
jb detail_render_test_frame
mov eax,oldTimer
mov coin3d_timer,eax
mov eax,oldMode
mov engineMode,eax
mov eax,oldState
mov coin3d_state,eax
mov eax,oldChoice
mov coin3d_choice,eax
mov eax,oldResult
mov coin3d_result,eax
xor eax,eax
ret
detail_render_test_fail:
mov eax,8602
ret
RunCoinDetailRenderTest3D ENDP

367
tools/tests/coin_test.inc Normal file
View file

@ -0,0 +1,367 @@
.data
coinTestStage3D dd 0
coinTestRates3D dd 8,16,33,250
coinTestTimes3D dd 0,180,300,420,600,900,1020,1110,1700
coinTest0 db "build\actions\coin-idle.bmp",0
coinTest1 db "build\actions\coin-hold.bmp",0
coinTest2 db "build\actions\coin-release.bmp",0
coinTest3 db "build\actions\coin-air.bmp",0
coinTest4 db "build\actions\coin-apex.bmp",0
coinTest5 db "build\actions\coin-descent.bmp",0
coinTest6 db "build\actions\coin-land.bmp",0
coinTest7 db "build\actions\coin-bounce.bmp",0
coinTest8 db "build\actions\coin-result.bmp",0
coinTest9 db "build\actions\coin-tails.bmp",0
coinTestPaths3D dd OFFSET coinTest0,OFFSET coinTest1,OFFSET coinTest2
dd OFFSET coinTest3,OFFSET coinTest4,OFFSET coinTest5
dd OFFSET coinTest6,OFFSET coinTest7,OFFSET coinTest8
.code
RunCoinTest3D PROC uses ebx esi edi
local oldBank:DWORD,oldSeed:DWORD,oldMode:DWORD,oldDelta:DWORD
local expected:DWORD,seedSnapshot:DWORD,ticks:DWORD,total:DWORD,iterations:DWORD
local pointer:DWORD,previousX:DWORD,previousY:DWORD
mov eax,bankroll
mov oldBank,eax
mov eax,rngSeed3D
mov oldSeed,eax
mov eax,engineMode
mov oldMode,eax
mov eax,deltaTime3D
mov oldDelta,eax
call ClearKeys3D
mov mouseClick3D,0
mov bankroll,100000
mov coinTestStage3D,1
mov engineMode,MODE_WALK
mov interactTargetId,INTERACT_COIN
call Trigger3DInteraction
cmp engineMode,MODE_COIN
jne coin_test_fail
cmp coinArtReady3D,1
jne coin_test_fail
cmp coinHandsArt3D.frames,16
jne coin_test_fail
mov eax,coinTableArt3D.pixels
mov pointer,eax
call InitCoinGame
mov eax,pointer
cmp coinTableArt3D.pixels,eax
jne coin_test_fail
; Missing packs and insufficient funds cannot consume a wager or RNG.
mov coinTestStage3D,2
call FreeNativeCoinArt3D
mov BYTE PTR coinHandsPath3D,'!'
call InitCoinGame
mov BYTE PTR coinHandsPath3D,'a'
test eax,eax
jnz coin_test_fail
cmp coinArtReady3D,0
jne coin_test_fail
cmp coinTableArt3D.pixels,0
jne coin_test_fail
mov eax,rngSeed3D
mov seedSnapshot,eax
mov pressedKeys3D[20h],1
call HandleCoinInput
cmp bankroll,100000
jne coin_test_fail
mov eax,seedSnapshot
cmp rngSeed3D,eax
jne coin_test_fail
call InitCoinGame
test eax,eax
jz coin_test_fail
mov bankroll,49
mov coin3d_bet,50
mov pressedKeys3D[20h],1
call HandleCoinInput
cmp bankroll,49
jne coin_test_fail
cmp coinError3D,1
jne coin_test_fail
cmp coin3d_state,COIN_IDLE
jne coin_test_fail
mov eax,seedSnapshot
cmp rngSeed3D,eax
jne coin_test_fail
; Bounds, face choices and edge-consumed controls use the real handler.
mov coinTestStage3D,3
mov pressedKeys3D[53h],1
call HandleCoinInput
cmp coin3d_bet,50
jne coin_test_fail
mov coin3d_bet,5000
mov pressedKeys3D[57h],1
call HandleCoinInput
cmp coin3d_bet,5000
jne coin_test_fail
mov coin3d_bet,100
mov mouseX3D,124
mov mouseY3D,204
mov mouseClick3D,1
call HandleCoinInput
cmp coin3d_bet,50
jne coin_test_fail
mov mouseX3D,148
mov mouseClick3D,1
call HandleCoinInput
cmp coin3d_bet,100
jne coin_test_fail
call HandleCoinInput
cmp coin3d_bet,100
jne coin_test_fail
mov mouseX3D,150
mov mouseY3D,226
mov mouseClick3D,1
call HandleCoinInput
cmp coin3d_choice,2
jne coin_test_fail
mov pressedKeys3D[31h],1
call HandleCoinInput
cmp coin3d_choice,1
jne coin_test_fail
; Five hundred paid flips, both outcomes and both choices, four frame rates.
mov coinTestStage3D,4
mov iterations,500
coin_test_round:
mov bankroll,100000
mov coin3d_state,COIN_IDLE
mov coin3d_timer,0
mov coin3d_bet,100
mov engineMode,MODE_COIN
mov eax,iterations
and eax,1
inc eax
mov coin3d_choice,eax
mov pressedKeys3D[20h],1
call HandleCoinInput
cmp coin3d_state,COIN_TOSS
jne coin_test_fail
cmp bankroll,99900
jne coin_test_fail
mov eax,iterations
shr eax,1
and eax,1
inc eax
mov coin3d_result,eax
mov expected,99900
cmp eax,coin3d_choice
jne @F
mov expected,100100
@@:
mov eax,rngSeed3D
mov seedSnapshot,eax
mov eax,iterations
and eax,3
mov eax,coinTestRates3D[eax*4]
mov deltaTime3D,eax
mov ticks,0
mov total,0
; Half the rounds request Escape before the toss completes.
test iterations,1
jz coin_test_tick
call CardTableEscape3D
cmp eax,1
jne coin_test_fail
cmp engineMode,MODE_COIN
jne coin_test_fail
cmp coinExitQueued3D,1
jne coin_test_fail
coin_test_tick:
call HandleCoinInput
inc ticks
mov eax,deltaTime3D
add total,eax
cmp ticks,326
ja coin_test_fail
cmp coin3d_state,COIN_TOSS
je coin_test_tick
cmp coin3d_state,COIN_RESULT
jne coin_test_fail
cmp coin3d_timer,COIN_TOTAL_MS
jne coin_test_fail
cmp total,COIN_TOTAL_MS
jb coin_test_fail
mov eax,COIN_TOTAL_MS
add eax,deltaTime3D
cmp total,eax
jae coin_test_fail
mov eax,expected
cmp bankroll,eax
jne coin_test_fail
call CoinSettle3D
mov eax,expected
cmp bankroll,eax
jne coin_test_fail
mov eax,seedSnapshot
cmp rngSeed3D,eax
jne coin_test_fail
test iterations,1
jz @F
cmp engineMode,MODE_WALK
jne coin_test_fail
@@:
dec iterations
jnz coin_test_round
; Exit during every animation phase settles the paid bet before leaving.
mov coinTestStage3D,5
xor esi,esi
coin_test_exit_phase:
mov coin3d_state,COIN_TOSS
mov coin3d_choice,1
mov coin3d_result,1
mov coin3d_bet,100
mov bankroll,99900
mov coinExitQueued3D,0
mov engineMode,MODE_COIN
mov eax,coinTestTimes3D[esi*4]
cmp eax,COIN_TOTAL_MS
jb @F
dec eax
@@:
mov coin3d_timer,eax
mov mouseClick3D,1
mov mouseX3D,280
mov mouseY3D,9
mov deltaTime3D,16
call HandleCoinInput
mov deltaTime3D,COIN_TOTAL_MS
cmp engineMode,MODE_WALK
je @F
call HandleCoinInput
@@:
cmp engineMode,MODE_WALK
jne coin_test_fail
cmp bankroll,100100
jne coin_test_fail
inc esi
cmp esi,9
jb coin_test_exit_phase
; Capture the actual renderer; rendering cannot move the simulation/RNG.
mov coinTestStage3D,6
mov engineMode,MODE_COIN
mov coin3d_choice,1
mov coin3d_result,1
mov coinStartPhase3D,96
mov coin3d_win,200
mov coinExitQueued3D,0
mov eax,rngSeed3D
mov seedSnapshot,eax
xor esi,esi
coin_test_snapshot:
mov coin3d_state,COIN_TOSS
test esi,esi
jnz @F
mov coin3d_state,COIN_IDLE
@@:
cmp esi,8
jne @F
mov coin3d_state,COIN_RESULT
@@:
mov eax,coinTestTimes3D[esi*4]
mov coin3d_timer,eax
call RenderCoinGame
mov eax,coinTestTimes3D[esi*4]
cmp coin3d_timer,eax
jne coin_test_fail
cmp bankroll,100100
jne coin_test_fail
mov eax,seedSnapshot
cmp rngSeed3D,eax
jne coin_test_fail
invoke WriteActionSnapshot3D,coinTestPaths3D[esi*4]
test eax,eax
jz coin_test_fail
inc esi
cmp esi,9
jb coin_test_snapshot
mov coin3d_result,2
mov coin3d_choice,2
call RenderCoinGame
cmp coinFace3D,1
jne coin_test_fail
invoke WriteActionSnapshot3D,OFFSET coinTest9
mov coin3d_choice,1
; Coin position remains continuous across release, flight and bounces.
mov coin3d_state,COIN_TOSS
xor esi,esi
invoke CoinPose3D,0
mov eax,coinX3D
mov previousX,eax
mov eax,coinY3D
mov previousY,eax
coin_test_pose:
invoke CoinPose3D,esi
mov eax,coinX3D
sub eax,previousX
test eax,eax
jns @F
neg eax
@@:
cmp eax,2
ja coin_test_fail
mov eax,coinY3D
sub eax,previousY
test eax,eax
jns @F
neg eax
@@:
cmp eax,2
ja coin_test_fail
mov eax,coinX3D
mov previousX,eax
mov eax,coinY3D
mov previousY,eax
cmp coinWidth3D,32
jb coin_test_fail
cmp coinHeight3D,3
jb coin_test_fail
inc esi
cmp esi,COIN_TOTAL_MS
jbe coin_test_pose
mov coin3d_state,COIN_RESULT
mov mouseClick3D,1
mov mouseX3D,250
mov mouseY3D,226
call HandleCoinInput
cmp coin3d_state,COIN_IDLE
jne coin_test_fail
cmp bankroll,100100
jne coin_test_fail
call HandleCoinInput
cmp coin3d_state,COIN_IDLE
jne coin_test_fail
mov bankroll,999999999
mov coin3d_state,COIN_TOSS
mov coin3d_bet,5000
mov coin3d_choice,1
mov coin3d_result,1
call CoinSettle3D
call CoinSettle3D
cmp bankroll,1000000000
jne coin_test_fail
call CoinEscape3D
cmp engineMode,MODE_WALK
jne coin_test_fail
mov eax,oldBank
mov bankroll,eax
mov eax,oldSeed
mov rngSeed3D,eax
mov eax,oldMode
mov engineMode,eax
mov eax,oldDelta
mov deltaTime3D,eax
call ClearKeys3D
call RunCoinDetailRenderTest3D
test eax,eax
jz @F
ret
@@:
call FreeNativeCoinArt3D
xor eax,eax
ret
coin_test_fail:
mov eax,coinTestStage3D
add eax,8100
ret
RunCoinTest3D ENDP

View file

@ -0,0 +1,94 @@
"""Offline assertion breakpoint in the native test executable, not game code."""
import ctypes as c
import struct
from capstone import Cs, CS_ARCH_X86, CS_MODE_32
from debug_native_crash import K, ROOT, Startup, Process, Event
from check_card_exit_build import Coff
def main():
obj = Coff(ROOT / "build/actions/actions-test.obj")
sec, start = obj.functions["_RunWheelSceneTest3D@0"]
raw, size, _ = obj.sections[sec]
end = min(v for s, v in obj.functions.values() if s == sec and v > start)
instructions = list(Cs(CS_ARCH_X86, CS_MODE_32).disasm(obj.data[raw + start:raw + end], start))
trap_offset = instructions[-6].address
assert instructions[-6].mnemonic == "mov"
symbols = {}
_, _, _, table, total, _, _ = struct.unpack_from("<HHIIIHH", obj.data)
index = 0
while index < total:
value, section, _, _, aux = struct.unpack_from("<IhHBB", obj.data, table + index * 18 + 8)
if section == 2:
symbols[obj.symbols[index]] = value
index += aux + 1
pe = (ROOT / "build/actions/actions-test.exe").read_bytes()
header = struct.unpack_from("<I", pe, 60)[0]
count = struct.unpack_from("<H", pe, header + 6)[0]
opt = struct.unpack_from("<H", pe, header + 20)[0]
sections = header + 24 + opt
data_rva = None
for i in range(count):
at = sections + i * 40
if pe[at:at + 8].rstrip(b"\0") == b".data":
data_rva = struct.unpack_from("<I", pe, at + 12)[0]
assert data_rva
K.WriteProcessMemory.argtypes = [c.c_void_p, c.c_void_p, c.c_void_p, c.c_size_t, c.c_void_p]
K.TerminateProcess.argtypes = [c.c_void_p, c.c_uint32]
start_info, process = Startup(), Process()
start_info.cb, start_info.flags, start_info.show = c.sizeof(start_info), 1, 0
executable = str(ROOT / "build/actions/actions-test.exe")
command = c.create_unicode_buffer('"' + executable + '"')
if not K.CreateProcessW(executable, command, None, None, False, 3, None,
str(ROOT), c.byref(start_info), c.byref(process)):
raise c.WinError(c.get_last_error())
base = trap = 0
def memory(address, size):
buffer, read = c.create_string_buffer(size), c.c_size_t()
assert K.ReadProcessMemory(process.process, address, buffer, size, c.byref(read))
return buffer.raw[:read.value]
while True:
event = Event()
assert K.WaitForDebugEvent(c.byref(event), 120000)
data, status = bytes(event.data), 0x10002
if event.code == 3:
base = struct.unpack_from("<Q", data, 24)[0]
trap = base + 0x1000 + trap_offset
assert K.WriteProcessMemory(process.process, trap, b"\xcc", 1, None)
elif event.code == 1:
exception = struct.unpack_from("<I", data)[0]
address = struct.unpack_from("<Q", data, 16)[0]
if exception in (0x80000003, 0x4000001F) and address == trap:
thread = K.OpenThread(0x48, False, event.tid)
context = c.create_string_buffer(716)
struct.pack_into("<I", context, 0, 0x10007)
assert K.Wow64GetThreadContext(thread, context)
regs = dict(zip(("edi", "esi", "ebx", "edx", "ecx", "eax", "ebp", "eip", "cs", "flags", "esp"),
struct.unpack_from("<11I", context, 156)))
print("ASSERTION REGISTERS:", regs, flush=True)
print("LOCALS:", struct.unpack("<7I", memory(regs["ebp"] - 28, 28)), flush=True)
for name in ("wheelArtReady3D", "wheelStake3D", "wheelReturn3D", "wheelResult3D",
"wheelResultSlot3D", "wheelBallAngle3D", "bankroll", "rngSeed3D", "roulState"):
print(name, struct.unpack("<I", memory(base + data_rva + symbols[name], 4))[0], flush=True)
K.CloseHandle(thread)
K.TerminateProcess(process.process, 1)
elif exception not in (0x80000003, 0x4000001F):
status = 0x80010001
elif event.code == 5:
print("PROCESS EXIT:", struct.unpack_from("<I", data)[0], flush=True)
K.ContinueDebugEvent(event.pid, event.tid, status)
break
elif event.code == 6:
handle = struct.unpack_from("<Q", data)[0]
if handle:
K.CloseHandle(handle)
K.ContinueDebugEvent(event.pid, event.tid, status)
K.CloseHandle(process.thread)
K.CloseHandle(process.process)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,80 @@
.data
hallFurnitureCardShot3D db "build\actions\hall-card-furniture-v5.bmp",0
hallFurnitureSkillShot3D db "build\actions\hall-skill-furniture-v5.bmp",0
hallFurnitureLoungeShot3D db "build\actions\hall-lounge-v5.bmp",0
.code
TestHallFurniture3D PROC uses ebx esi edi
cmp hallCardFurniture3D.imageWidth,320
jne furniture_test_fail
cmp hallCardFurniture3D.height,208
jne furniture_test_fail
cmp hallSkillFurniture3D.imageWidth,224
jne furniture_test_fail
cmp hallSkillFurniture3D.height,368
jne furniture_test_fail
cmp hallSlotFurniture3D.frames,3
jne furniture_test_fail
cmp hallStationPacks3D[12*4],OFFSET hallSkillFurniture3D
jne furniture_test_fail
cmp hallSpriteHeights3D[12*4],FP_1*9/5
jne furniture_test_fail
; New decor has floor anchors, valid rooms, collision and preserved walking routes.
xor esi,esi
furniture_test_positions:
imul ebx,esi,SIZEOF CityProp3D
cmp hallLoungeDecor3D[ebx].groundZ,0
jne furniture_test_fail
mov eax,hallLoungeDecor3D[ebx].posX
sar eax,16
mov edx,hallLoungeDecor3D[ebx].posY
sar edx,16
shl edx,5
add eax,edx
cmp casinoMap[eax],0
jne furniture_test_fail
invoke CanWalk3D,hallLoungeDecor3D[ebx].posX,hallLoungeDecor3D[ebx].posY
test eax,eax
jnz furniture_test_fail
inc esi
cmp esi,HALL_DECOR_COUNT
jb furniture_test_positions
invoke CanWalk3D,16*FP_1,18*FP_1
cmp eax,1
jne furniture_test_fail
mov engineMode,MODE_WALK
mov viewHorizon3D,HALF_H
mov viewEye3D,FP_1*17/20
mov playerAngle,384
mov playerX,12*FP_1+FP_HALF
mov playerY,11*FP_1
call UpdateCamera3D
call RenderCeilingAndFloor
call RenderWalls
call PrepareCityDepth3D
call RenderSprites
call RenderHall3D
invoke WriteActionSnapshot3D,OFFSET hallFurnitureCardShot3D
mov playerX,18*FP_1
mov playerY,18*FP_1
call UpdateCamera3D
call RenderCeilingAndFloor
call RenderWalls
call PrepareCityDepth3D
call RenderSprites
call RenderHall3D
invoke WriteActionSnapshot3D,OFFSET hallFurnitureSkillShot3D
mov playerX,14*FP_1
mov playerY,22*FP_1
call UpdateCamera3D
call RenderCeilingAndFloor
call RenderWalls
call PrepareCityDepth3D
call RenderSprites
call RenderHall3D
invoke WriteActionSnapshot3D,OFFSET hallFurnitureLoungeShot3D
mov eax,1
ret
furniture_test_fail:
xor eax,eax
ret
TestHallFurniture3D ENDP

View file

@ -87,6 +87,8 @@ surface_test_fail:
TestHallSurface3D ENDP
include tools\tests\world_regression.inc
include tools\tests\hall_furniture_test.inc
include tools\tests\wheel_scene_test.inc
RunHallTest3D PROC uses ebx esi edi
local oldBank:DWORD,oldSeed:DWORD,endX:DWORD,endV:DWORD
@ -114,6 +116,9 @@ RunHallTest3D PROC uses ebx esi edi
jne hall_test_fail
cmp hallDecorPack3D.frames, 6
jne hall_test_fail
call TestHallFurniture3D
cmp eax,1
jne hall_test_fail
call TestHallSurface3D
cmp eax, 1
jne hall_test_fail
@ -420,6 +425,9 @@ hall_test_friction:
call RenderCityArrival3D
invoke WriteActionSnapshot3D,OFFSET hallTestGuards3D
mov hallTestStage3D, 77
call RunWheelSceneTest3D
test eax,eax
jnz hall_test_fail
call RunWorldRegression3D
test eax, eax
jz hall_test_fail

423
tools/tests/slots_test.inc Normal file
View file

@ -0,0 +1,423 @@
.data
slotsTestStage3D dd 0
slotsTest0 db "build\actions\slots-ready.bmp",0
slotsTest1 db "build\actions\slots-600.bmp",0
slotsTest2 db "build\actions\slots-1799.bmp",0
slotsTest3 db "build\actions\slots-1800.bmp",0
slotsTest4 db "build\actions\slots-2400.bmp",0
slotsTest5 db "build\actions\slots-win.bmp",0
slotsTest6 db "build\actions\slots-paytable.bmp",0
slotsTest7 db "build\actions\slots-exit.bmp",0
slotsTestTimes dd 600,1799,1800,2400
slotsTestPaths dd OFFSET slotsTest1,OFFSET slotsTest2,OFFSET slotsTest3,OFFSET slotsTest4
slotsTestRates dd 16,33,50,250
.code
RunSlotsTest3D PROC uses ebx esi edi
local oldBank:DWORD,oldMode:DWORD,oldSeed:DWORD,oldX:DWORD,oldY:DWORD,oldAngle:DWORD
local total:DWORD,iterations:DWORD,expected:DWORD,rngSnapshot:DWORD,ticks:DWORD
local previous:DWORD,reel:DWORD,timer:DWORD,first:DWORD,second:DWORD,third:DWORD
mov eax,bankroll
mov oldBank,eax
mov eax,engineMode
mov oldMode,eax
mov eax,rngSeed3D
mov oldSeed,eax
mov eax,playerX
mov oldX,eax
mov eax,playerY
mov oldY,eax
mov eax,playerAngle
mov oldAngle,eax
mov bankroll,100000
mov slotsTestStage3D,1
mov engineMode,MODE_WALK
mov interactTargetId,INTERACT_SLOTS_777
call Trigger3DInteraction
cmp engineMode,MODE_SLOTS
jne slots_test_fail
cmp bankroll,100000
jne slots_test_fail
cmp slotSpinState,SLOT_READY
jne slots_test_fail
call TableBusy3D
test eax,eax
jnz slots_test_fail
call RenderNativeSlots3D
invoke WriteActionSnapshot3D,OFFSET slotsTest0
test eax,eax
jz slots_test_fail
; Invalid dimensions fail closed, with no charged stake or loaded remnants.
mov slotSymbolsPack3D.frames,7
call InitNativeSlots3D
test eax,eax
jnz slots_test_fail
cmp slotSymbolsPack3D.pixels,0
jne slots_test_fail
cmp slotCabinetPack3D.pixels,0
jne slots_test_fail
call StartNativeSlots3D
cmp bankroll,100000
jne slots_test_fail
mov BYTE PTR slotSymbolsPath3D,'!'
call InitNativeSlots3D
mov BYTE PTR slotSymbolsPath3D,'a'
test eax,eax
jnz slots_test_fail
cmp slotCabinetPack3D.pixels,0
jne slots_test_fail
call InitNativeSlots3D
test eax,eax
jz slots_test_fail
mov slotBet,0
call StartNativeSlots3D
cmp slotSpinState,SLOT_READY
jne slots_test_fail
mov slotBet,10001
call StartNativeSlots3D
cmp bankroll,100000
jne slots_test_fail
mov bankroll,49
mov slotBet,50
mov eax,rngSeed3D
mov rngSnapshot,eax
call StartNativeSlots3D
cmp bankroll,49
jne slots_test_fail
mov eax,rngSnapshot
cmp rngSeed3D,eax
jne slots_test_fail
cmp slotError3D,1
jne slots_test_fail
; Every possible 20^3 stop combination exercises the actual MASM evaluator.
mov slotsTestStage3D,2
mov total,0
xor ebx,ebx
slots_test_first:
xor esi,esi
slots_test_second:
xor edi,edi
slots_test_third:
mov eax,slotStrip3D[ebx*4]
mov first,eax
mov eax,slotStrip3D[esi*4]
mov second,eax
mov eax,slotStrip3D[edi*4]
mov third,eax
invoke SlotMultiplier3D,first,second,third
add total,eax
mov expected,eax
mov ecx,first
cmp ecx,second
jne slots_test_pair_other
cmp ecx,third
jne slots_test_pair
mov eax,slotTriple3D[ecx*4]
jmp slots_test_compare
slots_test_pair_other:
cmp ecx,third
je slots_test_pair
mov ecx,second
cmp ecx,third
jne slots_test_zero
slots_test_pair:
xor eax,eax
cmp ecx,1
sete al
inc eax
jmp slots_test_compare
slots_test_zero:
xor eax,eax
slots_test_compare:
cmp eax,expected
jne slots_test_fail
inc edi
cmp edi,20
jb slots_test_third
inc esi
cmp esi,20
jb slots_test_second
inc ebx
cmp ebx,20
jb slots_test_first
cmp total,7202
jne slots_test_fail
invoke SlotMultiplier3D,8,8,8
test eax,eax
jnz slots_test_fail
; Five hundred complete paid spins at four update rates, no double debit/pay.
mov slotsTestStage3D,3
mov iterations,500
slots_test_round:
mov slotSpinState,SLOT_READY
mov slotBet,100
mov slotPaytable3D,0
mov bankroll,100000
call StartNativeSlots3D
cmp bankroll,99900
jne slots_test_fail
cmp slotSpinState,SLOT_SPIN
jne slots_test_fail
mov eax,rngSeed3D
mov rngSnapshot,eax
call StartNativeSlots3D
cmp bankroll,99900
jne slots_test_fail
mov eax,rngSnapshot
cmp rngSeed3D,eax
jne slots_test_fail
invoke SlotMultiplier3D,slotReel1,slotReel2,slotReel3
imul eax,100
add eax,99900
mov expected,eax
mov eax,iterations
and eax,3
mov eax,slotsTestRates[eax*4]
mov deltaTime3D,eax
mov ticks,0
slots_test_round_tick:
; Spinning ignores wager and start input, rather than queueing it for later.
mov pressedKeys3D[57h],1
mov pressedKeys3D[20h],1
call HandleNativeSlots3D
call ClearKeys3D
cmp slotBet,100
jne slots_test_fail
mov eax,rngSnapshot
cmp rngSeed3D,eax
jne slots_test_fail
inc ticks
cmp ticks,200
ja slots_test_fail
cmp slotSpinState,SLOT_SPIN
je slots_test_round_tick
mov eax,expected
cmp bankroll,eax
jne slots_test_fail
call SettleNativeSlots3D
mov eax,expected
cmp bankroll,eax
jne slots_test_fail
xor esi,esi
slots_test_final_reel:
invoke SlotReelPosition3D,esi
shr eax,6
cmp eax,slotTargets3D[esi*4]
jne slots_test_fail
inc esi
cmp esi,3
jb slots_test_final_reel
dec iterations
jnz slots_test_round
; The same millisecond timestamp gives the same strictly nondecreasing path.
mov slotsTestStage3D,4
mov slotSpinState,SLOT_SPIN
mov reel,0
slots_test_monotonic_reel:
mov timer,0
mov previous,0
slots_test_monotonic_tick:
mov eax,timer
mov slotSpinTimer,eax
invoke SlotReelPosition3D,reel
cmp eax,previous
jb slots_test_fail
mov previous,eax
inc timer
cmp timer,3000
jbe slots_test_monotonic_tick
inc reel
cmp reel,3
jb slots_test_monotonic_reel
; Force known stops for visual timing snapshots; neither drawing nor a panel pays.
mov slotsTestStage3D,5
mov slotSpinState,SLOT_READY
mov slotStops3D,0
mov slotStops3D[4],6
mov slotStops3D[8],12
mov bankroll,100000
call StartNativeSlots3D
mov slotReel1,0
mov slotReel2,0
mov slotReel3,0
xor esi,esi
slots_test_force_sevens:
mov slotTargets3D[esi*4],10
mov eax,10
sub eax,slotStartStops3D[esi*4]
jns @F
add eax,20
@@:
add eax,slotCycles3D[esi*4]
shl eax,6
mov slotTravel3D[esi*4],eax
inc esi
cmp esi,3
jb slots_test_force_sevens
xor esi,esi
slots_test_snapshots:
mov eax,slotsTestTimes[esi*4]
mov slotSpinTimer,eax
mov eax,rngSeed3D
mov rngSnapshot,eax
call RenderNativeSlots3D
mov eax,rngSnapshot
cmp rngSeed3D,eax
jne slots_test_fail
cmp bankroll,99900
jne slots_test_fail
mov eax,slotsTestTimes[esi*4]
cmp slotSpinTimer,eax
jne slots_test_fail
invoke WriteActionSnapshot3D,slotsTestPaths[esi*4]
test eax,eax
jz slots_test_fail
inc esi
cmp esi,4
jb slots_test_snapshots
call SettleNativeSlots3D
cmp bankroll,109900
jne slots_test_fail
call RenderNativeSlots3D
invoke WriteActionSnapshot3D,OFFSET slotsTest5
mov pressedKeys3D[50h],1
mov deltaTime3D,16
call HandleNativeSlots3D
cmp slotPaytable3D,1
jne slots_test_fail
call RenderNativeSlots3D
invoke WriteActionSnapshot3D,OFFSET slotsTest6
call StartNativeSlots3D
cmp bankroll,109900
jne slots_test_fail
call SlotEscape3D
cmp slotPaytable3D,0
jne slots_test_fail
cmp engineMode,MODE_SLOTS
jne slots_test_fail
; Header clicks use the same safe queued-exit path as Escape.
mov slotsTestStage3D,6
call StartNativeSlots3D
invoke SlotMultiplier3D,slotReel1,slotReel2,slotReel3
imul eax,100
add eax,109800
mov expected,eax
mov mouseClick3D,1
mov mouseX3D,280
mov mouseY3D,9
call HandleNativeSlots3D
cmp slotLeaveAfter3D,1
jne slots_test_fail
cmp paused3D,0
jne slots_test_fail
cmp engineMode,MODE_SLOTS
jne slots_test_fail
call RenderNativeSlots3D
invoke WriteActionSnapshot3D,OFFSET slotsTest7
mov deltaTime3D,250
mov ticks,0
slots_test_exit_tick:
call HandleNativeSlots3D
inc ticks
cmp ticks,13
ja slots_test_fail
cmp engineMode,MODE_SLOTS
je slots_test_exit_tick
cmp engineMode,MODE_WALK
jne slots_test_fail
mov eax,expected
cmp bankroll,eax
jne slots_test_fail
mov eax,oldX
cmp playerX,eax
jne slots_test_fail
mov eax,oldY
cmp playerY,eax
jne slots_test_fail
mov eax,oldAngle
cmp playerAngle,eax
jne slots_test_fail
; Mouse and key edge controls have identical bounded wager selections.
mov slotsTestStage3D,7
mov engineMode,MODE_SLOTS
call InitNativeSlots3D
test eax,eax
jz slots_test_fail
mov mouseX3D,44
mov mouseY3D,226
mov mouseClick3D,1
call HandleNativeSlots3D
cmp slotBet,100
jne slots_test_fail
call HandleNativeSlots3D
cmp slotBet,100
jne slots_test_fail
mov pressedKeys3D[53h],1
call HandleNativeSlots3D
cmp slotBet,50
jne slots_test_fail
call HandleNativeSlots3D
cmp slotBet,50
jne slots_test_fail
mov slotBetIndex3D,0
mov slotBet,10
mov pressedKeys3D[28h],1
call HandleNativeSlots3D
cmp slotBet,10
jne slots_test_fail
mov slotBetIndex3D,6
mov slotBet,10000
mov pressedKeys3D[26h],1
call HandleNativeSlots3D
cmp slotBet,10000
jne slots_test_fail
mov bankroll,100000
call StartNativeSlots3D
invoke SlotMultiplier3D,slotReel1,slotReel2,slotReel3
imul eax,10000
add eax,90000
mov expected,eax
mov slotSpinTimer,2999
mov deltaTime3D,16
mov mouseX3D,280
mov mouseY3D,9
mov mouseClick3D,1
call HandleNativeSlots3D
cmp engineMode,MODE_WALK
jne slots_test_fail
mov eax,expected
cmp bankroll,eax
jne slots_test_fail
mov engineMode,MODE_SLOTS
call CardTableEscape3D
cmp eax,1
jne slots_test_fail
cmp engineMode,MODE_WALK
jne slots_test_fail
; Bank cap and idempotent settlement remain safe at the maximum wager.
mov bankroll,999999999
mov slotStake3D,10000
mov slotReel1,7
mov slotReel2,7
mov slotReel3,7
mov slotSpinState,SLOT_SPIN
call SettleNativeSlots3D
cmp bankroll,1000000000
jne slots_test_fail
call SettleNativeSlots3D
cmp bankroll,1000000000
jne slots_test_fail
mov eax,oldBank
mov bankroll,eax
mov eax,oldSeed
mov rngSeed3D,eax
mov eax,oldMode
mov engineMode,eax
call ClearKeys3D
call FreeNativeSlots3D
xor eax,eax
ret
slots_test_fail:
mov eax,slotsTestStage3D
add eax,440
ret
RunSlotsTest3D ENDP

View file

@ -6,10 +6,32 @@ include F:\MASM32\masm32\include\kernel32.inc
InitPaths3D PROTO
LoadAnimPack3D PROTO :DWORD,:DWORD
FreeAnimPack3D PROTO :DWORD
InitNativeSlots3D PROTO
FreeNativeSlots3D PROTO
LoadNativeWheelArt3D PROTO
FreeNativeWheelArt3D PROTO
LoadNativeCoinArt3D PROTO
FreeNativeCoinArt3D PROTO
.data
streetPack dd 6 dup(0)
streetPath db "assets\animations\hall\street-life-v4.bin",0
missingPath db "assets\animations\hall\__missing_street_probe__.bin",0
cardFurniturePath db "assets\animations\hall\card-table-v5.bin",0
wheelFurniturePath db "assets\animations\hall\roulette-table-v5.bin",0
backFurniturePath db "assets\animations\hall\backroom-table-v5.bin",0
skillFurniturePath db "assets\animations\hall\skill-cabinet-v5.bin",0
chairFurniturePath db "assets\animations\hall\lounge-chair-v5.bin",0
lampFurniturePath db "assets\animations\hall\lamp-table-v5.bin",0
ropeFurniturePath db "assets\animations\hall\velvet-rope-v5.bin",0
slotFurniturePath db "assets\animations\hall\slot-cabinets-v5.bin",0
furniturePaths dd OFFSET cardFurniturePath,320,208,1
dd OFFSET wheelFurniturePath,288,268,1
dd OFFSET backFurniturePath,304,236,1
dd OFFSET skillFurniturePath,224,368,1
dd OFFSET chairFurniturePath,240,256,1
dd OFFSET lampFurniturePath,192,224,1
dd OFFSET ropeFurniturePath,288,192,1
dd OFFSET slotFurniturePath,224,256,3
.code
RunStreetAssetProbe PROC
call InitPaths3D
@ -25,6 +47,40 @@ RunStreetAssetProbe PROC
cmp streetPack[20],0
je probe_failed
invoke FreeAnimPack3D,OFFSET streetPack
; The newly playable cabinet must resolve its packs from these launch paths too.
call InitNativeSlots3D
cmp eax,1
jne probe_failed
call FreeNativeSlots3D
call LoadNativeWheelArt3D
cmp eax,1
jne probe_failed
call FreeNativeWheelArt3D
call LoadNativeCoinArt3D
cmp eax,1
jne probe_failed
call FreeNativeCoinArt3D
mov esi,OFFSET furniturePaths
mov edi,8
probe_furniture:
invoke LoadAnimPack3D,OFFSET streetPack,[esi]
cmp eax,1
jne probe_failed
mov eax,[esi+4]
cmp streetPack[4],eax
jne probe_failed
mov eax,[esi+8]
cmp streetPack[8],eax
jne probe_failed
mov eax,[esi+12]
cmp streetPack[12],eax
jne probe_failed
cmp streetPack[20],0
je probe_failed
invoke FreeAnimPack3D,OFFSET streetPack
add esi,16
dec edi
jnz probe_furniture
; Rejecting a missing asset must not leave a plausible loaded header behind.
invoke LoadAnimPack3D,OFFSET streetPack,OFFSET missingPath
test eax,eax

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